diff --git a/.gitignore b/.gitignore index e91237ad7..cb73be599 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,7 @@ tunnel.log /.well-known-cache /**/tmp-* /testdata/bigAlloc_*.csv -/sessions \ No newline at end of file +/sessions +/changelog.json +/.current-version +/.current-changelog.md \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ba71103e..a6a0e7ac3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,9 @@ +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "push"' + when: always + - when: never + default: image: name: fpco/stack-build:lts-16.11 @@ -21,6 +27,7 @@ variables: UPLOAD_S3_KEY_ID: gOel7KvadwNKgjjy UPLOAD_S3_KEY: ugO5pkEla7F0JW9MdPwLi4MWLT5ZbqAL N_PREFIX: "${HOME}/.n" + PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/uni2work" stages: - setup @@ -28,7 +35,10 @@ stages: - yesod:build - lint - test - - deploy + - prepare release + - upload packages + - release +# - deploy npm install: stage: setup @@ -51,7 +61,7 @@ npm install: artifacts: paths: - node_modules/ - name: "${CI_JOB_NAME}" + name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" expire_in: "1 day" retry: 2 interruptible: true @@ -68,7 +78,7 @@ frontend:build: - static - well-known - config/webpack.yml - name: "${CI_JOB_NAME}" + name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" expire_in: "1 day" dependencies: - npm install @@ -112,14 +122,16 @@ yesod:build:dev: artifacts: paths: - bin/ - name: "${CI_JOB_NAME}" + name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" expire_in: "1 week" dependencies: - frontend:build - only: - variables: - - $CI_COMMIT_REF_NAME !~ /^v[0-9].*/ + rules: + - if: $CI_COMMIT_REF_NAME =~ /(^v[0-9].*)|((^|\/)profile($|\/))/ + when: manual + - when: always + retry: 2 interruptible: true @@ -135,13 +147,42 @@ yesod:build: artifacts: paths: - bin/ - name: "${CI_JOB_NAME}" + name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" dependencies: - frontend:build - only: - variables: - - $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ + rules: + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ + when: always + - when: manual + + retry: 2 + interruptible: true + resource_group: ram + +yesod:build:profile: + cache: + <<: *global_cache + policy: pull + + stage: yesod:build + script: + - stack build --profile --copy-bins --local-bin-path $(pwd)/bin --flag uniworx:-library-only --flag uniworx:-dev --flag uniworx:pedantic --no-strip + needs: + - frontend:build + before_script: *haskell + artifacts: + paths: + - bin/ + name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" + dependencies: + - frontend:build + + rules: + - if: $CI_COMMIT_REF_NAME =~ /(^|\/)profile($|\/)/ + when: always + - when: manual + retry: 2 interruptible: true resource_group: ram @@ -170,28 +211,106 @@ frontend:test: retry: 2 interruptible: true -deploy:uniworx3: +parse-changelog: cache: {} - stage: deploy - variables: - GIT_STRATEGY: none - script: - - zip -qj - bin/uniworx bin/uniworxdb | ssh root@uniworx3.ifi.lmu.de /root/bin/accept_uni2work + stage: prepare release + dependencies: + - npm install needs: - - yesod:build - - frontend:test # For sanity + - npm install + rules: + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ + when: always before_script: - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list - apt-get update -y - - apt-get install -y --no-install-recommends openssh-client - - install -v -m 0700 -d ~/.ssh - - install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts - - install -v -T -m 0400 ${SSH_PRIVATE_KEY_UNIWORX3} ~/.ssh/uniworx3; echo "IdentityFile ~/.ssh/uniworx3" >> ~/.ssh/config; + - apt-get install -y --no-install-recommends jq + script: + - npm run parse-changelog + - | + jq -r '.versions[0].version' changelog.json > .current-version + - | + jq -r '.versions[0].body' changelog.json > .current-changelog.md + artifacts: + paths: + - .current-version + - .current-changelog.md + name: "changelog-${CI_COMMIT_SHORT_SHA}" + expire_in: "1 day" + retry: 2 + interruptible: true + +upload: + cache: {} + stage: upload packages + image: curlimages/curl:latest + needs: + - yesod:build + - parse-changelog dependencies: - yesod:build + - parse-changelog + rules: + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ + when: always + before_script: + - export VERSION="$(cat .current-version)" + script: + - | + curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file bin/uniworx ${PACKAGE_REGISTRY_URL}/${VERSION}/uniworx + - | + curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file bin/uniworxdb ${PACKAGE_REGISTRY_URL}/${VERSION}/uniworxdb + - | + curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file bin/uniworxload ${PACKAGE_REGISTRY_URL}/${VERSION}/uniworxload + - | + curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file bin/uniworx-wflint ${PACKAGE_REGISTRY_URL}/${VERSION}/uniworx-wflint + +release: + cache: {} + stage: release + image: registry.gitlab.com/gitlab-org/release-cli:latest + needs: + - upload + - parse-changelog + dependencies: + - parse-changelog + rules: + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ + when: always + before_script: + - export VERSION="$(cat .current-version)" + script: + - | + release-cli create --name "${VERSION}" --tag-name $CI_COMMIT_TAG --description ./current-changelog.md \ + --assets-link "{\"name\":\"uniworx\",\"url\":\"${PACKAGE_REGISTRY_URL}/${VERSION}/uniworx\",\"filepath\":\"uniworx\"}" \ + --assets-link "{\"name\":\"uniworxdb\",\"url\":\"${PACKAGE_REGISTRY_URL}/${VERSION}/uniworxdb\",\"filepath\":\"uniworxdb\"}" \ + --assets-link "{\"name\":\"uniworxload\",\"url\":\"${PACKAGE_REGISTRY_URL}/${VERSION}/uniworxload\",\"filepath\":\"uniworxload\"}" \ + --assets-link "{\"name\":\"uniworx-wflint\",\"url\":\"${PACKAGE_REGISTRY_URL}/${VERSION}/uniworx-wflint\",\"filepath\":\"uniworx-wflint\"}" + + +# deploy:uniworx3: +# cache: {} +# stage: deploy +# variables: +# GIT_STRATEGY: none +# script: +# - zip -qj - bin/uniworx bin/uniworxdb | ssh root@uniworx3.ifi.lmu.de /root/bin/accept_uni2work +# needs: +# - yesod:build +# - frontend:test # For sanity +# before_script: +# - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d +# - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list +# - apt-get update -y +# - apt-get install -y --no-install-recommends openssh-client +# - install -v -m 0700 -d ~/.ssh +# - install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts +# - install -v -T -m 0400 ${SSH_PRIVATE_KEY_UNIWORX3} ~/.ssh/uniworx3; echo "IdentityFile ~/.ssh/uniworx3" >> ~/.ssh/config; +# dependencies: +# - yesod:build - only: - variables: - - $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ - resource_group: uniworx3 +# only: +# variables: +# - $CI_COMMIT_REF_NAME =~ /^v[0-9].*/ +# resource_group: uniworx3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 44f426919..e22590a6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [24.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v24.1.1...v24.1.2) (2021-02-09) + +## [24.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v24.1.0...v24.1.1) (2021-02-09) + +## [24.1.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v24.0.0...v24.1.0) (2021-02-08) + + +### Features + +* ensure cached study feature relevance is up to date ([8798f54](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8798f547a60a7fa7c0849e20e1b0e9d012ac9312)) + + +### Bug Fixes + +* restore storting for exam-office exams ([5698e9c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5698e9ca0bb19585b9a9d2d3c10f8b5f99ae5db9)) + +## [24.0.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.7.0...v24.0.0) (2021-02-01) + + +### ⚠ BREAKING CHANGES + +* **jobs:** Job offloading + +### Features + +* **jobs:** batch job offloading ([09fb26f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/09fb26f1a892feba32185166223f8f95611ea9ef)) + + +### Bug Fixes + +* **workflows:** don't cache instance-list empty for correctness ([cb1e715](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cb1e715e9b2da2f5ac0bd03b636de0f961307efd)) + +## [23.7.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.6.0...v23.7.0) (2021-01-27) + + +### Features + +* **dbtable:** extra representations ([2c0fc63](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2c0fc63be1de02e8acffbc6a9c5ee83b061c5825)) +* **exams:** exam sheets ([500000b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/500000ba0f6f3b3c32cfd7593e5468796660d46b)) + + +### Bug Fixes + +* more verbose watchdog notification failures ([48028c4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/48028c40532577f74430340ed924af7116b8bd96)) +* **mass-input:** properly escape query selector ([9a3f401](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9a3f401b38e86e2f9e7fa722698a437d853b422e)) +* nonmoving-gc still segfaults ([c404ce9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c404ce9b3529cf402a0f9d649ca3299df09ba089)) + ## [23.6.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.5.0...v23.6.0) (2020-12-15) @@ -23,16 +70,16 @@ All notable changes to this project will be documented in this file. See [standa * **tokens:** introduce clock leniency and remove start for downloads ([8939a8b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8939a8b90a39a26614da18dd3985aee253cd191f)) * hopefully improve workflow auth performance ([1d3fd8c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1d3fd8c8a7824d6c6d043f4114067238af4bdc6e)) -### [23.4.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.2...v23.4.3) (2020-12-10) +## [23.4.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.2...v23.4.3) (2020-12-10) -### [23.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.1...v23.4.2) (2020-12-10) +## [23.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.1...v23.4.2) (2020-12-10) ### Bug Fixes * hopefully speed up aeson via ffi ([a00ba10](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a00ba10e9cf1ffa534908b9125730e88179052eb)) -### [23.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.0...v23.4.1) (2020-12-10) +## [23.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.4.0...v23.4.1) (2020-12-10) ## [23.4.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.3.0...v23.4.0) (2020-12-09) @@ -55,14 +102,14 @@ All notable changes to this project will be documented in this file. See [standa * **admin-crontab:** export as json ([bbd4916](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bbd4916f3a556ce4c05eb3b2b5268c9c072fdfdd)) * **jobs:** queue by jobctl priority ([a27a553](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a27a553e0a9782eda6023ec0b8b1055757bb511f)) -### [23.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.2.1...v23.2.2) (2020-12-09) +## [23.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.2.1...v23.2.2) (2020-12-09) ### Bug Fixes * **jobs:** adjust job handling to hopefully reduce load ([ed38f93](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ed38f93537b57b5b3e4563dc0259d805760071bc)) -### [23.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.2.0...v23.2.1) (2020-12-08) +## [23.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.2.0...v23.2.1) (2020-12-08) ### Bug Fixes @@ -82,14 +129,14 @@ All notable changes to this project will be documented in this file. See [standa * **auth:** fix infinite auth loop for workflow files ([21cf6cf](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/21cf6cfa873b841c2f9f8ab9f69c08ea72fc2420)) -### [23.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.1.1...v23.1.2) (2020-12-05) +## [23.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.1.1...v23.1.2) (2020-12-05) ### Bug Fixes * submission download token generation broke viewing ([e1b6084](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e1b60844cb77b1fd41900d0a3c4829ba21b6b3fe)) -### [23.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.1.0...v23.1.1) (2020-12-05) +## [23.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.1.0...v23.1.1) (2020-12-05) ### Bug Fixes @@ -118,7 +165,7 @@ All notable changes to this project will be documented in this file. See [standa * **tests:** generate sensible WorkflowPayloadLabels ([8a888d3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8a888d3945f0fd0d67ef83bae621744c943b99de)) * **workflows:** properly offer previous payload files ([aa0404a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/aa0404a0075acbcd4c6f94984acdbb4d68f08d0a)) -### [23.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.2...v23.0.3) (2020-11-29) +## [23.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.2...v23.0.3) (2020-11-29) ### Bug Fixes @@ -129,7 +176,7 @@ All notable changes to this project will be documented in this file. See [standa * build ([43bb0ab](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/43bb0abe7218f6d43b52d9a64e62f0dc29b9972e)) * **rooms:** honor roomHidden ([ed5d871](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ed5d871182954e2f0a9a5063f61277d925628c40)) -### [23.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.1...v23.0.2) (2020-11-28) +## [23.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.1...v23.0.2) (2020-11-28) ### Bug Fixes @@ -137,7 +184,7 @@ All notable changes to this project will be documented in this file. See [standa * **tests:** remove invalid claim of commutativity ([d2f0361](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d2f0361e49114e6dc6c55e64b677b8c842e93bee)) * build ([23a21b9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/23a21b905c902bea5fe88abd84da600e757a194e)) -### [23.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.0...v23.0.1) (2020-11-27) +## [23.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v23.0.0...v23.0.1) (2020-11-27) ### Bug Fixes @@ -196,7 +243,7 @@ Also improve efficiency of marking workflow files as referenced * **workflows:** refer by id in model ([94f78a0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/94f78a07d9376670122a2adce01cf7180a64d33d)) * **workflows:** ui improvements ([c7f4fa0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c7f4fa0e412d2b920a3819ffed5b79b8aeea2842)) -### [22.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v22.1.0...v22.1.1) (2020-11-14) +## [22.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v22.1.0...v22.1.1) (2020-11-14) ## [22.1.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v22.0.0...v22.1.0) (2020-11-10) @@ -222,7 +269,7 @@ Also improve efficiency of marking workflow files as referenced * **html-field:** introduce stored-markup ([e25e8a2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e25e8a2f4ca65afc29acc8a3884df9acf77d4398)) -### [21.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.1.0...v21.1.1) (2020-11-06) +## [21.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.1.0...v21.1.1) (2020-11-06) ### Bug Fixes @@ -236,21 +283,21 @@ Also improve efficiency of marking workflow files as referenced * **sheets:** upload-empty-ok ([ab1940c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ab1940cb09e824fbba03264b5451fa8b17c5c804)) -### [21.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.2...v21.0.3) (2020-11-05) +## [21.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.2...v21.0.3) (2020-11-05) ### Bug Fixes * **mails:** prevent emails being resent to due archiving errors ([8cf39dc](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8cf39dcbe68cefcc50691ae8a7194315d18420d6)) -### [21.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.1...v21.0.2) (2020-11-04) +## [21.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.1...v21.0.2) (2020-11-04) ### Bug Fixes * build ([fa61b46](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fa61b46d308753354623df17241b5312f324321e)) -### [21.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.0...v21.0.1) (2020-11-04) +## [21.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.0...v21.0.1) (2020-11-04) ### Bug Fixes @@ -301,7 +348,7 @@ Also improve efficiency of marking workflow files as referenced * **allocations:** fix allocation-course-accept-substitutes ([b4df980](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b4df98069982752e36e69571f5557a6179b44cff)) -### [20.12.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.12.0...v20.12.1) (2020-10-14) +## [20.12.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.12.0...v20.12.1) (2020-10-14) ### Bug Fixes @@ -316,7 +363,7 @@ Also improve efficiency of marking workflow files as referenced * **ldap:** expose active directory errors ([51ed7e0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/51ed7e0a26a94d2178a4ca10ad7ea36b99076b54)) -### [20.11.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.11.0...v20.11.1) (2020-10-14) +## [20.11.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.11.0...v20.11.1) (2020-10-14) ### Bug Fixes @@ -351,7 +398,7 @@ Also improve efficiency of marking workflow files as referenced * **exams:** auth ExamResults by ExamExamOfficeSchools ([29a3e24](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/29a3e24bcf01cd9c893857eda00dcd249e6cbbe2)) * **exams:** exam staff & additional schools ([94436ee](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/94436ee0e1ce2cbf13a66f9ad81883d7286acb9b)) -### [20.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.8.0...v20.8.1) (2020-10-12) +## [20.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.8.0...v20.8.1) (2020-10-12) ### Bug Fixes @@ -384,7 +431,7 @@ Also improve efficiency of marking workflow files as referenced * **study-features:** also apply caching to table columns ([564c0b9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/564c0b975ae65881cb3a168855b36e4b1614a6cb)) -### [20.5.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.5.0...v20.5.1) (2020-09-29) +## [20.5.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.5.0...v20.5.1) (2020-09-29) ### Bug Fixes @@ -409,7 +456,7 @@ Also improve efficiency of marking workflow files as referenced * **allocations:** notify for new course upon registration ([9e0b43a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9e0b43a60d26a05f6e1b9d4dae2b2f75dd52fff1)) * tests ([ca81f3b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ca81f3b0f2913431cbaf399c33ed30a21979ce69)) -### [20.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.4.0...v20.4.1) (2020-09-23) +## [20.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.4.0...v20.4.1) (2020-09-23) ### Bug Fixes @@ -428,14 +475,14 @@ Also improve efficiency of marking workflow files as referenced * **jobs:** better flushing, correct metrics, better etas ([e4416e7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e4416e7f0e2ea2cf9db0e61cf2d20c27260ccaf8)) -### [20.3.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.3.1...v20.3.2) (2020-09-22) +## [20.3.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.3.1...v20.3.2) (2020-09-22) ### Bug Fixes * **files:** don't inject serializable ([2ca024b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2ca024b9351df800b57d3235c4a00776cd669952)) -### [20.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.3.0...v20.3.1) (2020-09-22) +## [20.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.3.0...v20.3.1) (2020-09-22) ### Bug Fixes @@ -463,7 +510,7 @@ Also improve efficiency of marking workflow files as referenced * **exam-form:** sort occurrences and parts ([6d47549](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6d475497c0caee49ad34c5c3c6e7b1bf91ca0ba2)) -### [20.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.1.0...v20.1.1) (2020-09-18) +## [20.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v20.1.0...v20.1.1) (2020-09-18) ### Bug Fixes @@ -505,7 +552,7 @@ Also improve efficiency of marking workflow files as referenced * zip handling & tests ([350ee79](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/350ee79af3c8fcc480970166a559596873beab2a)) -### [19.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.3.0...v19.3.1) (2020-09-10) +## [19.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.3.0...v19.3.1) (2020-09-10) ### Bug Fixes @@ -539,14 +586,14 @@ Also improve efficiency of marking workflow files as referenced * tests ([018d26f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/018d26f4a1a1cf411324aeac56ce4d4203670942)) * tests ([5541619](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5541619372f4a4e46ccc403004e869afdfaed7b0)) -### [19.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.2.1...v19.2.2) (2020-08-26) +## [19.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.2.1...v19.2.2) (2020-08-26) ### Bug Fixes * have exam deregistration always delete stored grades ([24f428b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/24f428b13bb181bec99417b4e69fc538e35acbcf)) -### [19.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.2.0...v19.2.1) (2020-08-26) +## [19.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.2.0...v19.2.1) (2020-08-26) ### Bug Fixes @@ -568,23 +615,23 @@ Also improve efficiency of marking workflow files as referenced -### [19.1.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.4...v19.1.5) (2020-08-19) +## [19.1.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.4...v19.1.5) (2020-08-19) -### [19.1.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.3...v19.1.4) (2020-08-18) +## [19.1.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.3...v19.1.4) (2020-08-18) -### [19.1.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.2...v19.1.3) (2020-08-17) +## [19.1.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.2...v19.1.3) (2020-08-17) -### [19.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.1...v19.1.2) (2020-08-17) +## [19.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.1...v19.1.2) (2020-08-17) -### [19.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.0...v19.1.1) (2020-08-17) +## [19.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v19.1.0...v19.1.1) (2020-08-17) @@ -728,7 +775,7 @@ Also improve efficiency of marking workflow files as referenced -### [18.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.2.1...v18.2.2) (2020-07-23) +## [18.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.2.1...v18.2.2) (2020-07-23) ### Bug Fixes @@ -738,7 +785,7 @@ Also improve efficiency of marking workflow files as referenced -### [18.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.2.0...v18.2.1) (2020-07-22) +## [18.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.2.0...v18.2.1) (2020-07-22) @@ -824,7 +871,7 @@ Also improve efficiency of marking workflow files as referenced -### [17.6.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.4...v17.6.5) (2020-06-26) +## [17.6.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.4...v17.6.5) (2020-06-26) ### Bug Fixes @@ -833,19 +880,19 @@ Also improve efficiency of marking workflow files as referenced -### [17.6.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.3...v17.6.4) (2020-06-24) +## [17.6.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.3...v17.6.4) (2020-06-24) -### [17.6.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.2...v17.6.3) (2020-06-24) +## [17.6.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.2...v17.6.3) (2020-06-24) -### [17.6.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.1...v17.6.2) (2020-06-24) +## [17.6.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.1...v17.6.2) (2020-06-24) -### [17.6.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.0...v17.6.1) (2020-06-24) +## [17.6.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.6.0...v17.6.1) (2020-06-24) ### Bug Fixes @@ -890,7 +937,7 @@ Also improve efficiency of marking workflow files as referenced -### [17.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.4.0...v17.4.1) (2020-06-15) +## [17.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.4.0...v17.4.1) (2020-06-15) @@ -925,7 +972,7 @@ Also improve efficiency of marking workflow files as referenced -### [17.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.2.0...v17.2.1) (2020-05-28) +## [17.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.2.0...v17.2.1) (2020-05-28) @@ -944,7 +991,7 @@ Also improve efficiency of marking workflow files as referenced -### [17.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.1.0...v17.1.1) (2020-05-26) +## [17.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v17.1.0...v17.1.1) (2020-05-26) ### Bug Fixes @@ -1013,11 +1060,11 @@ Also improve efficiency of marking workflow files as referenced -### [16.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.4.1...v16.4.2) (2020-05-13) +## [16.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.4.1...v16.4.2) (2020-05-13) -### [16.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.4.0...v16.4.1) (2020-05-13) +## [16.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.4.0...v16.4.1) (2020-05-13) ### Bug Fixes @@ -1045,7 +1092,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.3.0...v16.3.1) (2020-05-10) +## [16.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.3.0...v16.3.1) (2020-05-10) ### Bug Fixes @@ -1063,7 +1110,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.2.1...v16.2.2) (2020-05-08) +## [16.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.2.1...v16.2.2) (2020-05-08) ### Bug Fixes @@ -1072,7 +1119,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.2.0...v16.2.1) (2020-05-08) +## [16.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.2.0...v16.2.1) (2020-05-08) ### Bug Fixes @@ -1110,7 +1157,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.0.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.4...v16.0.5) (2020-05-06) +## [16.0.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.4...v16.0.5) (2020-05-06) ### Bug Fixes @@ -1119,7 +1166,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.0.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.3...v16.0.4) (2020-05-06) +## [16.0.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.3...v16.0.4) (2020-05-06) ### Bug Fixes @@ -1128,7 +1175,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.2...v16.0.3) (2020-05-05) +## [16.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.2...v16.0.3) (2020-05-05) ### Bug Fixes @@ -1138,7 +1185,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.1...v16.0.2) (2020-05-05) +## [16.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.1...v16.0.2) (2020-05-05) ### Bug Fixes @@ -1148,7 +1195,7 @@ Also improve efficiency of marking workflow files as referenced -### [16.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.0...v16.0.1) (2020-05-05) +## [16.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v16.0.0...v16.0.1) (2020-05-05) ### Bug Fixes @@ -1177,7 +1224,7 @@ Also improve efficiency of marking workflow files as referenced -### [15.6.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.6.0...v15.6.1) (2020-04-30) +## [15.6.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.6.0...v15.6.1) (2020-04-30) ### Bug Fixes @@ -1221,7 +1268,7 @@ Also improve efficiency of marking workflow files as referenced -### [15.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.4.0...v15.4.1) (2020-04-26) +## [15.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.4.0...v15.4.1) (2020-04-26) ### Bug Fixes @@ -1281,7 +1328,7 @@ Also improve efficiency of marking workflow files as referenced -### [15.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.1.1...v15.1.2) (2020-04-19) +## [15.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.1.1...v15.1.2) (2020-04-19) ### Bug Fixes @@ -1290,7 +1337,7 @@ Also improve efficiency of marking workflow files as referenced -### [15.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.1.0...v15.1.1) (2020-04-17) +## [15.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v15.1.0...v15.1.1) (2020-04-17) ### Bug Fixes @@ -1442,7 +1489,7 @@ Also improve efficiency of marking workflow files as referenced -### [14.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v14.1.0...v14.1.1) (2020-03-06) +## [14.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v14.1.0...v14.1.1) (2020-03-06) ### Bug Fixes @@ -1497,7 +1544,7 @@ relative when priorities are ordinal -### [13.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v13.0.0...v13.0.1) (2020-02-24) +## [13.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v13.0.0...v13.0.1) (2020-02-24) ### Bug Fixes @@ -1566,7 +1613,7 @@ relative when priorities are ordinal -### [11.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v11.1.0...v11.1.1) (2020-02-14) +## [11.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v11.1.0...v11.1.1) (2020-02-14) ### Bug Fixes @@ -1764,7 +1811,7 @@ relative when priorities are ordinal -### [10.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.4.0...v10.4.1) (2020-01-17) +## [10.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.4.0...v10.4.1) (2020-01-17) ### Bug Fixes @@ -1874,7 +1921,7 @@ relative when priorities are ordinal -### [10.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.0.0...v10.0.1) (2019-12-19) +## [10.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.0.0...v10.0.1) (2019-12-19) ### Bug Fixes @@ -1934,7 +1981,7 @@ relative when priorities are ordinal -### [9.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.2...v9.0.3) (2019-12-03) +## [9.0.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.2...v9.0.3) (2019-12-03) ### Bug Fixes @@ -1943,7 +1990,7 @@ relative when priorities are ordinal -### [9.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.1...v9.0.2) (2019-12-02) +## [9.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.1...v9.0.2) (2019-12-02) ### Bug Fixes @@ -1952,7 +1999,7 @@ relative when priorities are ordinal -### [9.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.0...v9.0.1) (2019-11-28) +## [9.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v9.0.0...v9.0.1) (2019-11-28) ### Bug Fixes @@ -1975,7 +2022,7 @@ relative when priorities are ordinal -### [8.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v8.0.0...v8.0.1) (2019-11-27) +## [8.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v8.0.0...v8.0.1) (2019-11-27) ### Bug Fixes @@ -2013,7 +2060,7 @@ relative when priorities are ordinal -### [7.25.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.25.0...v7.25.1) (2019-11-22) +## [7.25.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.25.0...v7.25.1) (2019-11-22) @@ -2043,7 +2090,7 @@ relative when priorities are ordinal -### [7.23.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.23.1...v7.23.2) (2019-11-19) +## [7.23.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.23.1...v7.23.2) (2019-11-19) ### Bug Fixes @@ -2053,7 +2100,7 @@ relative when priorities are ordinal -### [7.23.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.23.0...v7.23.1) (2019-11-19) +## [7.23.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.23.0...v7.23.1) (2019-11-19) ### Bug Fixes @@ -2080,7 +2127,7 @@ relative when priorities are ordinal -### [7.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.22.0...v7.22.1) (2019-11-14) +## [7.22.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.22.0...v7.22.1) (2019-11-14) @@ -2102,23 +2149,23 @@ relative when priorities are ordinal -### [7.21.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.4...v7.21.5) (2019-11-13) +## [7.21.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.4...v7.21.5) (2019-11-13) -### [7.21.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.3...v7.21.4) (2019-11-13) +## [7.21.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.3...v7.21.4) (2019-11-13) -### [7.21.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.2...v7.21.3) (2019-11-13) +## [7.21.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.2...v7.21.3) (2019-11-13) -### [7.21.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.1...v7.21.2) (2019-11-12) +## [7.21.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.1...v7.21.2) (2019-11-12) -### [7.21.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.0...v7.21.1) (2019-11-11) +## [7.21.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.21.0...v7.21.1) (2019-11-11) @@ -2187,7 +2234,7 @@ relative when priorities are ordinal -### [7.19.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.19.1...v7.19.2) (2019-10-28) +## [7.19.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.19.1...v7.19.2) (2019-10-28) ### Bug Fixes @@ -2196,7 +2243,7 @@ relative when priorities are ordinal -### [7.19.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.19.0...v7.19.1) (2019-10-25) +## [7.19.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.19.0...v7.19.1) (2019-10-25) ### Bug Fixes @@ -2219,7 +2266,7 @@ relative when priorities are ordinal -### [7.18.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.2...v7.18.3) (2019-10-23) +## [7.18.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.2...v7.18.3) (2019-10-23) ### Bug Fixes @@ -2228,11 +2275,11 @@ relative when priorities are ordinal -### [7.18.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.1...v7.18.2) (2019-10-20) +## [7.18.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.1...v7.18.2) (2019-10-20) -### [7.18.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.0...v7.18.1) (2019-10-20) +## [7.18.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.18.0...v7.18.1) (2019-10-20) ### Bug Fixes @@ -2256,7 +2303,7 @@ relative when priorities are ordinal -### [7.17.14](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.13...v7.17.14) (2019-10-17) +## [7.17.14](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.13...v7.17.14) (2019-10-17) ### Bug Fixes @@ -2265,7 +2312,7 @@ relative when priorities are ordinal -### [7.17.13](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.12...v7.17.13) (2019-10-17) +## [7.17.13](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.12...v7.17.13) (2019-10-17) ### Bug Fixes @@ -2274,11 +2321,11 @@ relative when priorities are ordinal -### [7.17.12](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.11...v7.17.12) (2019-10-17) +## [7.17.12](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.11...v7.17.12) (2019-10-17) -### [7.17.11](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.10...v7.17.11) (2019-10-16) +## [7.17.11](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.10...v7.17.11) (2019-10-16) ### Bug Fixes @@ -2287,7 +2334,7 @@ relative when priorities are ordinal -### [7.17.10](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.9...v7.17.10) (2019-10-16) +## [7.17.10](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.9...v7.17.10) (2019-10-16) ### Bug Fixes @@ -2296,7 +2343,7 @@ relative when priorities are ordinal -### [7.17.9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.8...v7.17.9) (2019-10-16) +## [7.17.9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.8...v7.17.9) (2019-10-16) ### Bug Fixes @@ -2305,7 +2352,7 @@ relative when priorities are ordinal -### [7.17.8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.7...v7.17.8) (2019-10-16) +## [7.17.8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.7...v7.17.8) (2019-10-16) ### Bug Fixes @@ -2314,7 +2361,7 @@ relative when priorities are ordinal -### [7.17.7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.6...v7.17.7) (2019-10-15) +## [7.17.7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.6...v7.17.7) (2019-10-15) ### Bug Fixes @@ -2323,7 +2370,7 @@ relative when priorities are ordinal -### [7.17.6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.5...v7.17.6) (2019-10-15) +## [7.17.6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.5...v7.17.6) (2019-10-15) ### Bug Fixes @@ -2332,7 +2379,7 @@ relative when priorities are ordinal -### [7.17.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.4...v7.17.5) (2019-10-15) +## [7.17.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.4...v7.17.5) (2019-10-15) ### Bug Fixes @@ -2341,7 +2388,7 @@ relative when priorities are ordinal -### [7.17.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.3...v7.17.4) (2019-10-15) +## [7.17.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.3...v7.17.4) (2019-10-15) ### Bug Fixes @@ -2350,11 +2397,11 @@ relative when priorities are ordinal -### [7.17.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.2...v7.17.3) (2019-10-14) +## [7.17.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.2...v7.17.3) (2019-10-14) -### [7.17.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.1...v7.17.2) (2019-10-14) +## [7.17.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.1...v7.17.2) (2019-10-14) ### Bug Fixes @@ -2363,7 +2410,7 @@ relative when priorities are ordinal -### [7.17.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.0...v7.17.1) (2019-10-14) +## [7.17.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.17.0...v7.17.1) (2019-10-14) ### Bug Fixes @@ -2401,7 +2448,7 @@ relative when priorities are ordinal -### [7.14.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.14.0...v7.14.1) (2019-10-13) +## [7.14.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.14.0...v7.14.1) (2019-10-13) ### Bug Fixes @@ -2490,7 +2537,7 @@ relative when priorities are ordinal -### [7.9.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.9.0...v7.9.1) (2019-10-07) +## [7.9.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.9.0...v7.9.1) (2019-10-07) ### Bug Fixes @@ -2508,7 +2555,7 @@ relative when priorities are ordinal -### [7.8.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.4...v7.8.5) (2019-10-05) +## [7.8.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.4...v7.8.5) (2019-10-05) ### Bug Fixes @@ -2517,7 +2564,7 @@ relative when priorities are ordinal -### [7.8.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.3...v7.8.4) (2019-10-05) +## [7.8.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.3...v7.8.4) (2019-10-05) ### Bug Fixes @@ -2526,7 +2573,7 @@ relative when priorities are ordinal -### [7.8.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.2...v7.8.3) (2019-10-05) +## [7.8.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.2...v7.8.3) (2019-10-05) ### Bug Fixes @@ -2535,7 +2582,7 @@ relative when priorities are ordinal -### [7.8.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.1...v7.8.2) (2019-10-04) +## [7.8.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.1...v7.8.2) (2019-10-04) ### Bug Fixes @@ -2544,7 +2591,7 @@ relative when priorities are ordinal -### [7.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.0...v7.8.1) (2019-10-04) +## [7.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.8.0...v7.8.1) (2019-10-04) ### Bug Fixes @@ -2597,7 +2644,7 @@ relative when priorities are ordinal -### [7.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.4.1...v7.4.2) (2019-10-01) +## [7.4.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.4.1...v7.4.2) (2019-10-01) ### Bug Fixes @@ -2606,7 +2653,7 @@ relative when priorities are ordinal -### [7.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.4.0...v7.4.1) (2019-10-01) +## [7.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.4.0...v7.4.1) (2019-10-01) ### Bug Fixes @@ -2624,7 +2671,7 @@ relative when priorities are ordinal -### [7.3.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.3.1...v7.3.2) (2019-10-01) +## [7.3.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.3.1...v7.3.2) (2019-10-01) ### Bug Fixes @@ -2634,7 +2681,7 @@ relative when priorities are ordinal -### [7.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.3.0...v7.3.1) (2019-09-30) +## [7.3.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.3.0...v7.3.1) (2019-09-30) ### Bug Fixes @@ -2657,7 +2704,7 @@ relative when priorities are ordinal -### [7.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.2.1...v7.2.2) (2019-09-30) +## [7.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.2.1...v7.2.2) (2019-09-30) ### Bug Fixes @@ -2666,7 +2713,7 @@ relative when priorities are ordinal -### [7.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.2.0...v7.2.1) (2019-09-28) +## [7.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.2.0...v7.2.1) (2019-09-28) ### Bug Fixes @@ -2691,7 +2738,7 @@ relative when priorities are ordinal -### [7.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.1.1...v7.1.2) (2019-09-26) +## [7.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.1.1...v7.1.2) (2019-09-26) ### Bug Fixes @@ -2700,7 +2747,7 @@ relative when priorities are ordinal -### [7.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.1.0...v7.1.1) (2019-09-26) +## [7.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v7.1.0...v7.1.1) (2019-09-26) ### Bug Fixes @@ -2767,7 +2814,7 @@ relative when priorities are ordinal -### [6.11.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v6.11.0...v6.11.1) (2019-09-17) +## [6.11.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v6.11.0...v6.11.1) (2019-09-17) ### Bug Fixes @@ -2916,7 +2963,7 @@ relative when priorities are ordinal -### [6.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v6.2.0...v6.2.1) (2019-09-04) +## [6.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v6.2.0...v6.2.1) (2019-09-04) ### Bug Fixes @@ -3050,7 +3097,7 @@ relative when priorities are ordinal -### [5.2.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.2...v5.2.3) (2019-08-22) +## [5.2.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.2...v5.2.3) (2019-08-22) ### Bug Fixes @@ -3059,11 +3106,11 @@ relative when priorities are ordinal -### [5.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.1...v5.2.2) (2019-08-22) +## [5.2.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.1...v5.2.2) (2019-08-22) -### [5.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.0...v5.2.1) (2019-08-21) +## [5.2.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.2.0...v5.2.1) (2019-08-21) ### Bug Fixes @@ -3103,7 +3150,7 @@ relative when priorities are ordinal -### [5.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.0.1...v5.0.2) (2019-08-13) +## [5.0.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.0.1...v5.0.2) (2019-08-13) ### Bug Fixes @@ -3112,7 +3159,7 @@ relative when priorities are ordinal -### [5.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.0.0...v5.0.1) (2019-08-12) +## [5.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v5.0.0...v5.0.1) (2019-08-12) @@ -3173,7 +3220,7 @@ them together now) -### [4.13.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.13.0...v4.13.1) (2019-08-07) +## [4.13.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.13.0...v4.13.1) (2019-08-07) ### Bug Fixes @@ -3193,7 +3240,7 @@ them together now) -### [4.12.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.12.0...v4.12.1) (2019-08-06) +## [4.12.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.12.0...v4.12.1) (2019-08-06) ### Bug Fixes @@ -3357,7 +3404,7 @@ them together now) -### [4.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.1.1...v4.1.2) (2019-07-17) +## [4.1.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.1.1...v4.1.2) (2019-07-17) ### Bug Fixes @@ -3366,7 +3413,7 @@ them together now) -### [4.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.1.0...v4.1.1) (2019-07-17) +## [4.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.1.0...v4.1.1) (2019-07-17) ### Bug Fixes @@ -3387,7 +3434,7 @@ them together now) -### [4.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.0.0...v4.0.1) (2019-07-16) +## [4.0.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v4.0.0...v4.0.1) (2019-07-16) ### Bug Fixes @@ -3437,7 +3484,7 @@ them together now) -### [2.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v2.1.0...v2.1.1) (2019-07-10) +## [2.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v2.1.0...v2.1.1) (2019-07-10) ### Bug Fixes @@ -3484,7 +3531,7 @@ them together now) -### [1.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v1.4.0...v1.4.1) (2019-07-04) +## [1.4.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v1.4.0...v1.4.1) (2019-07-04) diff --git a/config/settings.yml b/config/settings.yml index c1fc4edbd..fa8a65f81 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -63,6 +63,9 @@ health-check-matching-cluster-config-timeout: "_env:HEALTHCHECK_MATCHING_CLUSTER synchronise-ldap-users-within: "_env:SYNCHRONISE_LDAP_WITHIN:1209600" synchronise-ldap-users-interval: "_env:SYNCHRONISE_LDAP_INTERVAL:3600" +study-features-recache-relevance-within: 172800 +study-features-recache-relevance-interval: 293 + log-settings: detailed: "_env:DETAILED_LOGGING:false" all: "_env:LOG_ALL:false" diff --git a/hlint.sh b/hlint.sh index c6772fda7..5f30751cc 100755 --- a/hlint.sh +++ b/hlint.sh @@ -4,4 +4,15 @@ set -e [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || : +move-back() { + mv -v .stack-work .stack-work-test + [[ -d .stack-work-build ]] && mv -v .stack-work-build .stack-work +} + +if [[ -d .stack-work-test ]]; then + [[ -d .stack-work ]] && mv -v .stack-work .stack-work-build + mv -v .stack-work-test .stack-work + trap move-back EXIT +fi + exec -- stack build --test --fast --flag uniworx:dev --flag uniworx:library-only uniworx:test:hlint diff --git a/models/study-features.model b/models/study-features.model index 71b72ad0f..1c0b2e111 100644 --- a/models/study-features.model +++ b/models/study-features.model @@ -8,7 +8,7 @@ StudyFeatures -- multiple entries possible for students pursuing several degree firstObserved UTCTime Maybe lastObserved UTCTime default=now() -- last update from LDAP valid Bool default=true - relevanceCached Bool default=false + relevanceCached UUID Maybe UniqueStudyFeatures user degree field type semester deriving Eq Show -- UniqueUserSubject ubuser degree field -- There exists a counterexample diff --git a/models/workflows.model b/models/workflows.model index 590b79744..7561e9c65 100644 --- a/models/workflows.model +++ b/models/workflows.model @@ -1,5 +1,10 @@ -WorkflowDefinition +SharedWorkflowGraph + hash WorkflowGraphReference graph (WorkflowGraph FileReference SqlBackendKey) -- UserId + Primary hash + +WorkflowDefinition + graph SharedWorkflowGraphId scope WorkflowScope' name WorkflowDefinitionName instanceCategory WorkflowInstanceCategory Maybe @@ -21,7 +26,7 @@ WorkflowDefinitionInstanceDescription WorkflowInstance definition WorkflowDefinitionId Maybe - graph (WorkflowGraph FileReference SqlBackendKey) -- UserId + graph SharedWorkflowGraphId scope (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey) -- TermId, SchoolId, CourseId name WorkflowInstanceName category WorkflowInstanceCategory Maybe @@ -37,5 +42,5 @@ WorkflowInstanceDescription WorkflowWorkflow instance WorkflowInstanceId Maybe scope (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey) -- TermId, SchoolId, CourseId - graph (WorkflowGraph FileReference SqlBackendKey) -- UserId + graph SharedWorkflowGraphId state (WorkflowState FileReference SqlBackendKey) -- UserId diff --git a/package-lock.json b/package-lock.json index 0d1379a39..e16333912 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "23.6.0", + "version": "24.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5115,6 +5115,16 @@ "supports-color": "^5.3.0" } }, + "changelog-parser": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/changelog-parser/-/changelog-parser-2.8.0.tgz", + "integrity": "sha512-ZtSwN0hY7t+WpvaXqqXz98RHCNhWX9HsvCRAv1aBLlqJ7BpKtqdM6Nu6JOiUhRAWR7Gov0aN0fUnmflTz0WgZg==", + "dev": true, + "requires": { + "line-reader": "^0.2.4", + "remove-markdown": "^0.2.2" + } + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -5648,22 +5658,45 @@ "dev": true }, "conventional-changelog": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.23.tgz", - "integrity": "sha512-sScUu2NHusjRC1dPc5p8/b3kT78OYr95/Bx7Vl8CPB8tF2mG1xei5iylDTRjONV5hTlzt+Cn/tBWrKdd299b7A==", + "version": "3.1.24", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz", + "integrity": "sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg==", "dev": true, "requires": { - "conventional-changelog-angular": "^5.0.11", - "conventional-changelog-atom": "^2.0.7", - "conventional-changelog-codemirror": "^2.0.7", - "conventional-changelog-conventionalcommits": "^4.4.0", - "conventional-changelog-core": "^4.2.0", - "conventional-changelog-ember": "^2.0.8", - "conventional-changelog-eslint": "^3.0.8", - "conventional-changelog-express": "^2.0.5", - "conventional-changelog-jquery": "^3.0.10", - "conventional-changelog-jshint": "^2.0.8", + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", "conventional-changelog-preset-loader": "^2.3.4" + }, + "dependencies": { + "conventional-changelog-angular": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-conventionalcommits": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz", + "integrity": "sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + } } }, "conventional-changelog-angular": { @@ -5677,18 +5710,18 @@ } }, "conventional-changelog-atom": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.7.tgz", - "integrity": "sha512-7dOREZwzB+tCEMjRTDfen0OHwd7vPUdmU0llTy1eloZgtOP4iSLVzYIQqfmdRZEty+3w5Jz+AbhfTJKoKw1JeQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", "dev": true, "requires": { "q": "^1.5.1" } }, "conventional-changelog-codemirror": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.7.tgz", - "integrity": "sha512-Oralk1kiagn3Gb5cR5BffenWjVu59t/viE6UMD/mQa1hISMPkMYhJIqX+CMeA1zXgVBO+YHQhhokEj99GP5xcg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", "dev": true, "requires": { "q": "^1.5.1" @@ -5712,175 +5745,118 @@ } }, "conventional-changelog-core": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.0.tgz", - "integrity": "sha512-8+xMvN6JvdDtPbGBqA7oRNyZD4od1h/SIzrWqHcKZjitbVXrFpozEeyn4iI4af1UwdrabQpiZMaV07fPUTGd4w==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.2.tgz", + "integrity": "sha512-7pDpRUiobQDNkwHyJG7k9f6maPo9tfPzkSWbRq97GGiZqisElhnvUZSvyQH20ogfOjntB5aadvv6NNcKL1sReg==", "dev": true, "requires": { "add-stream": "^1.0.0", - "conventional-changelog-writer": "^4.0.17", - "conventional-commits-parser": "^3.1.0", + "conventional-changelog-writer": "^4.0.18", + "conventional-commits-parser": "^3.2.0", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", - "git-raw-commits": "2.0.0", + "git-raw-commits": "^2.0.8", "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.0", + "git-semver-tags": "^4.1.1", "lodash": "^4.17.15", - "normalize-package-data": "^2.3.5", + "normalize-package-data": "^3.0.0", "q": "^1.5.1", "read-pkg": "^3.0.0", "read-pkg-up": "^3.0.0", "shelljs": "^0.8.3", - "through2": "^3.0.0" + "through2": "^4.0.0" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "conventional-commits-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", + "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^2.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" } }, - "conventional-changelog-writer": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", - "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", + "git-raw-commits": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz", + "integrity": "sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ==", "dev": true, "requires": { - "compare-func": "^2.0.0", - "conventional-commits-filter": "^2.0.6", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", + "dargs": "^7.0.0", "lodash": "^4.17.15", - "meow": "^7.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^3.0.0" + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" }, "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" + "readable-stream": "^3.0.0" } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + } + } + }, + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "read-pkg": { @@ -5895,6 +5871,18 @@ "type-fest": "^0.6.0" }, "dependencies": { + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -5922,123 +5910,24 @@ } } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, - "dargs": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", - "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", "dev": true, "requires": { - "number-is-nan": "^1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "git-raw-commits": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz", - "integrity": "sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg==", - "dev": true, - "requires": { - "dargs": "^4.0.1", - "lodash.template": "^4.0.2", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true - }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" } }, "p-limit": { @@ -6086,12 +5975,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -6101,6 +5984,32 @@ "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "read-pkg-up": { @@ -6111,72 +6020,108 @@ "requires": { "find-up": "^2.0.0", "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + } } }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true } } }, "conventional-changelog-ember": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.8.tgz", - "integrity": "sha512-JEMEcUAMg4Q9yxD341OgWlESQ4gLqMWMXIWWUqoQU8yvTJlKnrvcui3wk9JvnZQyONwM2g1MKRZuAjKxr8hAXA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", "dev": true, "requires": { "q": "^1.5.1" } }, "conventional-changelog-eslint": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.8.tgz", - "integrity": "sha512-5rTRltgWG7TpU1PqgKHMA/2ivjhrB+E+S7OCTvj0zM/QGg4vmnVH67Vq/EzvSNYtejhWC+OwzvDrLk3tqPry8A==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", "dev": true, "requires": { "q": "^1.5.1" } }, "conventional-changelog-express": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.5.tgz", - "integrity": "sha512-pW2hsjKG+xNx/Qjof8wYlAX/P61hT5gQ/2rZ2NsTpG+PgV7Rc8RCfITvC/zN9K8fj0QmV6dWmUefCteD9baEAw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", "dev": true, "requires": { "q": "^1.5.1" } }, "conventional-changelog-jquery": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.10.tgz", - "integrity": "sha512-QCW6wF8QgPkq2ruPaxc83jZxoWQxLkt/pNxIDn/oYjMiVgrtqNdd7lWe3vsl0hw5ENHNf/ejXuzDHk6suKsRpg==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", "dev": true, "requires": { "q": "^1.5.1" } }, "conventional-changelog-jshint": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.8.tgz", - "integrity": "sha512-hB/iI0IiZwnZ+seYI+qEQ4b+EMQSEC8jGIvhO2Vpz1E5p8FgLz75OX8oB1xJWl+s4xBMB6f8zJr0tC/BL7YOjw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", "dev": true, "requires": { "compare-func": "^2.0.0", @@ -6189,10 +6134,128 @@ "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", "dev": true }, + "conventional-changelog-writer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz", + "integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, "conventional-commits-filter": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz", - "integrity": "sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", "dev": true, "requires": { "lodash.ismatch": "^4.4.0", @@ -6215,18 +6278,18 @@ } }, "conventional-recommended-bump": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.0.10.tgz", - "integrity": "sha512-2ibrqAFMN3ZA369JgVoSbajdD/BHN6zjY7DZFKTHzyzuQejDUCjQ85S5KHxCRxNwsbDJhTPD5hOKcis/jQhRgg==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.0.11.tgz", + "integrity": "sha512-FciYBMwzwwBZ1K4NS8c57rsOfSc51e1V6UVSNIosrjH+A6xXkyiA4ELwoWyRKdMhJ+m3O6ru9ZJ7F2QFjjYJdQ==", "dev": true, "requires": { "concat-stream": "^2.0.0", "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-filter": "^2.0.6", - "conventional-commits-parser": "^3.1.0", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", "git-raw-commits": "2.0.0", - "git-semver-tags": "^4.1.0", - "meow": "^7.0.0", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", "q": "^1.5.1" }, "dependencies": { @@ -6236,15 +6299,19 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "conventional-commits-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", + "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^2.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" } }, "dargs": { @@ -6278,6 +6345,23 @@ "through2": "^2.0.0" }, "dependencies": { + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, "meow": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", @@ -6294,9 +6378,97 @@ "redent": "^2.0.0", "trim-newlines": "^2.0.0" } + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true } } }, + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "indent-string": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", @@ -6313,6 +6485,15 @@ "path-exists": "^3.0.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", @@ -6320,9 +6501,9 @@ "dev": true }, "meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "requires": { "@types/minimist": "^1.2.0", @@ -6330,183 +6511,24 @@ "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", + "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", - "dev": true - } + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" } }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", "dev": true, "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" } }, "p-limit": { @@ -6569,33 +6591,54 @@ "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "lru-cache": "^6.0.0" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, "strip-indent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", @@ -6603,19 +6646,30 @@ "dev": true }, "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "3" } }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true } } @@ -9257,13 +9311,93 @@ } }, "git-semver-tags": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.0.tgz", - "integrity": "sha512-TcxAGeo03HdErzKzi4fDD+xEL7gi8r2Y5YSxH6N2XYdVSV5UkBwfrt7Gqo1b+uSHCjy/sa9Y6BBBxxFLxfbhTg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", "dev": true, "requires": { - "meow": "^7.0.0", + "meow": "^8.0.0", "semver": "^6.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } } }, "gitconfiglocal": { @@ -9400,12 +9534,6 @@ "wordwrap": "^1.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11247,6 +11375,12 @@ "type-check": "~0.3.2" } }, + "line-reader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/line-reader/-/line-reader-0.2.4.tgz", + "integrity": "sha1-xDkrWH3qOFgMlnhXDm6OSfzlJiI=", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -18054,6 +18188,12 @@ "trash": "6.1.1" } }, + "remove-markdown": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.2.2.tgz", + "integrity": "sha1-ZrDO66n7d8qWNrsbAwfOIaMqEqY=", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -19280,21 +19420,21 @@ "dev": true }, "standard-version": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.0.0.tgz", - "integrity": "sha512-eRR04IscMP3xW9MJTykwz13HFNYs8jS33AGuDiBKgfo5YrO0qX0Nxb4rjupVwT5HDYL/aR+MBEVLjlmVFmFEDQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.1.0.tgz", + "integrity": "sha512-EJcbKUGKBuHjiDSUL5XjPhT1KGVM+UCvv/ti70fHnJwJyJqTSJWl0mWj/Wj0WwsoskyvKWURESzBsZmCCMUZzg==", "dev": true, "requires": { "chalk": "^2.4.2", - "conventional-changelog": "3.1.23", + "conventional-changelog": "3.1.24", "conventional-changelog-config-spec": "2.1.0", - "conventional-changelog-conventionalcommits": "4.4.0", - "conventional-recommended-bump": "6.0.10", + "conventional-changelog-conventionalcommits": "4.5.0", + "conventional-recommended-bump": "6.0.11", "detect-indent": "^6.0.0", "detect-newline": "^3.1.0", "dotgitignore": "^2.1.0", "figures": "^3.1.0", - "find-up": "^4.1.0", + "find-up": "^5.0.0", "fs-access": "^1.0.1", "git-semver-tags": "^4.0.0", "semver": "^7.1.1", @@ -19302,6 +19442,17 @@ "yargs": "^15.3.1" }, "dependencies": { + "conventional-changelog-conventionalcommits": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.5.0.tgz", + "integrity": "sha512-buge9xDvjjOxJlyxUnar/+6i/aVEVGA7EEh4OafBCXPlLUQPGbRUBhBUveWRxzvR8TEjhKEP4BdepnpG2FSZXw==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + }, "detect-indent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", @@ -19317,10 +19468,65 @@ "escape-string-regexp": "^1.0.5" } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } @@ -20347,23 +20553,11 @@ "dev": true }, "uglify-js": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.0.tgz", - "integrity": "sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g==", + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.7.tgz", + "integrity": "sha512-SIZhkoh+U/wjW+BHGhVwE9nt8tWJspncloBcFapkpGRwNPqcH8pzX36BXe3TPBjzHWPMUZotpCigak/udWNr1Q==", "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true - } - } + "optional": true }, "underscore": { "version": "1.11.0", @@ -21411,6 +21605,12 @@ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", "dev": true }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, "yup": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/yup/-/yup-0.27.0.tgz", diff --git a/package.json b/package.json index fbda3c014..403fe9fd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "23.6.0", + "version": "24.1.2", "description": "", "keywords": [], "author": "", @@ -26,7 +26,8 @@ "i18n:test": "./missing-translations.sh", "prerelease": "./is-clean.sh && npm run test", "release": "standard-version -a", - "postrelease": "git push --follow-tags origin master" + "postrelease": "git push --follow-tags origin master", + "parse-changelog": "changelog-parser ./CHANGELOG.md > changelog.json" }, "husky": { "hooks": { @@ -42,7 +43,8 @@ }, "standard-version": { "scripts": { - "postbump": "./sync-versions.hs && git add -- package.yaml" + "postbump": "./sync-versions.hs && git add -- package.yaml", + "postchangelog": "sed 's/^### \\[/## [/g' -i CHANGELOG.md" }, "commitUrlFormat": "https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/{{hash}}", "compareUrlFormat": "https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/{{previousTag}}...{{currentTag}}", @@ -70,6 +72,7 @@ "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-preset-es2015": "^6.24.1", "cbt_tunnels": "^1.2.2", + "changelog-parser": "^2.8.0", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.0.3", "css-loader": "^2.1.1", @@ -104,13 +107,13 @@ "sass": "^1.26.10", "sass-loader": "^7.3.1", "semver": "^6.3.0", - "standard-version": "^9.0.0", + "standard-version": "^9.1.0", "style-loader": "^0.23.1", "terser-webpack-plugin": "^2.3.8", "tmp": "^0.1.0", "typeface-roboto": "0.0.75", - "typeface-source-sans-pro": "0.0.75", "typeface-source-code-pro": "^1.1.3", + "typeface-source-sans-pro": "0.0.75", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-manifest-plugin": "^2.2.0", diff --git a/package.yaml b/package.yaml index ee9cd9830..ca2df3648 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 23.6.0 +version: 24.1.2 dependencies: - base diff --git a/src/Application.hs b/src/Application.hs index c7619cbed..6dc58f427 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -89,6 +89,7 @@ import Control.Concurrent.STM.Delay import Control.Monad.Trans.Cont (runContT, callCC) import qualified Data.Set as Set +import qualified Data.Map as Map import Handler.Utils.Routes (classifyHandler) @@ -148,6 +149,7 @@ mkYesodDispatch "UniWorX" resourcesUniWorX makeFoundation :: (MonadResource m, MonadUnliftIO m, MonadCatch m) => AppSettings -> m UniWorX makeFoundation appSettings''@AppSettings{..} = do registerGHCMetrics + registerHealthCheckInterval appHealthCheckInterval -- Some basic initializations: HTTP connection manager, logger, and static -- subsite. @@ -553,41 +555,46 @@ appMain = runResourceT $ do Just wInterval | maybe True (== myProcessID) watchdogProcess -> let notifyWatchdog :: forall a m'. ( MonadLogger m', MonadIO m') => m' a - notifyWatchdog = go Nothing - where - go :: Maybe (Set (UTCTime, HealthReport)) -> m' a - go pResults = do - let delay = floor $ wInterval % 4 - d <- liftIO $ newDelay delay + notifyWatchdog = forever' Nothing $ \pResults -> do + let delay = floor $ wInterval % 4 + d <- liftIO $ newDelay delay + + $logDebugS "Notify" $ "Waiting up to " <> tshow delay <> "µs..." + mResults <- atomically $ asum + [ pResults <$ waitDelay d + , do + results <- readTVar $ foundation ^. _appHealthReport + guardOn (pResults /= Just results) $ Just results + ] - $logDebugS "Notify" $ "Waiting up to " <> tshow delay <> "µs..." - mResults <- atomically $ asum - [ pResults <$ waitDelay d - , do - results <- readTVar $ foundation ^. _appHealthReport - guardOn (pResults /= Just results) $ Just results - ] + $logDebugS "Notify" "Checking for status/watchdog..." + mResults <$ do + void . runMaybeT $ do + results <- hoistMaybe mResults - $logDebugS "Notify" "Checking for status/watchdog..." - (*> go mResults) . void . runMaybeT $ do - results <- hoistMaybe mResults + let latestResults = Map.fromListWith (\_ x -> x) $ Set.toAscList results + Min status <- hoistMaybe $ ofoldMap1 (Min . healthReportStatus) <$> fromNullable latestResults + $logInfoS "NotifyStatus" $ toPathPiece status + liftIO . void . Systemd.notifyStatus . unpack $ toPathPiece status - Min status <- hoistMaybe $ ofoldMap1 (Min . healthReportStatus . view _2) <$> fromNullable results - $logInfoS "NotifyStatus" $ toPathPiece status - liftIO . void . Systemd.notifyStatus . unpack $ toPathPiece status + now <- liftIO getCurrentTime + let missing = flip ifoldMap (foundation ^. _appHealthCheckInterval) $ \hc mInterval -> fromMaybe mempty $ do + interval <- mInterval + let lastSuccess = maybeMonoid mResults + & Set.filter (\(_, rep) -> classifyHealthReport rep == hc) + & Set.filter (\(_, rep) -> healthReportStatus rep >= HealthSuccess) + & Set.mapMonotonic (view _1) + & Set.lookupMax - now <- liftIO getCurrentTime - iforM_ (foundation ^. _appHealthCheckInterval) . curry $ \case - (_, Nothing) -> return () - (hc, Just interval) -> do - lastSuccess <- hoistMaybe $ results - & Set.filter (\(_, rep) -> classifyHealthReport rep == hc) - & Set.filter (\(_, rep) -> healthReportStatus rep >= HealthSuccess) - & Set.mapMonotonic (view _1) - & Set.lookupMax - guard $ lastSuccess > addUTCTime (negate interval) now - $logInfoS "NotifyWatchdog" "Notify" - liftIO $ void Systemd.notifyWatchdog + successIsCurrent = lastSuccess > Just (negate interval `addUTCTime` now) + + return . guardMonoid (not successIsCurrent) $ Set.singleton hc + + if | Set.null missing -> do + $logInfoS "NotifyWatchdog" "Notify" + liftIO $ void Systemd.notifyWatchdog + | otherwise -> + $logWarnS "NotifyWatchdog" $ "No notify; missing \n\t " <> tshow (toList missing) <> "\n\tin " <> tshow (toList <$> mResults) in do $logDebugS "Notify" "Spawning notify thread..." void $ allocateLinkedAsync notifyWatchdog diff --git a/src/Foundation/Authorization.hs b/src/Foundation/Authorization.hs index 6a1e3d3d1..a6420a109 100644 --- a/src/Foundation/Authorization.hs +++ b/src/Foundation/Authorization.hs @@ -1254,7 +1254,7 @@ tagAccessPredicate AuthRegisterGroup = APDB $ \_ _ mAuthId route _ -> case route return Authorized r -> $unsupportedAuthPredicate AuthRegisterGroup r tagAccessPredicate AuthEmpty = APDB $ \_ _ mAuthId route _ - -> let workflowInstanceWorkflowsEmpty rScope win = selectLanguageI18n <=< $memcacheAuthHere' (Right diffDay) (mAuthId, route) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do + -> let workflowInstanceWorkflowsEmpty rScope win = selectLanguageI18n <=< $cachedHereBinary (mAuthId, route) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do scope <- fromRouteWorkflowScope rScope let dbScope = scope ^. _DBWorkflowScope getWorkflowWorkflows = E.selectSource . E.from $ \(workflowWorkflow `E.InnerJoin` workflowInstance) -> do @@ -1416,9 +1416,8 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> wInitiate win rScope = selectLanguageI18n <=< $memcacheAuthHere' (Right diffDay) (evalCtx, route, mAuthId) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowInitiate) $ do -- @isWrite@ not included since it should make no difference regarding initiation (in the end that will always be a write) scope <- MaybeT . $cachedHereBinary rScope . runMaybeT $ fromRouteWorkflowScope rScope Entity _ WorkflowInstance{..} <- $cachedHereBinary (win, scope) . MaybeT . getBy . UniqueWorkflowInstance win $ scope ^. _DBWorkflowScope + wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph let - wiGraph :: IdWorkflowGraph - wiGraph = _DBWorkflowGraph # workflowInstanceGraph edges = do WGN{..} <- wiGraph ^.. _wgNodes . folded WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded @@ -1434,11 +1433,9 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> (wwId, edges) <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowWorkflowEdgeActors cID) $ do wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId + wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph let - wwGraph :: IdWorkflowGraph - wwGraph = _DBWorkflowGraph # workflowWorkflowGraph - wwNode = wpTo $ last workflowWorkflowState return . (wwId, ) . (Set.fromList :: _ -> Set (WorkflowRole UserId)) . foldMap toNullable $ do @@ -1455,11 +1452,9 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> (wwId, roles) <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowWorkflowViewers cID) $ do wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId + wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph let - wwGraph :: IdWorkflowGraph - wwGraph = _DBWorkflowGraph # workflowWorkflowGraph - nodeViewers = do WorkflowAction{..} <- otoList workflowWorkflowState (node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph @@ -1483,9 +1478,7 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt wwCID WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId stIx <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decryptWorkflowStateIndex wwId stCID - let - wwGraph :: IdWorkflowGraph - wwGraph = _DBWorkflowGraph # workflowWorkflowGraph + wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph act <- workflowStateIndex stIx $ _DBWorkflowState # workflowWorkflowState let cState = wpTo act @@ -1764,8 +1757,8 @@ mayViewWorkflowAction' eval mAuthId wwId WorkflowAction{..} = hoist (withReaderT WorkflowWorkflow{..} <- MaybeT . lift $ get wwId rScope <- hoist lift . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope cID <- hoist lift . catchMaybeT (Proxy @CryptoIDError) . lift $ encrypt wwId - let WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph - canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) + WorkflowGraph{..} <- lift . lift $ getSharedIdWorkflowGraph workflowWorkflowGraph + let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) evalWorkflowRole'' role = lift $ is _Authorized <$> evalWorkflowRoleFor' eval mAuthId (Just wwId) role canonRoute False WorkflowNodeView{..} <- hoistMaybe $ Map.lookup wpTo wgNodes >>= wgnViewers guardM $ orM diff --git a/src/Foundation/Yesod/Auth.hs b/src/Foundation/Yesod/Auth.hs index f1c1c3a40..f114b23e4 100644 --- a/src/Foundation/Yesod/Auth.hs +++ b/src/Foundation/Yesod/Auth.hs @@ -352,7 +352,7 @@ upsertCampusUser upsertMode ldapData = do , studyFeaturesFirstObserved = Just now , studyFeaturesLastObserved = now , studyFeaturesValid = True - , studyFeaturesRelevanceCached = False + , studyFeaturesRelevanceCached = Nothing } (sf :) <$> assimilateSubTerms subterms unusedFeats Nothing diff --git a/src/Foundation/Yesod/Middleware.hs b/src/Foundation/Yesod/Middleware.hs index 473f50de3..650236778 100644 --- a/src/Foundation/Yesod/Middleware.hs +++ b/src/Foundation/Yesod/Middleware.hs @@ -143,6 +143,7 @@ yesodMiddleware = cacheControlMiddleware . storeBearerMiddleware . csrfMiddlewar MetricsR -> mzero HealthR -> mzero InstanceR -> mzero + AdminCrontabR -> mzero _other -> return () reqHost <- MaybeT $ W.requestHeaderHost <$> waiRequest @@ -313,7 +314,8 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . (_, WorkflowWorkflowR cID (WWFilesR wpl _)) <- hoistMaybe $ route ^? _WorkflowScopeRoute wwId <- decrypt cID WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId . lift $ get wwId - [wpl'] <- return . filter (== wpl) . sortOn (CI.original . unWorkflowPayloadLabel) . foldMap Map.keys $ wgnPayloadView <$> wgNodes workflowWorkflowGraph + wwGraph <- lift . lift $ getSharedDBWorkflowGraph workflowWorkflowGraph + [wpl'] <- return . filter (== wpl) . sortOn (CI.original . unWorkflowPayloadLabel) . foldMap Map.keys $ wgnPayloadView <$> wgNodes wwGraph (caseChanged `on` unWorkflowPayloadLabel) wpl wpl' return $ route & typesUsing @RouteChildren @WorkflowPayloadLabel . filtered (== wpl) .~ wpl' diff --git a/src/Handler/Admin/Crontab.hs b/src/Handler/Admin/Crontab.hs index daff2070f..18225b5cb 100644 --- a/src/Handler/Admin/Crontab.hs +++ b/src/Handler/Admin/Crontab.hs @@ -17,6 +17,8 @@ import qualified Data.Text.Lazy.Builder as Text.Builder import qualified Data.HashSet as HashSet import qualified Data.HashMap.Strict as HashMap +import qualified Data.UUID as UUID + deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece' 1 @@ -32,9 +34,12 @@ getAdminCrontabR = do let mCrontab = mCrontab' <&> _2 %~ filter (hasn't $ _3 . _MatchNone) + instanceId <- getsYesod appInstanceID + selectRep $ do provideRep $ do crontabBearer <- runMaybeT . hoist runDB $ do + guardM $ hasGlobalGetParam GetGenerateToken uid <- MaybeT maybeAuthId guardM . lift . existsBy $ UniqueUserGroupMember UserGroupCrontab uid @@ -49,6 +54,10 @@ getAdminCrontabR = do
                 #{toPathPiece t}
+          
+
+
_{MsgInstanceId} +
#{UUID.toText instanceId}
$maybe (genTime, crontab) <- mCrontab

diff --git a/src/Handler/ExamOffice/Exams.hs b/src/Handler/ExamOffice/Exams.hs index f4f957c4c..7b98ed007 100644 --- a/src/Handler/ExamOffice/Exams.hs +++ b/src/Handler/ExamOffice/Exams.hs @@ -15,6 +15,8 @@ import qualified Database.Esqueleto.Utils as E import qualified Colonnade +import qualified Data.Conduit.Combinators as C + type ExamsTableExpr = ( E.SqlExpr (Maybe (Entity Exam)) `E.InnerJoin` E.SqlExpr (Maybe (Entity Course)) @@ -38,54 +40,6 @@ querySchool = to $ $(E.sqlIJproj 3 3) . $(E.sqlFOJproj 2 1) queryExternalExam :: Getter ExamsTableExpr (E.SqlExpr (Maybe (Entity ExternalExam))) queryExternalExam = to $(E.sqlFOJproj 2 2) -querySynchronised :: E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Word64)) -querySynchronised office = to . runReader $ do - exam' <- view queryExam - externalExam' <- view queryExternalExam - let - examSynchronised examId = E.subSelectCount . E.from $ \examResult -> do - E.where_ $ examResult E.^. ExamResultExam E.==. examId - E.where_ $ Exam.examOfficeExamResultAuth office examResult - E.where_ $ Exam.resultIsSynced office examResult - externalExamSynchronised externalExamId = E.subSelectCount . E.from $ \externalExamResult -> do - E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. externalExamId - E.where_ $ ExternalExam.examOfficeExternalExamResultAuth office externalExamResult - E.where_ $ ExternalExam.resultIsSynced office externalExamResult - return $ E.maybe (E.val 0) examSynchronised (exam' E.?. ExamId) E.+. E.maybe (E.val 0) externalExamSynchronised (externalExam' E.?. ExternalExamId) - -queryResults :: E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Word64)) -queryResults office = to . runReader $ do - exam' <- view queryExam - externalExam' <- view queryExternalExam - let - results examId = E.subSelectCount . E.from $ \examResult -> do - E.where_ $ examResult E.^. ExamResultExam E.==. examId - E.where_ $ Exam.examOfficeExamResultAuth office examResult - externalResults externalExamId = E.subSelectCount . E.from $ \externalExamResult -> do - E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. externalExamId - E.where_ $ ExternalExam.examOfficeExternalExamResultAuth office externalExamResult - return $ E.maybe (E.val 0) results (exam' E.?. ExamId) E.+. E.maybe (E.val 0) externalResults (externalExam' E.?. ExternalExamId) - -queryIsSynced :: UTCTime -> E.SqlExpr (E.Value UserId) -> Getter ExamsTableExpr (E.SqlExpr (E.Value Bool)) -queryIsSynced now office = to . runReader $ do - exam' <- view queryExam - externalExam' <- view queryExternalExam - school' <- view querySchool - let - examSynchronised examId = E.not_ . E.exists . E.from $ \examResult -> do - E.where_ $ examResult E.^. ExamResultExam E.==. examId - E.where_ $ Exam.examOfficeExamResultAuth office examResult - E.where_ . E.not_ $ Exam.resultIsSynced office examResult - externalExamSynchronised externalExamId = E.not_ . E.exists . E.from $ \externalExamResult -> do - E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. externalExamId - E.where_ $ ExternalExam.examOfficeExternalExamResultAuth office externalExamResult - E.where_ . E.not_ $ ExternalExam.resultIsSynced office externalExamResult - open examClosed' examFinished' - = E.bool (E.maybe E.true (E.>. E.val now) $ E.min examClosed' examFinished') - (E.maybe E.true (E.>. E.val now) examClosed') - (E.maybe E.false (E.==. E.val ExamCloseSeparate) (school' E.?. SchoolExamCloseMode)) - return $ E.maybe E.false examSynchronised (exam' E.?. ExamId) E.||. E.maybe2 E.false open (exam' E.?. ExamClosed) (exam' E.?. ExamFinished) E.||. E.maybe E.false externalExamSynchronised (externalExam' E.?. ExternalExamId) - resultExam :: Traversal' ExamsTableData (Entity Exam) resultExam = _dbrOutput . _1 . _Right . _1 @@ -128,10 +82,6 @@ getEOExamsR = do externalExamLink ExternalExam{..} = SomeRoute $ EExamR externalExamTerm externalExamSchool externalExamCourseName externalExamExamName EEGradesR - querySynchronised' = querySynchronised $ E.val uid - queryResults' = queryResults $ E.val uid - queryIsSynced' = queryIsSynced now $ E.val uid - examsDBTable = DBTable{..} where dbtSQLQuery = runReaderT $ do @@ -140,19 +90,15 @@ getEOExamsR = do school <- view querySchool externalExam <- view queryExternalExam - synchronised <- view querySynchronised' - results <- view queryResults' - lift $ do E.on E.false E.on $ school E.?. SchoolId E.==. course E.?. CourseSchool E.on $ exam E.?. ExamCourse E.==. course E.?. CourseId - E.where_ $ results E.>. E.val 0 E.where_ $ (E.not_ (E.isNothing $ exam E.?. ExamId) E.&&. E.not_ (E.isNothing $ course E.?. CourseId) E.&&. E.isNothing (externalExam E.?. ExternalExamId)) E.||. ( E.isNothing (exam E.?. ExamId) E.&&. E.isNothing (course E.?. CourseId) E.&&. E.not_ (E.isNothing $ externalExam E.?. ExternalExamId)) - return (exam, course, school, externalExam, synchronised, results) + return (exam, course, school, externalExam) dbtRowKey = views ($(multifocusG 2) queryExam queryExternalExam) (bimap (E.?. ExamId) (E.?. ExternalExamId)) dbtProj :: DBRow _ -> DB ExamsTableData @@ -162,11 +108,24 @@ getEOExamsR = do school <- view _3 externalExam <- view _4 + let + getExamResults = for_ exam $ \(Entity examId _) -> E.selectSource . E.from $ \examResult -> do + E.where_ $ examResult E.^. ExamResultExam E.==. E.val examId + E.where_ $ Exam.examOfficeExamResultAuth (E.val uid) examResult + return $ Exam.resultIsSynced (E.val uid) examResult + getExternalExamResults = for_ externalExam $ \(Entity externalExamId _) -> E.selectSource . E.from $ \externalExamResult -> do + E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. E.val externalExamId + E.where_ $ ExternalExam.examOfficeExternalExamResultAuth (E.val uid) externalExamResult + return $ ExternalExam.resultIsSynced (E.val uid) externalExamResult + getResults = getExamResults >> getExternalExamResults + foldResult (E.Value isSynced) = (Sum 1, guardMonoid isSynced $ Sum 1) + (Sum resultCount, Sum syncedCount) <- lift . runConduit $ getResults .| C.foldMap foldResult + case (exam, course, school, externalExam) of - (Just exam', Just course', Just school', Nothing) -> - (Right (exam', course', school'),,) <$> view (_5 . _Value . _Integral) <*> view (_6 . _Value . _Integral) - (Nothing, Nothing, Nothing, Just externalExam') -> - (Left externalExam',,) <$> view (_5 . _Value . _Integral) <*> view (_6 . _Value . _Integral) + (Just exam', Just course', Just school', Nothing) -> return + (Right (exam', course', school'), syncedCount, resultCount) + (Nothing, Nothing, Nothing, Just externalExam') -> return + (Left externalExam', syncedCount, resultCount) _other -> return $ error "Got exam & externalExam in same result" @@ -216,8 +175,10 @@ getEOExamsR = do , emptyOpticColonnade (resultCourse . _entityVal . _courseTerm <> resultExternalExam . _entityVal . _externalExamTerm) colTermShort ] dbtSorting = mconcat - [ singletonMap "synced" . SortColumn $ (E./.) <$> view querySynchronised' <*> view queryResults' - , singletonMap "is-synced" . SortColumn $ view queryIsSynced' + [ singletonMap "synced" $ + SortProjected . comparing $ ((/) `on` toRational) <$> view resultSynchronised <*> view resultResults + , singletonMap "is-synced" $ + SortProjected . comparing $ (>=) <$> view resultSynchronised <*> view resultResults , sortExamName (to $ E.unsafeCoalesce . sequence [views queryExam (E.?. ExamName), views queryExternalExam (E.?. ExternalExamExamName)]) , sortExamTime (queryExam . $(multifocusG 2) (to $ E.joinV . (E.?. ExamStart)) (to $ E.joinV . (E.?. ExamEnd))) , sortExamFinished (queryExam . to (E.joinV . (E.?. ExamFinished))) @@ -236,12 +197,14 @@ getEOExamsR = do -> hasReadAccessTo . urlRoute $ externalExamLink eexam :: DB Bool | otherwise -> return $ error "Got neither exam nor externalExam in result" + , singletonMap "has-results" . FilterProjected $ \(Any b) r -> (return $ b == (r ^. resultResults > 0) :: DB Bool) + , singletonMap "is-synced" . FilterProjected $ \(Any b) r -> (return $ b == (r ^. resultSynchronised >= r ^. resultResults) :: DB Bool) ] dbtFilterUI = mconcat - [ + [ flip (prismAForm $ singletonFilter "is-synced" . maybePrism _PathPiece) $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgExamSynchronised) ] - dbtStyle = def -- { dbsFilterLayout = defaultDBSFilterLayout } + dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout } dbtParams = def dbtIdent :: Text @@ -255,6 +218,7 @@ getEOExamsR = do examsDBTableValidator = def & defaultSorting [SortAscBy "is-synced", SortAscBy "exam-time"] & forceFilter "may-access" (Any True) + & forceFilter "has-results" (Any True) dbTableWidget' examsDBTableValidator examsDBTable diff --git a/src/Handler/Metrics.hs b/src/Handler/Metrics.hs index 8eef6305d..4aa4c9a84 100644 --- a/src/Handler/Metrics.hs +++ b/src/Handler/Metrics.hs @@ -26,6 +26,7 @@ getMetricsR = selectRep $ do samples <- sortBy metricSort <$> collectMetrics metricsBearer <- runMaybeT . hoist runDB $ do + guardM $ hasGlobalGetParam GetGenerateToken uid <- MaybeT maybeAuthId guardM . lift . existsBy $ UniqueUserGroupMember UserGroupMetrics uid diff --git a/src/Handler/Utils/StudyFeatures.hs b/src/Handler/Utils/StudyFeatures.hs index 7e3dc481a..213657cdd 100644 --- a/src/Handler/Utils/StudyFeatures.hs +++ b/src/Handler/Utils/StudyFeatures.hs @@ -30,6 +30,8 @@ import qualified Database.Esqueleto as E import qualified Database.Esqueleto.PostgreSQL as E import qualified Database.Esqueleto.Utils as E +import qualified Data.Conduit.Combinators as C + data UserTableStudyFeature = UserTableStudyFeature { userTableField @@ -114,20 +116,16 @@ isRelevantStudyFeature termField record studyFeatures E.&&. overlap studyFeatures' E.>. overlap studyFeatures isRelevantStudyFeatureCached :: PersistEntity record - => EntityField record TermId - -> E.SqlExpr (Entity record) - -> E.SqlExpr (Entity StudyFeatures) - -> E.SqlExpr (E.Value Bool) + => EntityField record TermId + -> E.SqlExpr (Entity record) + -> E.SqlExpr (Entity StudyFeatures) + -> E.SqlExpr (E.Value Bool) isRelevantStudyFeatureCached termField record studyFeatures - = E.bool calcNow useCache $ studyFeatures E.^. StudyFeaturesRelevanceCached - where - useCache - = E.exists . E.from $ \relevantStudyFeatures -> - E.where_ $ relevantStudyFeatures E.^. RelevantStudyFeaturesTerm E.==. record E.^. termField - E.&&. relevantStudyFeatures E.^. RelevantStudyFeaturesStudyFeatures E.==. studyFeatures E.^. StudyFeaturesId - calcNow = isRelevantStudyFeature termField record studyFeatures + = E.exists . E.from $ \relevantStudyFeatures -> + E.where_ $ relevantStudyFeatures E.^. RelevantStudyFeaturesTerm E.==. record E.^. termField + E.&&. relevantStudyFeatures E.^. RelevantStudyFeaturesStudyFeatures E.==. studyFeatures E.^. StudyFeaturesId -cacheStudyFeatureRelevance :: MonadIO m +cacheStudyFeatureRelevance :: MonadResource m => (E.SqlExpr (Entity StudyFeatures) -> E.SqlExpr (E.Value Bool)) -> SqlPersistT m () cacheStudyFeatureRelevance fFilter = do @@ -139,9 +137,15 @@ cacheStudyFeatureRelevance fFilter = do return $ RelevantStudyFeatures E.<# (term E.^. TermId) E.<&> (studyFeatures E.^. StudyFeaturesId) ) ( \_current _excluded -> [] ) - E.update $ \studyFeatures -> do - E.set studyFeatures [ StudyFeaturesRelevanceCached E.=. E.true ] - E.where_ $ fFilter studyFeatures + + let getStudyFeatures = E.selectSource . E.from $ \studyFeatures -> do + E.where_ $ fFilter studyFeatures + E.where_ . E.isNothing $ studyFeatures E.^. StudyFeaturesRelevanceCached + return $ studyFeatures E.^. StudyFeaturesId + migrateStudyFeatures genUUID lift' (E.Value sfId) = do + uuid <- genUUID + lift' $ update sfId [ StudyFeaturesRelevanceCached =. Just uuid ] + in runConduit $ getStudyFeatures .| randUUIDC (\genUUID lift' -> C.mapM_ $ migrateStudyFeatures genUUID lift') isCourseStudyFeature :: E.SqlExpr (Entity Course) -> E.SqlExpr (Entity StudyFeatures) -> E.SqlExpr (E.Value Bool) isCourseStudyFeature = isRelevantStudyFeatureCached CourseTerm diff --git a/src/Handler/Utils/StudyFeatures/Parse.hs b/src/Handler/Utils/StudyFeatures/Parse.hs index a0e749f86..516dd1b95 100644 --- a/src/Handler/Utils/StudyFeatures/Parse.hs +++ b/src/Handler/Utils/StudyFeatures/Parse.hs @@ -43,7 +43,7 @@ pStudyFeatures studyFeaturesUser now = do studyFeaturesSuperField = Nothing studyFeaturesFirstObserved = Just now studyFeaturesLastObserved = now - studyFeaturesRelevanceCached = False + studyFeaturesRelevanceCached = Nothing return StudyFeatures{..} pStudyFeature `sepBy1` char '#' diff --git a/src/Handler/Utils/Users.hs b/src/Handler/Utils/Users.hs index 65a7af6c0..cfd159c74 100644 --- a/src/Handler/Utils/Users.hs +++ b/src/Handler/Utils/Users.hs @@ -758,7 +758,7 @@ assimilateUser newUserId oldUserId = mapReaderT execWriterT $ do , StudyFeaturesFirstObserved =. (min `on` studyFeaturesFirstObserved) oldStudyFeatures newStudyFeatures , StudyFeaturesLastObserved =. (max `on` studyFeaturesLastObserved) oldStudyFeatures newStudyFeatures , StudyFeaturesValid =. ((||) `on` studyFeaturesValid) oldStudyFeatures newStudyFeatures - , StudyFeaturesRelevanceCached =. ((||) `on` studyFeaturesRelevanceCached) oldStudyFeatures newStudyFeatures + , StudyFeaturesRelevanceCached =. ((<|>) `on` studyFeaturesRelevanceCached) oldStudyFeatures newStudyFeatures ] E.insertSelectWithConflict UniqueRelevantStudyFeatures diff --git a/src/Handler/Utils/Workflow/EdgeForm.hs b/src/Handler/Utils/Workflow/EdgeForm.hs index 1e6b033f1..d7b932e82 100644 --- a/src/Handler/Utils/Workflow/EdgeForm.hs +++ b/src/Handler/Utils/Workflow/EdgeForm.hs @@ -81,14 +81,15 @@ workflowEdgeForm mwwId mPrev = runMaybeT $ do MsgRenderer mr <- getMsgRenderer ctx' <- bitraverse (MaybeT . getEntity) (MaybeT . getEntity) mwwId - let (scope, graph) = case ctx of - Left WorkflowInstance{..} -> ( _DBWorkflowScope # workflowInstanceScope - , _DBWorkflowGraph # workflowInstanceGraph - ) - Right WorkflowWorkflow{..} -> ( _DBWorkflowScope # workflowWorkflowScope - , _DBWorkflowGraph # workflowWorkflowGraph - ) - wState = ctx ^? _Right . _workflowWorkflowState . to last . _wpTo + let (scope, sharedGraphId) = case ctx' of + Left (Entity _ WorkflowInstance{..}) -> ( _DBWorkflowScope # workflowInstanceScope + , workflowInstanceGraph + ) + Right (Entity _ WorkflowWorkflow{..}) -> ( _DBWorkflowScope # workflowWorkflowScope + , workflowWorkflowGraph + ) + graph <- lift $ getSharedIdWorkflowGraph sharedGraphId + let wState = ctx ^? _Right . _workflowWorkflowState . to last . _wpTo wPayload' = ctx ^? _Right . _workflowWorkflowState . re _DBWorkflowState ctx = bimap entityVal entityVal ctx' mAuthId <- maybeAuthId diff --git a/src/Handler/Workflow/Definition/Edit.hs b/src/Handler/Workflow/Definition/Edit.hs index 1f7dbf8cc..1967fc958 100644 --- a/src/Handler/Workflow/Definition/Edit.hs +++ b/src/Handler/Workflow/Definition/Edit.hs @@ -5,6 +5,7 @@ module Handler.Workflow.Definition.Edit ) where import Import +import Utils.Workflow import Handler.Utils import Handler.Workflow.Definition.Form @@ -29,7 +30,7 @@ postAWDEditR wds' wdn = do | Entity _ WorkflowDefinitionInstanceDescription{..} <- iDescs ] - wdfGraph <- toWorkflowGraphForm workflowDefinitionGraph + wdfGraph <- toWorkflowGraphForm =<< getSharedDBWorkflowGraph workflowDefinitionGraph return WorkflowDefinitionForm { wdfScope = workflowDefinitionScope @@ -44,9 +45,10 @@ postAWDEditR wds' wdn = do act <- formResultMaybe editRes $ \WorkflowDefinitionForm{..} -> do wdfGraph' <- fromWorkflowGraphForm wdfGraph + wdfGraph'' <- insertSharedWorkflowGraph wdfGraph' insConflict <- replaceUnique wdId WorkflowDefinition - { workflowDefinitionGraph = wdfGraph' + { workflowDefinitionGraph = wdfGraph'' , workflowDefinitionScope = wdfScope , workflowDefinitionName = wdfName , workflowDefinitionInstanceCategory = wdfInstanceCategory diff --git a/src/Handler/Workflow/Definition/Instantiate.hs b/src/Handler/Workflow/Definition/Instantiate.hs index 4f0773167..fbac35cd6 100644 --- a/src/Handler/Workflow/Definition/Instantiate.hs +++ b/src/Handler/Workflow/Definition/Instantiate.hs @@ -3,6 +3,7 @@ module Handler.Workflow.Definition.Instantiate ) where import Import +import Utils.Workflow import Handler.Utils import Handler.Utils.Workflow.Form @@ -22,9 +23,10 @@ postAWDInstantiateR wds' wdn = do & over _wisTerm unTermKey & over _wisSchool unSchoolKey & over _wisCourse (view _SqlKey) + workflowInstanceGraph <- insertSharedWorkflowGraph wifGraph' instId <- insertUnique WorkflowInstance { workflowInstanceDefinition = Just wdId - , workflowInstanceGraph = wifGraph' + , workflowInstanceGraph , workflowInstanceScope = wifScope' , workflowInstanceName = wifName , workflowInstanceCategory = wifCategory diff --git a/src/Handler/Workflow/Definition/New.hs b/src/Handler/Workflow/Definition/New.hs index 898c3b831..827986354 100644 --- a/src/Handler/Workflow/Definition/New.hs +++ b/src/Handler/Workflow/Definition/New.hs @@ -5,6 +5,7 @@ module Handler.Workflow.Definition.New import Import import Handler.Utils import Handler.Workflow.Definition.Form +import Utils.Workflow getAdminWorkflowDefinitionNewR, postAdminWorkflowDefinitionNewR :: Handler Html @@ -15,9 +16,10 @@ postAdminWorkflowDefinitionNewR = do act <- formResultMaybe newRes $ \WorkflowDefinitionForm{ .. } -> do wdfGraph' <- fromWorkflowGraphForm wdfGraph + workflowDefinitionGraph <- insertSharedWorkflowGraph wdfGraph' insRes <- insertUnique WorkflowDefinition - { workflowDefinitionGraph = wdfGraph' + { workflowDefinitionGraph , workflowDefinitionScope = wdfScope , workflowDefinitionName = wdfName , workflowDefinitionInstanceCategory = wdfInstanceCategory diff --git a/src/Handler/Workflow/Instance/Form.hs b/src/Handler/Workflow/Instance/Form.hs index 549158565..246ac38cf 100644 --- a/src/Handler/Workflow/Instance/Form.hs +++ b/src/Handler/Workflow/Instance/Form.hs @@ -64,7 +64,7 @@ workflowInstanceForm forcedDefId template = renderWForm FormStandard $ do [ (workflowDefinitionInstanceDescriptionLanguage, (workflowDefinitionInstanceDescriptionTitle, workflowDefinitionInstanceDescriptionDescription)) | Entity _ WorkflowDefinitionInstanceDescription{..} <- descs ] - defGraph <- for defEnt $ toWorkflowGraphForm . workflowDefinitionGraph . entityVal + defGraph <- for defEnt $ toWorkflowGraphForm <=< lift . lift . getSharedDBWorkflowGraph . workflowDefinitionGraph . entityVal wifScopeRes <- aFormToWForm . hoistAForm lift $ workflowInstanceScopeForm (workflowDefinitionScope . entityVal <$> defEnt) (fslI MsgWorkflowScope) (wifScope <$> template) wifNameRes <- wreq ciField (fslI MsgWorkflowInstanceName) (fmap wifName template <|> fmap (workflowDefinitionName . entityVal) defEnt) diff --git a/src/Handler/Workflow/Instance/Initiate.hs b/src/Handler/Workflow/Instance/Initiate.hs index f9f5677b2..fda1576d6 100644 --- a/src/Handler/Workflow/Instance/Initiate.hs +++ b/src/Handler/Workflow/Instance/Initiate.hs @@ -45,7 +45,8 @@ workflowInstanceInitiateR rScope win = do ((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm edgeAct <- formResultMaybe edgeRes $ \edgeRes' -> do - workflowWorkflowState <- view _DBWorkflowState <$> followEdge (_DBWorkflowGraph # workflowInstanceGraph) edgeRes' Nothing + wGraph <- getSharedIdWorkflowGraph workflowInstanceGraph + workflowWorkflowState <- view _DBWorkflowState <$> followEdge wGraph edgeRes' Nothing wwId <- insert WorkflowWorkflow { workflowWorkflowInstance = Just wiId diff --git a/src/Handler/Workflow/Instance/New.hs b/src/Handler/Workflow/Instance/New.hs index c02f6d2e8..e715c62ba 100644 --- a/src/Handler/Workflow/Instance/New.hs +++ b/src/Handler/Workflow/Instance/New.hs @@ -25,13 +25,14 @@ adminWorkflowInstanceNewR wdId = do act <- formResultMaybe instRes $ \WorkflowInstanceForm{..} -> do wifGraph' <- fromWorkflowGraphForm wifGraph + workflowInstanceGraph <- insertSharedWorkflowGraph wifGraph' let wifScope' = wifScope & over _wisTerm unTermKey & over _wisSchool unSchoolKey & over _wisCourse (view _SqlKey) instId <- insertUnique WorkflowInstance { workflowInstanceDefinition = wdId - , workflowInstanceGraph = wifGraph' + , workflowInstanceGraph , workflowInstanceScope = wifScope' , workflowInstanceName = wifName , workflowInstanceCategory = wifCategory diff --git a/src/Handler/Workflow/Workflow/List.hs b/src/Handler/Workflow/Workflow/List.hs index c9333ccbb..97d56fb28 100644 --- a/src/Handler/Workflow/Workflow/List.hs +++ b/src/Handler/Workflow/Workflow/List.hs @@ -231,8 +231,8 @@ workflowWorkflowList (title, heading) WWListColumns{..} sqlPred = do MaybeT $ selectWorkflowInstanceDescription wiId cID <- encrypt wwId rScope <- lift . runMaybeT . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope - let WorkflowGraph{..} = ww ^. _entityVal . _workflowWorkflowGraph . from _DBWorkflowGraph - hasWorkflowRole' :: WorkflowRole UserId -> DB Bool + WorkflowGraph{..} <- lift . getSharedIdWorkflowGraph $ ww ^. _entityVal . _workflowWorkflowGraph + let hasWorkflowRole' :: WorkflowRole UserId -> DB Bool hasWorkflowRole' role = maybeT (return False) $ do rScope' <- hoistMaybe rScope let canonRoute = _WorkflowScopeRoute # (rScope', WorkflowWorkflowR cID WWWorkflowR) @@ -360,6 +360,8 @@ workflowWorkflowList (title, heading) WWListColumns{..} sqlPred = do jwiScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope let jwiName = workflowInstanceName return JsonWorkflowInstance{..} + let Entity _ WorkflowWorkflow{..} = res ^. resultWorkflowWorkflow + WorkflowGraph{..} <- getSharedIdWorkflowGraph workflowWorkflowGraph (fmap getLast -> wState) <- let go :: forall m. ( MonadHandler m @@ -410,9 +412,7 @@ workflowWorkflowList (title, heading) WWListColumns{..} sqlPred = do tell . Just $ Last (stCID, nTo, aUser, wpTime, payload) - Entity _ WorkflowWorkflow{..} = res ^. resultWorkflowWorkflow wState = review _DBWorkflowState workflowWorkflowState - WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph in runConduit $ sourceWorkflowActionInfos wwId wState .| execWriterC (C.mapM_ go) let jwwLastAction = wState <&> \(jwaIx, jwaTo, jwaUser, jwaTime, _) -> JsonWorkflowAction{..} diff --git a/src/Handler/Workflow/Workflow/Workflow.hs b/src/Handler/Workflow/Workflow/Workflow.hs index f63f95e43..430d8aa59 100644 --- a/src/Handler/Workflow/Workflow/Workflow.hs +++ b/src/Handler/Workflow/Workflow/Workflow.hs @@ -83,8 +83,8 @@ workflowR rScope cID = do WorkflowWorkflow{..} <- get404 wwId maybeT notFound . void . assertM (== review _DBWorkflowScope workflowWorkflowScope) $ fromRouteWorkflowScope rScope mEdgeForm <- workflowEdgeForm (Right wwId) Nothing + wGraph <- getSharedIdWorkflowGraph workflowWorkflowGraph let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) - wGraph = _DBWorkflowGraph # workflowWorkflowGraph mEdge <- for mEdgeForm $ \edgeForm -> do ((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm diff --git a/src/Jobs.hs b/src/Jobs.hs index 642692491..a5ebcb40d 100644 --- a/src/Jobs.hs +++ b/src/Jobs.hs @@ -10,6 +10,7 @@ module Jobs import Import hiding (StateT) import Jobs.Types as Types hiding (JobCtl(JobCtlQueue)) import Jobs.Queue +import Jobs.Offload import Jobs.Crontab import qualified Data.Conduit.List as C @@ -64,6 +65,7 @@ import Jobs.Handler.Files import Jobs.Handler.ExternalApis import Jobs.Handler.PersonalisedSheetFiles import Jobs.Handler.PruneOldSentMails +import Jobs.Handler.StudyFeatures import Jobs.HealthReport @@ -106,6 +108,7 @@ handleJobs foundation@UniWorX{..} jobShutdown <- liftIO newEmptyTMVarIO jobCurrentCrontab <- liftIO $ newTVarIO Nothing jobHeldLocks <- liftIO $ newTVarIO Set.empty + jobOffload <- liftIO newEmptyTMVarIO registerJobHeldLocksCount jobHeldLocks registerJobWorkerQueueDepth appJobState atomically $ putTMVar appJobState JobState @@ -156,7 +159,9 @@ manageJobPool foundation@UniWorX{..} unmask = shutdownOnException $ \routeExc -> atomically . asum $ [ spawnMissingWorkers , reapDeadWorkers - ] ++ maybe [] (\(cTime, delay) -> [return () <$ waitDelay delay, transferJobs cTime]) transferInfo ++ + ] ++ maybe [] (\(cTime, delay) -> [return () <$ waitDelay delay, transferJobs cTime]) transferInfo + ++ maybeToList (manageOffloadHandler <$> mkJobOffloadHandler (appDatabaseConf appSettings') (appJobMode appSettings')) + ++ [ terminateGracefully terminate' ] where @@ -287,6 +292,27 @@ manageJobPool foundation@UniWorX{..} unmask = shutdownOnException $ \routeExc -> return $ $logWarnS "JobPoolManager" [st|Moved #{tshow (olength movePairs)} long-unadressed jobs from #{tshow (olength senders)} senders to #{tshow (olength receivers)} receivers|] + manageOffloadHandler :: ReaderT UniWorX m JobOffloadHandler -> STM (ContT () m ()) + manageOffloadHandler spawn = do + shouldTerminate' <- readTMVar appJobState >>= fmap not . isEmptyTMVar . jobShutdown + guard $ not shouldTerminate' + + JobContext{jobOffload} <- jobContext <$> readTMVar appJobState + cOffload <- tryReadTMVar jobOffload + + let respawn = do + nOffload <- lift $ runReaderT spawn foundation + atomically $ do + putTMVar jobOffload nOffload + whenIsJust cOffload $ \pOffload -> do + pOutgoing <- readTVar $ jobOffloadOutgoing pOffload + modifyTVar (jobOffloadOutgoing nOffload) (pOutgoing <>) + + respawn <$ case cOffload of + Nothing -> return () + Just JobOffloadHandler{..} -> waitSTM jobOffloadHandler + + stopJobCtl :: MonadUnliftIO m => UniWorX -> m () -- ^ Stop all worker threads currently running stopJobCtl UniWorX{appJobState} = do @@ -472,46 +498,55 @@ handleJobs' wNum = C.mapM_ $ \jctl -> hoist delimitInternalState . withJobWorker $logDebugS logIdent "JobCtlQueue..." lift $ queueJob' job $logInfoS logIdent "JobCtlQueue" - handleCmd (JobCtlPerform jId) = handle handleQueueException . jLocked jId $ \(Entity _ j@QueuedJob{..}) -> lift $ do - content <- case fromJSON queuedJobContent of - Aeson.Success c -> return c - Aeson.Error t -> do - $logErrorS logIdent $ "Aeson decoding error: " <> pack t - throwM $ JInvalid jId j - - $logInfoS logIdent $ tshow content - $logDebugS logIdent . LT.toStrict . decodeUtf8 $ Aeson.encode content + handleCmd (JobCtlPerform jId) = do + jMode <- getsYesod $ view _appJobMode + case jMode of + JobsLocal{} -> performLocal + JobsOffload -> performOffload + where + performOffload = hoist atomically $ do + JobOffloadHandler{..} <- lift . readTMVar =<< asks jobOffload + lift $ modifyTVar jobOffloadOutgoing (`snoc` jId) + performLocal = handle handleQueueException . jLocked jId $ \(Entity _ j@QueuedJob{..}) -> lift $ do + content <- case fromJSON queuedJobContent of + Aeson.Success c -> return c + Aeson.Error t -> do + $logErrorS logIdent $ "Aeson decoding error: " <> pack t + throwM $ JInvalid jId j - instanceID' <- getsYesod $ view instanceID - now <- liftIO getCurrentTime + $logInfoS logIdent $ tshow content + $logDebugS logIdent . LT.toStrict . decodeUtf8 $ Aeson.encode content - let cleanup = do - when queuedJobWriteLastExec $ - void $ upsertBy - (UniqueCronLastExec queuedJobContent) - CronLastExec - { cronLastExecJob = queuedJobContent - , cronLastExecTime = now - , cronLastExecInstance = instanceID' - } - [ CronLastExecTime =. now - , CronLastExecInstance =. instanceID' - ] - delete jId + instanceID' <- getsYesod $ view instanceID + now <- liftIO getCurrentTime - case performJob content of - JobHandlerAtomic act -> runDBJobs . setSerializableBatch $ do - act & withJobWorkerState wNum (JobWorkerExecJob content) - hoist lift cleanup - JobHandlerException act -> do - act & withJobWorkerState wNum (JobWorkerExecJob content) - runDB $ setSerializableBatch cleanup - JobHandlerAtomicWithFinalizer act fin -> do - res <- runDBJobs . setSerializableBatch $ do - res <- act & withJobWorkerState wNum (JobWorkerExecJob content) - hoist lift cleanup - return res - fin res + let cleanup = do + when queuedJobWriteLastExec $ + void $ upsertBy + (UniqueCronLastExec queuedJobContent) + CronLastExec + { cronLastExecJob = queuedJobContent + , cronLastExecTime = now + , cronLastExecInstance = instanceID' + } + [ CronLastExecTime =. now + , CronLastExecInstance =. instanceID' + ] + delete jId + + case performJob content of + JobHandlerAtomic act -> runDBJobs . setSerializableBatch $ do + act & withJobWorkerState wNum (JobWorkerExecJob content) + hoist lift cleanup + JobHandlerException act -> do + act & withJobWorkerState wNum (JobWorkerExecJob content) + runDB $ setSerializableBatch cleanup + JobHandlerAtomicWithFinalizer act fin -> do + res <- runDBJobs . setSerializableBatch $ do + res <- act & withJobWorkerState wNum (JobWorkerExecJob content) + hoist lift cleanup + return res + fin res handleCmd JobCtlDetermineCrontab = do $logDebugS logIdent "DetermineCrontab..." newCTab <- liftHandler . runDB $ setSerializableBatch determineCrontab' diff --git a/src/Jobs/Crontab.hs b/src/Jobs/Crontab.hs index c4944141c..dfacd3172 100644 --- a/src/Jobs/Crontab.hs +++ b/src/Jobs/Crontab.hs @@ -27,17 +27,6 @@ determineCrontab :: DB (Crontab JobCtl) determineCrontab = execWriterT $ do UniWorX{ appSettings' = AppSettings{..} } <- getYesod - case appJobFlushInterval of - Just interval -> tell $ HashMap.singleton - JobCtlFlush - Cron - { cronInitial = CronAsap - , cronRepeat = CronRepeatScheduled CronAsap - , cronRateLimit = interval - , cronNotAfter = Right CronNotScheduled - } - Nothing -> return () - whenIsJust appJobCronInterval $ \interval -> tell $ HashMap.singleton JobCtlDetermineCrontab @@ -48,77 +37,6 @@ determineCrontab = execWriterT $ do , cronNotAfter = Right CronNotScheduled } - oldestInvitationMUTC <- lift $ preview (_head . _entityVal . _invitationExpiresAt . _Just) <$> selectList [InvitationExpiresAt !=. Nothing] [Asc InvitationExpiresAt, LimitTo 1] - whenIsJust oldestInvitationMUTC $ \oldestInvUTC -> tell $ HashMap.singleton - (JobCtlQueue JobPruneInvitations) - Cron - { cronInitial = CronTimestamp $ utcToLocalTime oldestInvUTC - , cronRepeat = CronRepeatOnChange - , cronRateLimit = nominalDay - , cronNotAfter = Right CronNotScheduled - } - - oldestSessionFile <- lift $ preview (_head . _entityVal . _sessionFileTouched) <$> selectList [] [Asc SessionFileTouched, LimitTo 1] - whenIsJust oldestSessionFile $ \oldest -> tell $ HashMap.singleton - (JobCtlQueue JobPruneSessionFiles) - Cron - { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime appSessionFilesExpire oldest - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appSessionFilesExpire / 2 - , cronNotAfter = Right CronNotScheduled - } - - oldestFallbackPersonalisedSheetFilesKey <- lift $ preview (_head . _entityVal . _fallbackPersonalisedSheetFilesKeyGenerated) <$> selectList [] [Asc FallbackPersonalisedSheetFilesKeyGenerated, LimitTo 1] - whenIsJust oldestFallbackPersonalisedSheetFilesKey $ \oldest -> tell $ HashMap.singleton - (JobCtlQueue JobPruneFallbackPersonalisedSheetFilesKeys) - Cron - { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime appFallbackPersonalisedSheetFilesKeysExpire oldest - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appFallbackPersonalisedSheetFilesKeysExpire / 2 - , cronNotAfter = Right CronNotScheduled - } - - oldestSentMail <- lift $ preview (_head . _entityVal . _sentMailSentAt) <$> selectList [] [Asc SentMailSentAt, LimitTo 1] - whenIsJust ((,) <$> appMailRetainSent <*> oldestSentMail) $ \(retain, oldest) -> tell $ HashMap.singleton - (JobCtlQueue JobPruneOldSentMails) - Cron - { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime retain oldest - , cronRepeat = CronRepeatOnChange - , cronRateLimit = retain / 2 - , cronNotAfter = Right CronNotScheduled - } - - - whenIsJust (appInjectFiles <* appUploadCacheConf) $ \iInterval -> - tell $ HashMap.singleton - (JobCtlQueue JobInjectFiles) - Cron - { cronInitial = CronAsap - , cronRepeat = CronRepeatScheduled CronAsap - , cronRateLimit = iInterval - , cronNotAfter = Right CronNotScheduled - } - - whenIsJust appRechunkFiles $ \rInterval -> - tell $ HashMap.singleton - (JobCtlQueue JobRechunkFiles) - Cron - { cronInitial = CronAsap - , cronRepeat = CronRepeatScheduled CronAsap - , cronRateLimit = rInterval - , cronNotAfter = Right CronNotScheduled - } - - whenIsJust appCheckMissingFiles $ \rInterval -> - tell $ HashMap.singleton - (JobCtlQueue JobDetectMissingFiles) - Cron - { cronInitial = CronAsap - , cronRepeat = CronRepeatScheduled CronAsap - , cronRateLimit = rInterval - , cronNotAfter = Right CronNotScheduled - } - tell . flip foldMap universeF $ \kind -> case appHealthCheckInterval kind of Just int -> HashMap.singleton @@ -131,368 +49,478 @@ determineCrontab = execWriterT $ do } Nothing -> mempty - let newyear = cronCalendarAny - { cronDayOfYear = cronMatchOne 1 - } - in tell $ HashMap.singleton - (JobCtlQueue JobTruncateTransactionLog) + when (is _JobsLocal appJobMode) $ do + case appJobFlushInterval of + Just interval -> tell $ HashMap.singleton + JobCtlFlush Cron - { cronInitial = newyear - , cronRepeat = CronRepeatScheduled newyear - , cronRateLimit = minNominalYear + { cronInitial = CronAsap + , cronRepeat = CronRepeatScheduled CronAsap + , cronRateLimit = interval , cronNotAfter = Right CronNotScheduled } + Nothing -> return () - oldestLogEntry <- fmap listToMaybe . lift . E.select . E.from $ \transactionLog -> do - E.where_ . E.not_ . E.isNothing $ transactionLog E.^. TransactionLogRemote - E.orderBy [E.asc $ transactionLog E.^. TransactionLogTime] - E.limit 1 - return $ transactionLog E.^. TransactionLogTime - for_ oldestLogEntry $ \(E.Value oldestEntry) -> - tell $ HashMap.singleton - (JobCtlQueue JobDeleteTransactionLogIPs) + oldestInvitationMUTC <- lift $ preview (_head . _entityVal . _invitationExpiresAt . _Just) <$> selectList [InvitationExpiresAt !=. Nothing] [Asc InvitationExpiresAt, LimitTo 1] + whenIsJust oldestInvitationMUTC $ \oldestInvUTC -> tell $ HashMap.singleton + (JobCtlQueue JobPruneInvitations) Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appTransactionLogIPRetentionTime oldestEntry + { cronInitial = CronTimestamp $ utcToLocalTime oldestInvUTC , cronRepeat = CronRepeatOnChange , cronRateLimit = nominalDay , cronNotAfter = Right CronNotScheduled } - let - getNextIntervals within interval cInterval = do - now <- liftIO getPOSIXTime - return $ do - let - epochInterval = within / 2 - (currEpoch, epochNow) = now `divMod'` epochInterval - currInterval = epochNow `div'` interval - numIntervals = max 1 . floor $ epochInterval / interval - n = ceiling $ 4 * cInterval / interval - i <- [ negate (ceiling $ n % 2) .. ceiling $ n % 2 ] - let - ((+ currEpoch) -> nextEpoch, nextInterval) = (currInterval + i) `divMod` numIntervals - nextIntervalTime - = posixSecondsToUTCTime $ fromInteger nextEpoch * epochInterval + fromInteger nextInterval * interval - return (nextEpoch, nextInterval, nextIntervalTime, numIntervals) - - if - | is _Just appLdapConf - , Just syncWithin <- appSynchroniseLdapUsersWithin - , Just cInterval <- appJobCronInterval - -> do - nextIntervals <- getNextIntervals syncWithin appSynchroniseLdapUsersInterval cInterval - - forM_ nextIntervals $ \(nextEpoch, nextInterval, nextIntervalTime, numIntervals) -> do - tell $ HashMap.singleton - (JobCtlQueue JobSynchroniseLdap - { jEpoch = fromInteger nextEpoch - , jNumIterations = fromInteger numIntervals - , jIteration = fromInteger nextInterval - }) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ nextIntervalTime - , cronRepeat = CronRepeatNever - , cronRateLimit = appSynchroniseLdapUsersInterval - , cronNotAfter = Left syncWithin - } - | otherwise - -> return () - - whenIsJust ((,) <$> appPruneUnreferencedFilesWithin <*> appJobCronInterval) $ \(within, cInterval) -> do - nextIntervals <- getNextIntervals within appPruneUnreferencedFilesInterval cInterval - forM_ nextIntervals $ \(nextEpoch, nextInterval, nextIntervalTime, numIntervals) -> do - tell $ HashMap.singleton - (JobCtlQueue JobPruneUnreferencedFiles - { jEpoch = fromInteger nextEpoch - , jNumIterations = fromInteger numIntervals - , jIteration = fromInteger nextInterval - } - ) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ nextIntervalTime - , cronRepeat = CronRepeatNever - , cronRateLimit = appPruneUnreferencedFilesInterval - , cronNotAfter = Left within - } - - let - sheetJobs (Entity nSheet Sheet{..}) = do - for_ (max <$> sheetVisibleFrom <*> sheetActiveFrom) $ \aFrom -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationSheetActive{..}) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aFrom - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) sheetActiveTo - } - for_ (max <$> sheetVisibleFrom <*> sheetHintFrom) $ \hFrom -> maybeT (return ()) $ do - guard $ maybe True (\aFrom -> abs (diffUTCTime aFrom hFrom) > 300) sheetActiveFrom - guardM $ or2M (return $ maybe True (\sFrom -> abs (diffUTCTime sFrom hFrom) > 300) sheetSolutionFrom) - (fmap not . lift . lift $ exists [SheetFileType ==. SheetSolution, SheetFileSheet ==. nSheet]) - guardM . lift . lift $ exists [SheetFileType ==. SheetHint, SheetFileSheet ==. nSheet] - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationSheetHint{..}) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ hFrom - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) sheetActiveTo - } - for_ (max <$> sheetVisibleFrom <*> sheetSolutionFrom) $ \sFrom -> maybeT (return ()) $ do - guard $ maybe True (\aFrom -> abs (diffUTCTime aFrom sFrom) > 300) sheetActiveFrom - guardM . lift . lift $ exists [SheetFileType ==. SheetSolution, SheetFileSheet ==. nSheet] - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationSheetSolution{..}) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ sFrom - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Left nominalDay - } - for_ sheetActiveTo $ \aTo -> do - whenIsJust (max aTo <$> sheetVisibleFrom) $ \aTo' -> do - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationSheetSoonInactive{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . maybe id max sheetActiveFrom $ addUTCTime (-nominalDay) aTo' - , cronRepeat = CronRepeatOnChange -- Allow repetition of the notification (if something changes), but wait at least an hour - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ aTo - } - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationSheetInactive{..}) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aTo - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Left appNotificationExpiration - } - when sheetAutoDistribute $ - tell $ HashMap.singleton - (JobCtlQueue $ JobDistributeCorrections nSheet) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aTo - , cronRepeat = CronRepeatNever - , cronRateLimit = 3600 -- Irrelevant due to `cronRepeat` - , cronNotAfter = Left nominalDay - } - - runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ sheetJobs - - let - correctorNotifications :: Map (UserId, SheetId) (Max UTCTime) -> WriterT (Crontab JobCtl) DB () - correctorNotifications = (tell .) . Map.foldMapWithKey $ \(nUser, nSheet) (Max time) -> HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationCorrectionsAssigned { nUser, nSheet } ) + oldestSessionFile <- lift $ preview (_head . _entityVal . _sessionFileTouched) <$> selectList [] [Asc SessionFileTouched, LimitTo 1] + whenIsJust oldestSessionFile $ \oldest -> tell $ HashMap.singleton + (JobCtlQueue JobPruneSessionFiles) Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay time - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Left appNotificationExpiration + { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime appSessionFilesExpire oldest + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appSessionFilesExpire / 2 + , cronNotAfter = Right CronNotScheduled } - submissionsByCorrector :: Entity Submission -> Map (UserId, SheetId) (Max UTCTime) - submissionsByCorrector (Entity _ sub) - | Just ratingBy <- submissionRatingBy sub - , Just assigned <- submissionRatingAssigned sub - , not $ submissionRatingDone sub - = Map.singleton (ratingBy, submissionSheet sub) $ Max assigned - | otherwise - = Map.empty + oldestFallbackPersonalisedSheetFilesKey <- lift $ preview (_head . _entityVal . _fallbackPersonalisedSheetFilesKeyGenerated) <$> selectList [] [Asc FallbackPersonalisedSheetFilesKeyGenerated, LimitTo 1] + whenIsJust oldestFallbackPersonalisedSheetFilesKey $ \oldest -> tell $ HashMap.singleton + (JobCtlQueue JobPruneFallbackPersonalisedSheetFilesKeys) + Cron + { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime appFallbackPersonalisedSheetFilesKeysExpire oldest + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appFallbackPersonalisedSheetFilesKeysExpire / 2 + , cronNotAfter = Right CronNotScheduled + } - collateSubmissionsByCorrector acc entity = Map.unionWith (<>) acc $ submissionsByCorrector entity - correctorNotifications <=< runConduit $ - transPipe lift ( selectSource [ SubmissionRatingBy !=. Nothing, SubmissionRatingAssigned !=. Nothing ] [] - ) - .| C.fold collateSubmissionsByCorrector Map.empty + oldestSentMail <- lift $ preview (_head . _entityVal . _sentMailSentAt) <$> selectList [] [Asc SentMailSentAt, LimitTo 1] + whenIsJust ((,) <$> appMailRetainSent <*> oldestSentMail) $ \(retain, oldest) -> tell $ HashMap.singleton + (JobCtlQueue JobPruneOldSentMails) + Cron + { cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime retain oldest + , cronRepeat = CronRepeatOnChange + , cronRateLimit = retain / 2 + , cronNotAfter = Right CronNotScheduled + } - let - examSelect = E.selectSource . E.from $ \(exam `E.InnerJoin` course `E.InnerJoin` school) -> do - E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool - E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse - return (exam, course, school) - examJobs (Entity nExam Exam{..}, _, Entity _ School{..}) = do - newestResult <- lift . E.select . E.from $ \examResult -> do - E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam - return . E.max_ $ examResult E.^. ExamResultLastChanged + whenIsJust (appInjectFiles <* appUploadCacheConf) $ \iInterval -> + tell $ HashMap.singleton + (JobCtlQueue JobInjectFiles) + Cron + { cronInitial = CronAsap + , cronRepeat = CronRepeatScheduled CronAsap + , cronRateLimit = iInterval + , cronNotAfter = Right CronNotScheduled + } - whenIsJust examVisibleFrom $ \visibleFrom -> do - case over (mapped . _Value) ((max `on` NTop) examFinished) newestResult of - [E.Value (NTop (Just ts))] -> + whenIsJust appRechunkFiles $ \rInterval -> + tell $ HashMap.singleton + (JobCtlQueue JobRechunkFiles) + Cron + { cronInitial = CronAsap + , cronRepeat = CronRepeatScheduled CronAsap + , cronRateLimit = rInterval + , cronNotAfter = Right CronNotScheduled + } + + whenIsJust appCheckMissingFiles $ \rInterval -> + tell $ HashMap.singleton + (JobCtlQueue JobDetectMissingFiles) + Cron + { cronInitial = CronAsap + , cronRepeat = CronRepeatScheduled CronAsap + , cronRateLimit = rInterval + , cronNotAfter = Right CronNotScheduled + } + + let newyear = cronCalendarAny + { cronDayOfYear = cronMatchOne 1 + } + in tell $ HashMap.singleton + (JobCtlQueue JobTruncateTransactionLog) + Cron + { cronInitial = newyear + , cronRepeat = CronRepeatScheduled newyear + , cronRateLimit = minNominalYear + , cronNotAfter = Right CronNotScheduled + } + + oldestLogEntry <- fmap listToMaybe . lift . E.select . E.from $ \transactionLog -> do + E.where_ . E.not_ . E.isNothing $ transactionLog E.^. TransactionLogRemote + E.orderBy [E.asc $ transactionLog E.^. TransactionLogTime] + E.limit 1 + return $ transactionLog E.^. TransactionLogTime + for_ oldestLogEntry $ \(E.Value oldestEntry) -> + tell $ HashMap.singleton + (JobCtlQueue JobDeleteTransactionLogIPs) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appTransactionLogIPRetentionTime oldestEntry + , cronRepeat = CronRepeatOnChange + , cronRateLimit = nominalDay + , cronNotAfter = Right CronNotScheduled + } + + let + getNextIntervals within interval cInterval = do + now <- liftIO getPOSIXTime + return $ do + let + epochInterval = within / 2 + (currEpoch, epochNow) = now `divMod'` epochInterval + currInterval = epochNow `div'` interval + numIntervals = max 1 . floor $ epochInterval / interval + n = ceiling $ 4 * cInterval / interval + i <- [ negate (ceiling $ n % 2) .. ceiling $ n % 2 ] + let + ((+ currEpoch) -> nextEpoch, nextInterval) = (currInterval + i) `divMod` numIntervals + nextIntervalTime + = posixSecondsToUTCTime $ fromInteger nextEpoch * epochInterval + fromInteger nextInterval * interval + return (nextEpoch, nextInterval, nextIntervalTime, numIntervals) + + if + | is _Just appLdapConf + , Just syncWithin <- appSynchroniseLdapUsersWithin + , Just cInterval <- appJobCronInterval + -> do + nextIntervals <- getNextIntervals syncWithin appSynchroniseLdapUsersInterval cInterval + + forM_ nextIntervals $ \(nextEpoch, nextInterval, nextIntervalTime, numIntervals) -> do tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamResult{..}) + (JobCtlQueue JobSynchroniseLdap + { jEpoch = fromInteger nextEpoch + , jNumIterations = fromInteger numIntervals + , jIteration = fromInteger nextInterval + }) Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom ts - , cronRepeat = CronRepeatOnChange + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ nextIntervalTime + , cronRepeat = CronRepeatNever + , cronRateLimit = appSynchroniseLdapUsersInterval + , cronNotAfter = Left syncWithin + } + | otherwise + -> return () + + whenIsJust ((,) <$> appPruneUnreferencedFilesWithin <*> appJobCronInterval) $ \(within, cInterval) -> do + nextIntervals <- getNextIntervals within appPruneUnreferencedFilesInterval cInterval + forM_ nextIntervals $ \(nextEpoch, nextInterval, nextIntervalTime, numIntervals) -> do + tell $ HashMap.singleton + (JobCtlQueue JobPruneUnreferencedFiles + { jEpoch = fromInteger nextEpoch + , jNumIterations = fromInteger numIntervals + , jIteration = fromInteger nextInterval + } + ) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ nextIntervalTime + , cronRepeat = CronRepeatNever + , cronRateLimit = appPruneUnreferencedFilesInterval + , cronNotAfter = Left within + } + + whenIsJust ((,) <$> appStudyFeaturesRecacheRelevanceWithin <*> appJobCronInterval) $ \(within, cInterval) -> do + nextIntervals <- getNextIntervals within appStudyFeaturesRecacheRelevanceInterval cInterval + forM_ nextIntervals $ \(nextEpoch, nextInterval, nextIntervalTime, numIntervals) -> do + tell $ HashMap.singleton + (JobCtlQueue JobStudyFeaturesRecacheRelevance + { jEpoch = fromInteger nextEpoch + , jNumIterations = fromInteger numIntervals + , jIteration = fromInteger nextInterval + } + ) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ nextIntervalTime + , cronRepeat = CronRepeatNever + , cronRateLimit = appStudyFeaturesRecacheRelevanceInterval + , cronNotAfter = Left within + } + + let + sheetJobs (Entity nSheet Sheet{..}) = do + for_ (max <$> sheetVisibleFrom <*> sheetActiveFrom) $ \aFrom -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationSheetActive{..}) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aFrom + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) sheetActiveTo + } + for_ (max <$> sheetVisibleFrom <*> sheetHintFrom) $ \hFrom -> maybeT (return ()) $ do + guard $ maybe True (\aFrom -> abs (diffUTCTime aFrom hFrom) > 300) sheetActiveFrom + guardM $ or2M (return $ maybe True (\sFrom -> abs (diffUTCTime sFrom hFrom) > 300) sheetSolutionFrom) + (fmap not . lift . lift $ exists [SheetFileType ==. SheetSolution, SheetFileSheet ==. nSheet]) + guardM . lift . lift $ exists [SheetFileType ==. SheetHint, SheetFileSheet ==. nSheet] + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationSheetHint{..}) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ hFrom + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) sheetActiveTo + } + for_ (max <$> sheetVisibleFrom <*> sheetSolutionFrom) $ \sFrom -> maybeT (return ()) $ do + guard $ maybe True (\aFrom -> abs (diffUTCTime aFrom sFrom) > 300) sheetActiveFrom + guardM . lift . lift $ exists [SheetFileType ==. SheetSolution, SheetFileSheet ==. nSheet] + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationSheetSolution{..}) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ sFrom + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Left nominalDay + } + for_ sheetActiveTo $ \aTo -> do + whenIsJust (max aTo <$> sheetVisibleFrom) $ \aTo' -> do + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationSheetSoonInactive{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . maybe id max sheetActiveFrom $ addUTCTime (-nominalDay) aTo' + , cronRepeat = CronRepeatOnChange -- Allow repetition of the notification (if something changes), but wait at least an hour , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ . addUTCTime appNotificationExpiration $ max visibleFrom ts + , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ aTo + } + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationSheetInactive{..}) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aTo + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Left appNotificationExpiration + } + when sheetAutoDistribute $ + tell $ HashMap.singleton + (JobCtlQueue $ JobDistributeCorrections nSheet) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ aTo + , cronRepeat = CronRepeatNever + , cronRateLimit = 3600 -- Irrelevant due to `cronRepeat` + , cronNotAfter = Left nominalDay + } + + runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ sheetJobs + + let + correctorNotifications :: Map (UserId, SheetId) (Max UTCTime) -> WriterT (Crontab JobCtl) DB () + correctorNotifications = (tell .) . Map.foldMapWithKey $ \(nUser, nSheet) (Max time) -> HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationCorrectionsAssigned { nUser, nSheet } ) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay time + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Left appNotificationExpiration + } + + submissionsByCorrector :: Entity Submission -> Map (UserId, SheetId) (Max UTCTime) + submissionsByCorrector (Entity _ sub) + | Just ratingBy <- submissionRatingBy sub + , Just assigned <- submissionRatingAssigned sub + , not $ submissionRatingDone sub + = Map.singleton (ratingBy, submissionSheet sub) $ Max assigned + | otherwise + = Map.empty + + collateSubmissionsByCorrector acc entity = Map.unionWith (<>) acc $ submissionsByCorrector entity + correctorNotifications <=< runConduit $ + transPipe lift ( selectSource [ SubmissionRatingBy !=. Nothing, SubmissionRatingAssigned !=. Nothing ] [] + ) + .| C.fold collateSubmissionsByCorrector Map.empty + + + let + examSelect = E.selectSource . E.from $ \(exam `E.InnerJoin` course `E.InnerJoin` school) -> do + E.on $ school E.^. SchoolId E.==. course E.^. CourseSchool + E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse + return (exam, course, school) + examJobs (Entity nExam Exam{..}, _, Entity _ School{..}) = do + newestResult <- lift . E.select . E.from $ \examResult -> do + E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam + return . E.max_ $ examResult E.^. ExamResultLastChanged + + whenIsJust examVisibleFrom $ \visibleFrom -> do + case over (mapped . _Value) ((max `on` NTop) examFinished) newestResult of + [E.Value (NTop (Just ts))] -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamResult{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom ts + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ . addUTCTime appNotificationExpiration $ max visibleFrom ts + } + _other -> return () + + whenIsJust examRegisterFrom $ \registerFrom -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamRegistrationActive{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom registerFrom + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) examRegisterTo + } + whenIsJust ((,) <$> examRegisterFrom <*> examRegisterTo) $ \(registerFrom, registerTo) -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamRegistrationSoonInactive{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) registerTo + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ registerTo + } + whenIsJust ((,) <$> examRegisterFrom <*> examDeregisterUntil) $ \(registerFrom, deregisterUntil) -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamDeregistrationSoonInactive{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) deregisterUntil + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ deregisterUntil + } + + let closeTime = case (examClosed, examFinished) of + (mClose, Just finish) + | isn't _ExamCloseSeparate schoolExamCloseMode -> Just $ maybe id min mClose finish + (Just close, _) + | is _ExamCloseSeparate schoolExamCloseMode -> Just close + _other -> Nothing + + case closeTime of + Just close -> do + -- If an exam that was previously under `ExamCloseSeparate` rules transitions to `ExamCloseOnFinish`, it might suddenly have been closed an arbitrary time ago + -- If `cronNotAfter` was only `appNotificationExpiration` in that case, no notification might ever be sent + -- That's probably fine. + + changedResults <- lift . E.select . E.from $ \examResult -> do + E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam + E.&&. examResult E.^. ExamResultLastChanged E.>. E.val close + return $ examResult E.^. ExamResultId + + case newestResult of + [E.Value (Just lastChange)] + | not $ null changedResults + -> tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExamResultsChanged{ nExamResults = Set.fromList $ map E.unValue changedResults }) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay lastChange + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Left appNotificationExpiration + } + _other -> return () + + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExamResults{..}) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ close + , cronRepeat = CronRepeatNever + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Left appNotificationExpiration + } + Nothing -> return () + in runConduit $ transPipe lift examSelect .| C.mapM_ examJobs + + + let + externalExamJobs nExternalExam = do + newestResult <- lift . E.select . E.from $ \externalExamResult -> do + E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. E.val nExternalExam + return . E.max_ $ externalExamResult E.^. ExternalExamResultLastChanged + + case newestResult of + [E.Value (Just lastChange)] -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExternalExamResults{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay lastChange + , cronRepeat = CronRepeatOnChange + , cronRateLimit = nominalDay + , cronNotAfter = Left appNotificationExpiration } _other -> return () - whenIsJust examRegisterFrom $ \registerFrom -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamRegistrationActive{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ max visibleFrom registerFrom - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Left appNotificationExpiration) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) examRegisterTo - } - whenIsJust ((,) <$> examRegisterFrom <*> examRegisterTo) $ \(registerFrom, registerTo) -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamRegistrationSoonInactive{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) registerTo - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ registerTo - } - whenIsJust ((,) <$> examRegisterFrom <*> examDeregisterUntil) $ \(registerFrom, deregisterUntil) -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamDeregistrationSoonInactive{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ . max visibleFrom . max registerFrom $ addUTCTime (-nominalDay) deregisterUntil - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Right . CronTimestamp $ utcToLocalTimeTZ appTZ deregisterUntil - } - - let closeTime = case (examClosed, examFinished) of - (mClose, Just finish) - | isn't _ExamCloseSeparate schoolExamCloseMode -> Just $ maybe id min mClose finish - (Just close, _) - | is _ExamCloseSeparate schoolExamCloseMode -> Just close - _other -> Nothing - - case closeTime of - Just close -> do - -- If an exam that was previously under `ExamCloseSeparate` rules transitions to `ExamCloseOnFinish`, it might suddenly have been closed an arbitrary time ago - -- If `cronNotAfter` was only `appNotificationExpiration` in that case, no notification might ever be sent - -- That's probably fine. - - changedResults <- lift . E.select . E.from $ \examResult -> do - E.where_ $ examResult E.^. ExamResultExam E.==. E.val nExam - E.&&. examResult E.^. ExamResultLastChanged E.>. E.val close - return $ examResult E.^. ExamResultId - - case newestResult of - [E.Value (Just lastChange)] - | not $ null changedResults - -> tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExamResultsChanged{ nExamResults = Set.fromList $ map E.unValue changedResults }) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay lastChange - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Left appNotificationExpiration - } - _other -> return () - - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExamResults{..}) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ close - , cronRepeat = CronRepeatNever - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Left appNotificationExpiration - } - Nothing -> return () - in runConduit $ transPipe lift examSelect .| C.mapM_ examJobs + runConduit $ transPipe lift (selectKeys [] []) .| C.mapM_ externalExamJobs - let - externalExamJobs nExternalExam = do - newestResult <- lift . E.select . E.from $ \externalExamResult -> do - E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. E.val nExternalExam - return . E.max_ $ externalExamResult E.^. ExternalExamResultLastChanged + allocations <- lift $ selectList [] [] - case newestResult of - [E.Value (Just lastChange)] -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationExamOfficeExternalExamResults{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay lastChange - , cronRepeat = CronRepeatOnChange - , cronRateLimit = nominalDay - , cronNotAfter = Left appNotificationExpiration - } - _other -> return () + let + allocationTimes :: EntityField Allocation (Maybe UTCTime) -> MergeHashMap UTCTime [Entity Allocation] + allocationTimes aField = flip foldMap allocations $ \allocEnt -> case allocEnt ^. fieldLens aField of + Nothing -> mempty + Just t -> _MergeHashMap # HashMap.singleton t (pure allocEnt) - runConduit $ transPipe lift (selectKeys [] []) .| C.mapM_ externalExamJobs + forM_ allocations $ \(Entity nAllocation _) -> do + doneSince <- lift $ fmap (E.unValue <=< listToMaybe) . E.select . E.from $ \participant -> do + E.where_ $ participant E.^. CourseParticipantAllocated E.==. E.just (E.val nAllocation) + return . E.max_ $ participant E.^. CourseParticipantRegistration + whenIsJust doneSince $ \doneSince' -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationAllocationResults{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay doneSince' + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ . addUTCTime appNotificationCollateDelay $ addUTCTime appNotificationExpiration doneSince' + } - allocations <- lift $ selectList [] [] - - let - allocationTimes :: EntityField Allocation (Maybe UTCTime) -> MergeHashMap UTCTime [Entity Allocation] - allocationTimes aField = flip foldMap allocations $ \allocEnt -> case allocEnt ^. fieldLens aField of - Nothing -> mempty - Just t -> _MergeHashMap # HashMap.singleton t (pure allocEnt) - - forM_ allocations $ \(Entity nAllocation _) -> do - doneSince <- lift $ fmap (E.unValue <=< listToMaybe) . E.select . E.from $ \participant -> do - E.where_ $ participant E.^. CourseParticipantAllocated E.==. E.just (E.val nAllocation) - return . E.max_ $ participant E.^. CourseParticipantRegistration - - whenIsJust doneSince $ \doneSince' -> + iforM_ (allocationTimes AllocationStaffRegisterFrom) $ \staffRegisterFrom allocs -> tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationAllocationResults{..}) + (JobCtlQueue $ JobQueueNotification NotificationAllocationStaffRegister{ nAllocations = setOf (folded . _entityKey) allocs }) Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appNotificationCollateDelay doneSince' + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ staffRegisterFrom , cronRepeat = CronRepeatOnChange , cronRateLimit = appNotificationRateLimit - , cronNotAfter = Right . CronTimestamp . utcToLocalTimeTZ appTZ . addUTCTime appNotificationCollateDelay $ addUTCTime appNotificationExpiration doneSince' + , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffRegisterTo . to NTop . filtered (> NTop (Just staffRegisterFrom))) allocs + } + iforM_ (allocationTimes AllocationRegisterFrom) $ \registerFrom allocs -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationAllocationRegister{ nAllocations = setOf (folded . _entityKey) allocs }) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ registerFrom + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationRegisterTo . to NTop . filtered (> NTop (Just registerFrom))) allocs + } + iforM_ (allocationTimes AllocationStaffAllocationFrom) $ \staffAllocationFrom allocs -> + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationAllocationAllocation{ nAllocations = setOf (folded . _entityKey) allocs }) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ staffAllocationFrom + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffAllocationTo . to NTop . filtered (> NTop (Just staffAllocationFrom))) allocs + } + iforM_ (allocationTimes AllocationRegisterTo) $ \registerTo allocs' -> do + let allocs = flip filter allocs' $ \(Entity _ Allocation{..}) -> maybe True (> registerTo) allocationStaffAllocationTo + tell $ HashMap.singleton + (JobCtlQueue $ JobQueueNotification NotificationAllocationUnratedApplications{ nAllocations = setOf (folded . _entityKey) allocs }) + Cron + { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ registerTo + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appNotificationRateLimit + , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffAllocationTo . to NTop . filtered (> NTop (Just registerTo))) allocs } - let externalApiJobs (Entity jExternalApi ExternalApi{..}) = - tell $ HashMap.singleton - (JobCtlQueue JobExternalApiExpire{..}) - Cron - { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appExternalApisExpiry externalApiLastAlive - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appExternalApisExpiry - , cronNotAfter = Right CronNotScheduled - } - - runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ externalApiJobs + hasRelevanceUncached <- lift $ exists [StudyFeaturesRelevanceCached ==. Nothing] + when hasRelevanceUncached . tell $ HashMap.singleton + (JobCtlQueue JobStudyFeaturesCacheRelevance) + Cron + { cronInitial = CronAsap + , cronRepeat = CronRepeatOnChange + , cronRateLimit = nominalDay + , cronNotAfter = Right CronNotScheduled + } - iforM_ (allocationTimes AllocationStaffRegisterFrom) $ \staffRegisterFrom allocs -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationAllocationStaffRegister{ nAllocations = setOf (folded . _entityKey) allocs }) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ staffRegisterFrom - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffRegisterTo . to NTop . filtered (> NTop (Just staffRegisterFrom))) allocs - } - iforM_ (allocationTimes AllocationRegisterFrom) $ \registerFrom allocs -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationAllocationRegister{ nAllocations = setOf (folded . _entityKey) allocs }) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ registerFrom - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationRegisterTo . to NTop . filtered (> NTop (Just registerFrom))) allocs - } - iforM_ (allocationTimes AllocationStaffAllocationFrom) $ \staffAllocationFrom allocs -> - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationAllocationAllocation{ nAllocations = setOf (folded . _entityKey) allocs }) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ staffAllocationFrom - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffAllocationTo . to NTop . filtered (> NTop (Just staffAllocationFrom))) allocs - } - iforM (allocationTimes AllocationRegisterTo) $ \registerTo allocs' -> do - let allocs = flip filter allocs' $ \(Entity _ Allocation{..}) -> maybe True (> registerTo) allocationStaffAllocationTo - tell $ HashMap.singleton - (JobCtlQueue $ JobQueueNotification NotificationAllocationUnratedApplications{ nAllocations = setOf (folded . _entityKey) allocs }) - Cron - { cronInitial = CronTimestamp $ utcToLocalTimeTZ appTZ registerTo - , cronRepeat = CronRepeatOnChange - , cronRateLimit = appNotificationRateLimit - , cronNotAfter = maybe (Right CronNotScheduled) (Right . CronTimestamp . utcToLocalTimeTZ appTZ) $ nBot =<< minimumOf (folded . _entityVal . _allocationStaffAllocationTo . to NTop . filtered (> NTop (Just registerTo))) allocs - } + let externalApiJobs (Entity jExternalApi ExternalApi{..}) = + tell $ HashMap.singleton + (JobCtlQueue JobExternalApiExpire{..}) + Cron + { cronInitial = CronTimestamp . utcToLocalTimeTZ appTZ $ addUTCTime appExternalApisExpiry externalApiLastAlive + , cronRepeat = CronRepeatOnChange + , cronRateLimit = appExternalApisExpiry + , cronNotAfter = Right CronNotScheduled + } + + runConduit $ transPipe lift (selectSource [] []) .| C.mapM_ externalApiJobs diff --git a/src/Jobs/Handler/Files.hs b/src/Jobs/Handler/Files.hs index e100c571d..0415a1083 100644 --- a/src/Jobs/Handler/Files.hs +++ b/src/Jobs/Handler/Files.hs @@ -23,14 +23,8 @@ import qualified Network.Minio as Minio import Crypto.Hash (hashDigestSize, digestFromByteString) -import Data.List ((!!), unfoldr, maximumBy, init, genericLength) -import qualified Data.ByteString as ByteString -import Data.Bits (Bits(shiftR)) - import qualified Data.Map.Strict as Map -import Control.Monad.Random.Lazy (evalRand, mkStdGen) -import System.Random.Shuffle (shuffleM) import System.IO.Unsafe import Handler.Utils.Files (sourceFileDB) @@ -44,6 +38,8 @@ import qualified Data.Sequence as Seq import Jobs.Queue (YesodJobDB) +import Jobs.Handler.Intervals.Utils + dispatchJobPruneSessionFiles :: JobHandler UniWorX dispatchJobPruneSessionFiles = JobHandlerAtomicWithFinalizer act fin @@ -73,9 +69,7 @@ fileReferences (E.just -> fHash) ] workflowFileReferences :: MonadResource m => ConduitT () FileContentReference (SqlPersistT m) () -workflowFileReferences = mconcat [ E.selectSource (E.from $ pure . (E.^. WorkflowDefinitionGraph)) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue) - , E.selectSource (E.from $ pure . (E.^. WorkflowInstanceGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue) - , E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowGraph )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue) +workflowFileReferences = mconcat [ E.selectSource (E.from $ pure . (E.^. SharedWorkflowGraphGraph)) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue) , E.selectSource (E.from $ pure . (E.^. WorkflowWorkflowState )) .| awaitForever (mapMOf_ (typesCustom @WorkflowChildren . _fileReferenceContent . _Just) yield . E.unValue) ] @@ -161,52 +155,10 @@ dispatchJobPruneUnreferencedFiles numIterations epoch iteration = JobHandlerAtom ( Unwrapped FileContentChunkReference ~ Digest h ) => Integer chunkHashBytes = fromIntegral (hashDigestSize (error "hashDigestSize inspected argument" :: h)) - chunkHashBits = chunkHashBytes * 8 - base :: Integer - base = 2 ^ chunkHashBits - intervals :: [Integer] - -- | Exclusive upper bounds - intervals - | numIterations <= 0 = pure base - | otherwise = go protoIntervals ^.. folded . _1 - where - go [] = [] - go ints - | maximumOf (folded . _1) ints == Just base = ints - | otherwise = go $ lts ++ over _1 succ (over _2 (subtract $ toInteger numIterations) closest) : map (over _1 succ) gts - where - closest = maximumBy (comparing $ view _2) ints - (lts, geqs) = partition (((>) `on` view _1) closest) ints - gts = filter (((<) `on` view _1) closest) geqs - -- | Exclusive upper bounds - protoIntervals :: [(Integer, Integer)] - protoIntervals = [ over _1 (i *) $ base `divMod` toInteger numIterations - | i <- [1 .. toInteger numIterations] - ] - intervalsDgsts' = zipWith (curry . over both $ toDigest <=< assertM' (> 0)) (0 : init intervals) intervals - - toDigest :: Integer -> Maybe FileContentChunkReference - toDigest = fmap (review _Wrapped) . digestFromByteString . pad . ByteString.pack . reverse . unfoldr step - where step i - | i <= 0 = Nothing - | otherwise = Just (fromIntegral i, i `shiftR` 8) - pad bs - | toInteger (ByteString.length bs) >= chunkHashBytes = bs - | otherwise = pad $ ByteString.cons 0 bs - - intervalsDgsts <- atomically $ do - cachedDgsts <- readTVar pruneUnreferencedFilesIntervalsCache - case Map.lookup numIterations cachedDgsts of - Just c -> return c - Nothing -> do - modifyTVar' pruneUnreferencedFilesIntervalsCache $ force . Map.insert numIterations intervalsDgsts' - return intervalsDgsts' + (minBoundDgst, maxBoundDgst) <- currentIntervalCached pruneUnreferencedFilesIntervalsCache chunkHashBytes (fmap (review _Wrapped) . digestFromByteString) numIterations epoch iteration let - permIntervalsDgsts = shuffleM intervalsDgsts `evalRand` mkStdGen (hash epoch) - - (minBoundDgst, maxBoundDgst) = permIntervalsDgsts !! fromIntegral (toInteger iteration `mod` genericLength permIntervalsDgsts) chunkIdFilter :: E.SqlExpr (E.Value FileContentChunkReference) -> E.SqlExpr (E.Value Bool) chunkIdFilter cRef = E.and $ catMaybes [ minBoundDgst <&> \b -> cRef E.>=. E.val b diff --git a/src/Jobs/Handler/Intervals/Utils.hs b/src/Jobs/Handler/Intervals/Utils.hs new file mode 100644 index 000000000..fb487b062 --- /dev/null +++ b/src/Jobs/Handler/Intervals/Utils.hs @@ -0,0 +1,77 @@ +module Jobs.Handler.Intervals.Utils + ( mkIntervals, mkIntervalsCached + , getCurrentInterval + , currentIntervalCached + ) where + +import Import hiding (init, maximumBy, cached) + +import Control.Monad.Random.Lazy (evalRand, mkStdGen) +import System.Random.Shuffle (shuffleM) +import Data.List ((!!), unfoldr, maximumBy, init, genericLength) +import qualified Data.ByteString as ByteString +import Data.Bits (Bits(shiftR)) + +import qualified Data.Map.Strict as Map + + +mkIntervals :: forall a. Integer -> (ByteString -> Maybe a) -> Natural -> [(Maybe a, Maybe a)] +mkIntervals bytes fromBS numIterations = zip (Nothing : init intervals') intervals' + where + bits = bytes * 8 + base :: Integer + base = 2 ^ bits + + -- | Exclusive upper bounds + intervals + | numIterations <= 0 = pure base + | otherwise = go protoIntervals ^.. folded . _1 + where + go [] = [] + go ints + | maximumOf (folded . _1) ints == Just base = ints + | otherwise = go $ lts ++ over _1 succ (over _2 (subtract $ toInteger numIterations) closest) : map (over _1 succ) gts + where + closest = maximumBy (comparing $ view _2) ints + (lts, geqs) = partition (((>) `on` view _1) closest) ints + gts = filter (((<) `on` view _1) closest) geqs + -- | Exclusive upper bounds + protoIntervals :: [(Integer, Integer)] + protoIntervals = [ over _1 (i *) $ base `divMod` toInteger numIterations + | i <- [1 .. toInteger numIterations] + ] + + intervals' = map (fromBS' <=< assertM' (> 0)) intervals + + fromBS' :: Integer -> Maybe a + fromBS' = fromBS . pad . ByteString.pack . reverse . unfoldr step + where step i + | i <= 0 || i >= base = Nothing + | otherwise = Just (fromIntegral i, i `shiftR` 8) + pad bs + | toInteger (ByteString.length bs) >= bytes = bs + | otherwise = pad $ ByteString.cons 0 bs + +getCurrentInterval :: forall a. Natural -> Natural -> [a] -> a +getCurrentInterval epoch iteration intervals = permIntervals !! fromIntegral (toInteger iteration `mod` genericLength permIntervals) + where permIntervals = shuffleM intervals `evalRand` mkStdGen (hash epoch) + + +mkIntervalsCached :: forall m a. (NFData a, MonadIO m) + => TVar (Map Natural [(Maybe a, Maybe a)]) + -> Integer -> (ByteString -> Maybe a) -> Natural -> m [(Maybe a, Maybe a)] +mkIntervalsCached cacheTVar bytes fromBS numIterations = atomically $ do + cached <- readTVar cacheTVar + case Map.lookup numIterations cached of + Just c -> return c + Nothing -> do + modifyTVar' cacheTVar $ force . Map.insert numIterations intervals' + return intervals' + where intervals' = mkIntervals bytes fromBS numIterations + +currentIntervalCached :: forall m a. (NFData a, MonadIO m) + => TVar (Map Natural [(Maybe a, Maybe a)]) + -> Integer -> (ByteString -> Maybe a) + -> Natural -> Natural -> Natural -> m (Maybe a, Maybe a) +currentIntervalCached cacheTVar bytes fromBS numIterations epoch iteration + = getCurrentInterval epoch iteration <$> mkIntervalsCached cacheTVar bytes fromBS numIterations diff --git a/src/Jobs/Handler/StudyFeatures.hs b/src/Jobs/Handler/StudyFeatures.hs new file mode 100644 index 000000000..4ebf56b04 --- /dev/null +++ b/src/Jobs/Handler/StudyFeatures.hs @@ -0,0 +1,45 @@ +module Jobs.Handler.StudyFeatures + ( dispatchJobStudyFeaturesCacheRelevance + , dispatchJobStudyFeaturesRecacheRelevance + ) where + +import Import + +import Handler.Utils.StudyFeatures + +import qualified Database.Esqueleto as E +import qualified Database.Esqueleto.Utils as E + +import qualified Data.Map.Strict as Map + +import System.IO.Unsafe + +import Jobs.Handler.Intervals.Utils + +import qualified Data.UUID as UUID + + +dispatchJobStudyFeaturesCacheRelevance :: JobHandler UniWorX +dispatchJobStudyFeaturesCacheRelevance = JobHandlerAtomic $ + cacheStudyFeatureRelevance $ \studyFeatures -> E.isNothing $ studyFeatures E.^. StudyFeaturesRelevanceCached + +{-# NOINLINE studyFeaturesRecacheRelevanceIntervalsCache #-} +studyFeaturesRecacheRelevanceIntervalsCache :: TVar (Map Natural [(Maybe UUID, Maybe UUID)]) +studyFeaturesRecacheRelevanceIntervalsCache = unsafePerformIO $ newTVarIO Map.empty + + +dispatchJobStudyFeaturesRecacheRelevance :: Natural -> Natural -> Natural -> JobHandler UniWorX +dispatchJobStudyFeaturesRecacheRelevance numIterations epoch iteration = JobHandlerAtomic $ do + (minBoundUUID, maxBoundUUID) <- currentIntervalCached studyFeaturesRecacheRelevanceIntervalsCache 16 (UUID.fromByteString . fromStrict) numIterations epoch iteration + + let + uuidFilter :: E.SqlExpr (E.Value (Maybe UUID)) -> E.SqlExpr (E.Value Bool) + uuidFilter cRef = E.and $ catMaybes + [ pure $ E.isJust cRef + , minBoundUUID <&> \b -> cRef E.>=. E.justVal b + , maxBoundUUID <&> \b -> cRef E.<. E.justVal b + ] + + $logDebugS "StudyFeaturesRecacheRelevance" . tshow $ (minBoundUUID, maxBoundUUID) + + cacheStudyFeatureRelevance $ \studyFeatures -> uuidFilter $ studyFeatures E.^. StudyFeaturesRelevanceCached diff --git a/src/Jobs/Offload.hs b/src/Jobs/Offload.hs new file mode 100644 index 000000000..2991e03f2 --- /dev/null +++ b/src/Jobs/Offload.hs @@ -0,0 +1,70 @@ +module Jobs.Offload + ( mkJobOffloadHandler + ) where + +import Import hiding (bracket, js) +import Jobs.Types +import Jobs.Queue + +import qualified Database.PostgreSQL.Simple as PG +import qualified Database.PostgreSQL.Simple.Types as PG +import qualified Database.PostgreSQL.Simple.Notification as PG + +import Database.Persist.Postgresql (PostgresConf, pgConnStr) + +import Data.Text.Encoding (decodeUtf8') + +import UnliftIO.Exception (bracket) + + +jobOffloadChannel :: Text +jobOffloadChannel = "job-offload" + +mkJobOffloadHandler :: forall m. + ( MonadResource m + , MonadUnliftIO m + , MonadThrow m, MonadReader UniWorX m + , MonadLogger m + ) + => PostgresConf -> JobMode + -> Maybe (m JobOffloadHandler) +mkJobOffloadHandler dbConf jMode + | is _JobsLocal jMode, hasn't (_jobsAcceptOffload . only True) jMode = Nothing + | otherwise = Just $ do + jobOffloadOutgoing <- newTVarIO mempty + jobOffloadHandler <- allocateAsync . bracket (liftIO . PG.connectPostgreSQL $ pgConnStr dbConf) (liftIO . PG.close) $ \pgConn -> do + myPid <- liftIO $ PG.getBackendPID pgConn + let shouldListen = has (_jobsAcceptOffload . only True) jMode + + when shouldListen $ + void . liftIO $ PG.execute pgConn "LISTEN ?" (PG.Only $ PG.Identifier jobOffloadChannel) + + foreverBreak $ \(($ ()) -> terminate) -> do + UniWorX{appJobState} <- ask + shouldTerminate <- atomically $ readTMVar appJobState >>= fmap not . isEmptyTMVar . jobShutdown + when shouldTerminate terminate + + let + getInput = do + n@PG.Notification{..} <- liftIO $ PG.getNotification pgConn + if | notificationPid == myPid || notificationChannel /= encodeUtf8 jobOffloadChannel -> getInput + | otherwise -> return n + getOutput = atomically $ do + jQueue <- readTVar jobOffloadOutgoing + case jQueue of + j :< js -> j <$ writeTVar jobOffloadOutgoing js + _other -> mzero + + io <- lift $ if + | shouldListen -> getInput `race` getOutput + | otherwise -> Right <$> getOutput + + case io of + Left PG.Notification{..} + | Just jId <- fromPathPiece =<< either (const Nothing) Just (decodeUtf8' notificationData) + -> writeJobCtl $ JobCtlPerform jId + | otherwise + -> $logErrorS "JobOffloadHandler" $ "Could not parse incoming notification data: " <> tshow notificationData + Right jId -> void . liftIO $ PG.execute pgConn "NOTIFY ?, ?" (PG.Identifier jobOffloadChannel, encodeUtf8 $ toPathPiece jId) + + return JobOffloadHandler{..} diff --git a/src/Jobs/Types.hs b/src/Jobs/Types.hs index c640857c2..7d2341572 100644 --- a/src/Jobs/Types.hs +++ b/src/Jobs/Types.hs @@ -9,7 +9,7 @@ module Jobs.Types , classifyJobCtl , YesodJobDB , JobHandler(..), _JobHandlerAtomic, _JobHandlerException - , JobContext(..) + , JobOffloadHandler(..), JobContext(..) , JobState(..), _jobWorkers, _jobWorkerName, _jobContext, _jobPoolManager, _jobCron, _jobShutdown, _jobCurrentCrontab , jobWorkerNames , JobWorkerState(..), _jobWorkerJobCtl, _jobWorkerJob @@ -99,6 +99,11 @@ data Job | JobRechunkFiles | JobDetectMissingFiles | JobPruneOldSentMails + | JobStudyFeaturesCacheRelevance + | JobStudyFeaturesRecacheRelevance { jNumIterations + , jEpoch + , jIteration :: Natural + } deriving (Eq, Ord, Show, Read, Generic, Typeable) data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId } @@ -240,10 +245,16 @@ showWorkerId = tshow . hashUnique . jobWorkerUnique newWorkerId :: MonadIO m => m JobWorkerId newWorkerId = JobWorkerId <$> liftIO newUnique +data JobOffloadHandler = JobOffloadHandler + { jobOffloadHandler :: Async () + , jobOffloadOutgoing :: TVar (Seq QueuedJobId) + } + data JobContext = JobContext { jobCrontab :: TVar (Crontab JobCtl) , jobConfirm :: TVar (HashMap JobCtl (NonEmpty (TMVar (Maybe SomeException)))) , jobHeldLocks :: TVar (Set QueuedJobId) + , jobOffload :: TMVar JobOffloadHandler } diff --git a/src/Model/Migration/Definitions.hs b/src/Model/Migration/Definitions.hs index 8fc1ae10d..2fe78d785 100644 --- a/src/Model/Migration/Definitions.hs +++ b/src/Model/Migration/Definitions.hs @@ -47,6 +47,8 @@ import Data.Time.Format import qualified Data.Time.Zones as TZ +import Utils.Workflow + data ManualMigration = Migration20180813SimplifyUserTheme @@ -97,6 +99,8 @@ data ManualMigration | Migration20201106StoredMarkup | Migration20201119RoomTypes | Migration20210115ExamPartsFrom + | Migration20210201SharedWorkflowGraphs + | Migration20210208StudyFeaturesRelevanceCachedUUIDs deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) deriving anyclass (Universe, Finite) @@ -135,6 +139,7 @@ migrateManual = do , ("file_content_entry_chunk_hash", "CREATE INDEX file_content_entry_chunk_hash ON \"file_content_entry\" (chunk_hash)" ) , ("sent_mail_bounce_secret", "CREATE INDEX sent_mail_bounce_secret ON \"sent_mail\" (bounce_secret) WHERE bounce_secret IS NOT NULL") , ("sent_mail_recipient", "CREATE INDEX sent_mail_recipient ON \"sent_mail\" (recipient) WHERE recipient IS NOT NULL") + , ("study_features_relevance_cached", "CREATE INDEX study_features_relevance_cached ON \"study_features\" (relevance_cached)") ] where addIndex :: Text -> Sql -> Migration @@ -968,6 +973,73 @@ customMigrations = mapF $ \case migrateExam _ = return () in runConduit $ getExam .| C.mapM_ migrateExam + Migration20210201SharedWorkflowGraphs -> do + unlessM (tableExists "shared_workflow_graph") + [executeQQ|CREATE TABLE "shared_workflow_graph" ("hash" bytea primary key, "graph" jsonb not null)|] + + whenM (tableExists "workflow_definition") $ do + [executeQQ|ALTER TABLE "workflow_definition" ADD COLUMN "graph_id" bytea references shared_workflow_graph(hash)|] + let getDefinitions = [queryQQ|SELECT "id", "graph" FROM "workflow_definition"|] + migrateDefinition [ fromPersistValue -> Right (wdId :: WorkflowDefinitionId), fromPersistValue -> Right (graph :: DBWorkflowGraph) ] = do + swgId <- insertSharedWorkflowGraph graph + [executeQQ|UPDATE "workflow_definition" SET "graph_id" = #{swgId} WHERE "id" = #{wdId}|] + migrateDefinition _ = return () + in runConduit $ getDefinitions .| C.mapM_ migrateDefinition + + [executeQQ| + ALTER TABLE "workflow_definition" DROP COLUMN "graph"; + ALTER TABLE "workflow_definition" ALTER COLUMN "graph_id" SET not null; + ALTER TABLE "workflow_definition" RENAME COLUMN "graph_id" TO "graph"; + |] + + whenM (tableExists "workflow_instance") $ do + [executeQQ|ALTER TABLE "workflow_instance" ADD COLUMN "graph_id" bytea references shared_workflow_graph(hash)|] + let getInstances = [queryQQ|SELECT "id", "graph" FROM "workflow_instance"|] + migrateInstance [ fromPersistValue -> Right (wiId :: WorkflowInstanceId), fromPersistValue -> Right (graph :: DBWorkflowGraph) ] = do + swgId <- insertSharedWorkflowGraph graph + [executeQQ|UPDATE "workflow_instance" SET "graph_id" = #{swgId} WHERE "id" = #{wiId}|] + migrateInstance _ = return () + in runConduit $ getInstances .| C.mapM_ migrateInstance + + [executeQQ| + ALTER TABLE "workflow_instance" DROP COLUMN "graph"; + ALTER TABLE "workflow_instance" ALTER COLUMN "graph_id" SET not null; + ALTER TABLE "workflow_instance" RENAME COLUMN "graph_id" TO "graph"; + |] + + whenM (tableExists "workflow_workflow") $ do + [executeQQ|ALTER TABLE "workflow_workflow" ADD COLUMN "graph_id" bytea references shared_workflow_graph(hash)|] + let getWorkflows = [queryQQ|SELECT "id", "graph" FROM "workflow_workflow"|] + migrateWorkflow [ fromPersistValue -> Right (wwId :: WorkflowWorkflowId), fromPersistValue -> Right (graph :: DBWorkflowGraph) ] = do + swgId <- insertSharedWorkflowGraph graph + [executeQQ|UPDATE "workflow_workflow" SET "graph_id" = #{swgId} WHERE "id" = #{wwId}|] + migrateWorkflow _ = return () + in runConduit $ getWorkflows .| C.mapM_ migrateWorkflow + + [executeQQ| + ALTER TABLE "workflow_workflow" DROP COLUMN "graph"; + ALTER TABLE "workflow_workflow" ALTER COLUMN "graph_id" SET not null; + ALTER TABLE "workflow_workflow" RENAME COLUMN "graph_id" TO "graph"; + |] + + Migration20210208StudyFeaturesRelevanceCachedUUIDs -> + whenM (tableExists "study_features") $ do + [executeQQ| + ALTER TABLE "study_features" ADD COLUMN "relevance_cached_uuid" uuid + |] + + let getStudyFeatures = [queryQQ|SELECT "id" FROM "study_features" WHERE relevance_cached|] + migrateStudyFeatures genUUID lift' [ fromPersistValue -> Right (sfId :: StudyFeaturesId) ] = do + uuid <- genUUID + lift' [executeQQ|UPDATE "study_features" SET "relevance_cached_uuid" = #{uuid} WHERE "id" = #{sfId}|] + migrateStudyFeatures _ _ _ = return () + in runConduit $ getStudyFeatures .| randUUIDC (\genUUID lift' -> C.mapM_ $ migrateStudyFeatures genUUID lift') + + [executeQQ| + ALTER TABLE "study_features" DROP COLUMN "relevance_cached"; + ALTER TABLE "study_features" RENAME COLUMN "relevance_cached_uuid" TO "relevance_cached"; + |] + tableExists :: MonadIO m => Text -> ReaderT SqlBackend m Bool tableExists table = do diff --git a/src/Model/Types/Exam.hs b/src/Model/Types/Exam.hs index 76588cb08..1a9cb0ef4 100644 --- a/src/Model/Types/Exam.hs +++ b/src/Model/Types/Exam.hs @@ -326,7 +326,10 @@ derivePersistFieldJSON ''ExamGradingRule newtype ExamPassed = ExamPassed { examPassed :: Bool } deriving (Read, Show, Generic, Typeable) - deriving newtype (Eq, Ord, Enum, Bounded, PersistField, PersistFieldSql) + deriving newtype (Eq, Ord, Enum, Bounded, PersistField) + +instance PersistFieldSql ExamPassed where + sqlType _ = sqlType $ Proxy @Bool deriveFinite ''ExamPassed finitePathPiece ''ExamPassed ["failed", "passed"] diff --git a/src/Model/Types/File.hs b/src/Model/Types/File.hs index 5beb0ef55..be8922eff 100644 --- a/src/Model/Types/File.hs +++ b/src/Model/Types/File.hs @@ -19,7 +19,7 @@ module Model.Types.File import Import.NoModel -import Database.Persist.Sql (PersistFieldSql) +import Database.Persist.Sql (PersistFieldSql(..)) import Web.HttpApiData (ToHttpApiData, FromHttpApiData) import Data.ByteArray (ByteArrayAccess) @@ -42,24 +42,30 @@ import qualified Data.Map as Map newtype FileContentChunkReference = FileContentChunkReference (Digest SHA3_512) deriving (Eq, Ord, Read, Show, Lift, Generic, Typeable) - deriving newtype ( PersistField, PersistFieldSql + deriving newtype ( PersistField , PathPiece, ToHttpApiData, FromHttpApiData, ToJSON, FromJSON , Hashable, NFData , ByteArrayAccess , Binary ) +instance PersistFieldSql FileContentChunkReference where + sqlType _ = sqlType $ Proxy @(Digest SHA3_512) + makeWrapped ''FileContentChunkReference newtype FileContentReference = FileContentReference (Digest SHA3_512) deriving (Eq, Ord, Read, Show, Lift, Generic, Typeable) - deriving newtype ( PersistField, PersistFieldSql + deriving newtype ( PersistField , PathPiece, ToHttpApiData, FromHttpApiData, ToJSON, FromJSON , Hashable, NFData , ByteArrayAccess , Binary ) +instance PersistFieldSql FileContentReference where + sqlType _ = sqlType $ Proxy @(Digest SHA3_512) + makeWrapped ''FileContentReference diff --git a/src/Model/Types/Mail.hs b/src/Model/Types/Mail.hs index be993e10b..a6e96ad4f 100644 --- a/src/Model/Types/Mail.hs +++ b/src/Model/Types/Mail.hs @@ -19,7 +19,7 @@ import qualified Data.HashSet as HashSet import qualified Data.HashMap.Strict as HashMap import Crypto.Hash (digestFromByteString, SHAKE128) -import Database.Persist.Sql (PersistFieldSql) +import Database.Persist.Sql (PersistFieldSql(..)) import Data.ByteArray (ByteArrayAccess) import qualified Data.ByteArray as BA @@ -102,11 +102,14 @@ derivePersistFieldJSON ''NotificationSettings newtype BounceSecret = BounceSecret (Digest (SHAKE128 64)) deriving (Eq, Ord, Read, Show, Lift, Generic, Typeable) - deriving newtype ( PersistField, PersistFieldSql + deriving newtype ( PersistField , Hashable, NFData , ByteArrayAccess ) +instance PersistFieldSql BounceSecret where + sqlType _ = sqlType $ Proxy @(Digest (SHAKE128 64)) + instance PathPiece BounceSecret where toPathPiece = CI.foldCase . encodeBase32Unpadded . BA.convert fromPathPiece = fmap BounceSecret . digestFromByteString <=< either (const Nothing) Just . decodeBase32Unpadded . encodeUtf8 @@ -120,10 +123,13 @@ derivePersistFieldJSON ''MailContent newtype MailContentReference = MailContentReference (Digest SHA3_512) deriving (Eq, Ord, Read, Show, Lift, Generic, Typeable) - deriving newtype ( PersistField, PersistFieldSql + deriving newtype ( PersistField , PathPiece, ToHttpApiData, FromHttpApiData, ToJSON, FromJSON , Hashable, NFData , ByteArrayAccess ) +instance PersistFieldSql MailContentReference where + sqlType _ = sqlType $ Proxy @(Digest SHA3_512) + derivePersistFieldJSON ''MailHeaders diff --git a/src/Model/Types/Submission.hs b/src/Model/Types/Submission.hs index e46458607..737017dc8 100644 --- a/src/Model/Types/Submission.hs +++ b/src/Model/Types/Submission.hs @@ -52,9 +52,12 @@ type PseudonymWord = CI Text newtype Pseudonym = Pseudonym Word24 deriving (Eq, Ord, Read, Show, Generic, Data) deriving newtype ( Bounded, Enum, Integral, Num, Real, Ix - , PersistField, PersistFieldSql, Random + , PersistField, Random ) +instance PersistFieldSql Pseudonym where + sqlType _ = sqlType $ Proxy @Word24 + instance FromJSON Pseudonym where parseJSON v@(Aeson.Number _) = do w <- parseJSON v :: Aeson.Parser Word32 diff --git a/src/Model/Types/Workflow.hs b/src/Model/Types/Workflow.hs index 852de6f04..99a9998cf 100644 --- a/src/Model/Types/Workflow.hs +++ b/src/Model/Types/Workflow.hs @@ -1,7 +1,7 @@ {-# LANGUAGE UndecidableInstances #-} module Model.Types.Workflow - ( WorkflowGraph(..) + ( WorkflowGraph(..), WorkflowGraphReference(..) , WorkflowGraphNodeLabel , WorkflowGraphNode(..) , WorkflowNodeView(..) @@ -37,6 +37,8 @@ import Model.Types.Security (AuthDNF, PredDNF) import Model.Types.File (FileContentReference, FileFieldUserOption, FileField, _fieldAdditionalFiles, FileReferenceTitleMapConvertible(..)) import Database.Persist.Sql (PersistFieldSql(..)) +import Web.HttpApiData (ToHttpApiData, FromHttpApiData) +import Data.ByteArray (ByteArrayAccess) import Data.Maybe (fromJust) @@ -77,11 +79,26 @@ deriving instance (Eq fileid, Eq userid, Typeable fileid, Typeable userid, Eq (F deriving instance (Ord fileid, Ord userid, Typeable fileid, Typeable userid, Ord (FileField fileid)) => Ord (WorkflowGraph fileid userid) deriving instance (Show fileid, Show userid, Show (FileField fileid)) => Show (WorkflowGraph fileid userid) +newtype WorkflowGraphReference = WorkflowGraphReference (Digest SHA3_256) + deriving (Eq, Ord, Read, Show, Lift, Generic, Typeable) + deriving newtype ( PersistField + , PathPiece, ToHttpApiData, FromHttpApiData, ToJSON, FromJSON + , Hashable, NFData + , ByteArrayAccess + , Binary + ) + +instance PersistFieldSql WorkflowGraphReference where + sqlType _ = sqlType $ Proxy @(Digest SHA3_256) + ----- WORKFLOW GRAPH: NODES ----- newtype WorkflowGraphNodeLabel = WorkflowGraphNodeLabel { unWorkflowGraphNodeLabel :: CI Text } deriving stock (Eq, Ord, Read, Show, Data, Generic, Typeable) - deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary) + deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, Binary) + +instance PersistFieldSql WorkflowGraphNodeLabel where + sqlType _ = sqlType $ Proxy @(CI Text) data WorkflowGraphNode fileid userid = WGN { wgnFinal :: Maybe Icon @@ -112,7 +129,10 @@ data WorkflowNodeMessage userid = WorkflowNodeMessage newtype WorkflowGraphEdgeLabel = WorkflowGraphEdgeLabel { unWorkflowGraphEdgeLabel :: CI Text } deriving stock (Eq, Ord, Read, Show, Data, Generic, Typeable) - deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary) + deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, Binary) + +instance PersistFieldSql WorkflowGraphEdgeLabel where + sqlType _ = sqlType $ Proxy @(CI Text) data WorkflowGraphRestriction = WorkflowGraphRestrictionPayloadFilled { wgrPayloadFilled :: WorkflowPayloadLabel } @@ -341,9 +361,12 @@ classifyWorkflowScope = \case newtype WorkflowPayloadLabel = WorkflowPayloadLabel { unWorkflowPayloadLabel :: CI Text } deriving stock (Eq, Ord, Show, Read, Data, Generic, Typeable) - deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary) + deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, Binary) deriving anyclass (Hashable) +instance PersistFieldSql WorkflowPayloadLabel where + sqlType _ = sqlType $ Proxy @(CI Text) + newtype WorkflowStateIndex = WorkflowStateIndex { unWorkflowStateIndex :: Word64 } deriving stock (Eq, Ord, Show, Read, Data, Generic, Typeable) deriving newtype (Num, Real, Integral, Enum, Bounded, ToJSON, FromJSON, PathPiece, Binary) @@ -1052,3 +1075,7 @@ instance Binary WorkflowScope' instance (Binary termid, Binary schoolid, Binary courseid) => Binary (WorkflowScope termid schoolid courseid) instance Binary userid => Binary (WorkflowRole userid) + +----- TH Jail ----- + +makeWrapped ''WorkflowGraphReference diff --git a/src/Settings.hs b/src/Settings.hs index fd7b542d3..7fa29ef5b 100644 --- a/src/Settings.hs +++ b/src/Settings.hs @@ -210,9 +210,19 @@ data AppSettings = AppSettings , appInitialInstanceID :: Maybe (Either FilePath UUID) , appRibbon :: Maybe Text + , appJobMode :: JobMode + + , appStudyFeaturesRecacheRelevanceWithin :: Maybe NominalDiffTime + , appStudyFeaturesRecacheRelevanceInterval :: NominalDiffTime + , appMemcacheAuth :: Bool } deriving Show +data JobMode = JobsLocal { jobsAcceptOffload :: Bool } + | JobsOffload + deriving (Eq, Ord, Read, Show, Generic, Typeable) + deriving anyclass (Hashable) + data ApprootScope = ApprootUserGenerated | ApprootDefault deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) deriving anyclass (Universe, Finite, Hashable) @@ -346,6 +356,11 @@ deriveFromJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 } ''UserDefaultConf +deriveJSON defaultOptions + { fieldLabelModifier = camelToPathPiece' 1 + , constructorTagModifier = camelToPathPiece' 1 + } ''JobMode + instance FromJSON LdapConf where parseJSON = withObject "LdapConf" $ \o -> do ldapTls <- o .:? "tls" @@ -532,7 +547,7 @@ instance FromJSON AppSettings where appFileChunkingHashWindow <- o .: "file-chunking-hash-window" appFileChunkingParams <- maybe (fail "Could not recommend FastCDCParameters") return $ recommendFastCDCParameters appFileChunkingTargetExponent appFileChunkingHashWindow - appPruneUnreferencedFilesWithin <- o .: "prune-unreferenced-files-within" + appPruneUnreferencedFilesWithin <- o .:? "prune-unreferenced-files-within" appPruneUnreferencedFilesInterval <- o .: "prune-unreferenced-files-interval" appMaximumContentLength <- o .: "maximum-content-length" @@ -603,6 +618,11 @@ instance FromJSON AppSettings where appMemcacheAuth <- o .:? "memcache-auth" .!= False + appJobMode <- o .:? "job-mode" .!= JobsLocal True + + appStudyFeaturesRecacheRelevanceWithin <- o .:? "study-features-recache-relevance-within" + appStudyFeaturesRecacheRelevanceInterval <- o .: "study-features-recache-relevance-interval" + return AppSettings{..} makeClassy_ ''AppSettings diff --git a/src/Utils.hs b/src/Utils.hs index dc118b3db..295aefcf0 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -70,6 +70,8 @@ import Control.Monad.Writer.Class (MonadWriter(..)) import Control.Monad.Catch import Control.Monad.Morph (hoist) import Control.Monad.Fail +import Control.Monad.Trans.Cont (ContT, evalContT, callCC) +import qualified Control.Monad.State.Class as State import Language.Haskell.TH import Language.Haskell.TH.Instances () @@ -97,6 +99,7 @@ import qualified Crypto.MAC.KMAC as KMAC import qualified Crypto.Hash as Crypto import Crypto.Hash (HashAlgorithm, Digest) import Crypto.Hash.Instances () +import qualified Crypto.Random as Crypto import Data.ByteArray (ByteArrayAccess) @@ -139,6 +142,9 @@ import Text.Hamlet (Translate) import Data.Ratio ((%)) +import Data.UUID (UUID) +import qualified Data.UUID as UUID + {-# ANN module ("HLint: ignore Use asum" :: String) #-} @@ -462,6 +468,10 @@ guardMonoid True x = x assertMonoid :: Monoid m => (m -> Bool) -> m -> m assertMonoid f x = guardMonoid (f x) x +maybeMonoid :: Monoid m => Maybe m -> m +-- ^ Identify `Nothing` with `mempty` +maybeMonoid = fromMaybe mempty + ------------ -- Tuples -- ------------ @@ -935,6 +945,17 @@ diffTimeout timeoutLength timeoutRes act = fromMaybe timeoutRes <$> timeout time = let (MkFixed micro :: Micro) = realToFrac timeoutLength in fromInteger micro +forever' :: Monad m + => a + -> (a -> m a) + -> m b +forever' start cont = cont start >>= flip forever' cont + +foreverBreak :: Monad m + => ((r -> ContT r m b) -> ContT r m a) + -> m r +foreverBreak cont = evalContT . callCC $ forever . cont + -------------- -- Foldable -- @@ -1391,6 +1412,17 @@ uniforms :: (RandomGen g, MonadSplit g m, Foldable t) => t a -> m [a] uniforms xs = LazyRand.evalRand go <$> getSplit where go = (:) <$> interleave (uniform xs) <*> go +randUUIDC :: MonadIO m + => (forall m'. Monad m' => m' UUID -> (forall a. m a -> m' a) -> ConduitT i o m' r) + -> ConduitT i o m r +randUUIDC cont = do + drg <- liftIO Crypto.drgNew + let + mkUUID = do + uuidBS <- State.state $ Crypto.randomBytesGenerate 16 + return . fromMaybe (error $ "Could not convert bytestring to uuid: " <> show uuidBS) . UUID.fromByteString $ fromStrict uuidBS + evalStateC drg $ cont mkUUID lift + ---------- -- Lens -- ---------- diff --git a/src/Utils/DateTime.hs b/src/Utils/DateTime.hs index d41b7fb76..de3f76087 100644 --- a/src/Utils/DateTime.hs +++ b/src/Utils/DateTime.hs @@ -16,8 +16,9 @@ module Utils.DateTime , day ) where -import ClassyPrelude.Yesod hiding (lift) +import ClassyPrelude.Yesod hiding (lift, Proxy(..)) import System.Locale.Read +import Data.Proxy import Data.Time (NominalDiffTime, nominalDay, LocalTime(..), TimeOfDay, midnight, ZonedTime(..), DiffTime) import Data.Time.Zones as Zones (TZ) @@ -38,7 +39,7 @@ import Instances.TH.Lift () import Data.Data (Data) import Data.Universe -import Database.Persist.Sql (PersistFieldSql) +import Database.Persist.Sql (PersistFieldSql(..)) import Utils.PathPiece @@ -98,7 +99,10 @@ instance HasLocalTime TimeOfDay where newtype DateTimeFormat = DateTimeFormat { unDateTimeFormat :: String } deriving (Eq, Ord, Read, Show, Data, Generic, Typeable) - deriving newtype (ToJSON, FromJSON, PersistField, PersistFieldSql, IsString) + deriving newtype (ToJSON, FromJSON, PersistField, IsString) + +instance PersistFieldSql DateTimeFormat where + sqlType _ = sqlType $ Proxy @String instance Hashable DateTimeFormat diff --git a/src/Utils/Lens.hs b/src/Utils/Lens.hs index 4197649a6..09a615353 100644 --- a/src/Utils/Lens.hs +++ b/src/Utils/Lens.hs @@ -5,6 +5,7 @@ module Utils.Lens ( module Utils.Lens ) where import Import.NoModel +import Settings import Model import Model.Rating import qualified ClassyPrelude.Yesod as Yesod (HasHttpManager(..)) @@ -274,6 +275,9 @@ makePrisms ''AllocationPriority makePrisms ''RoomReference makeLenses_ ''RoomReference +makePrisms ''JobMode +makeLenses_ ''JobMode + -- makeClassy_ ''Load -------------------------- diff --git a/src/Utils/Metrics.hs b/src/Utils/Metrics.hs index f8b21dad5..174a74cde 100644 --- a/src/Utils/Metrics.hs +++ b/src/Utils/Metrics.hs @@ -5,6 +5,7 @@ module Utils.Metrics , registerGHCMetrics , observeHTTPRequestLatency , registerReadyMetric + , registerHealthCheckInterval , withJobWorkerState , observeYesodCacheSize , observeFavouritesQuickActionsDuration @@ -74,6 +75,17 @@ healthReportDuration = unsafeRegister . vector ("check", "status") $ histogram i "Duration of last health check performed by this Uni2work-instance" buckets = histogramBuckets 5e-6 100e-3 +data HealthCheckInterval = MkHealthCheckInterval + +healthCheckInterval :: (HealthCheck -> Maybe NominalDiffTime) -> Metric HealthCheckInterval +healthCheckInterval hcInts = Metric $ return (MkHealthCheckInterval, collectHealthCheckInterval) + where + collectHealthCheckInterval = return . pure . SampleGroup info GaugeType $ do + (hc, Just int) <- itoList hcInts + return . Sample "uni2work_health_check_interval_seconds" [("check", toPathPiece hc)] . encodeUtf8 $ tshow (realToFrac int :: Nano) + info = Info "uni2work_health_check_interval_seconds" + "Target interval at which health checks are executed by this Uni2work-instance" + {-# NOINLINE httpRequestLatency #-} httpRequestLatency :: Vector Label3 Histogram httpRequestLatency = unsafeRegister . vector ("handler", "method", "status") $ histogram info buckets @@ -219,9 +231,9 @@ missingFiles = unsafeRegister . vector "ref" $ gauge info withHealthReportMetrics :: MonadIO m => m HealthReport -> m HealthReport withHealthReportMetrics act = do - before <- liftIO $ getTime Monotonic + before <- liftIO getPOSIXTime report <- act - after <- liftIO $ getTime Monotonic + after <- liftIO getPOSIXTime let checkVal = toPathPiece $ classifyHealthReport report statusVal = toPathPiece $ healthReportStatus report @@ -258,6 +270,9 @@ observeHTTPRequestLatency classifyHandler app req respond' = do registerReadyMetric :: MonadIO m => m () registerReadyMetric = liftIO $ void . register . readyMetric =<< getPOSIXTime +registerHealthCheckInterval :: MonadIO m => (HealthCheck -> Maybe NominalDiffTime) -> m () +registerHealthCheckInterval = liftIO . void . register . healthCheckInterval + classifyJobWorkerState :: JobWorkerId -> JobWorkerState -> Prometheus.Label4 classifyJobWorkerState wId jws = (showWorkerId wId, tag, maybe "n/a" pack mJobCtl, maybe "n/a" pack mJob) where diff --git a/src/Utils/Parameters.hs b/src/Utils/Parameters.hs index 5d8faa79f..679a9a46b 100644 --- a/src/Utils/Parameters.hs +++ b/src/Utils/Parameters.hs @@ -32,6 +32,7 @@ data GlobalGetParam = GetLang | GetDownload | GetError | GetSelectTable + | GetGenerateToken deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) deriving anyclass (Universe, Finite) diff --git a/src/Utils/Workflow.hs b/src/Utils/Workflow.hs index 8facd298d..5454c150a 100644 --- a/src/Utils/Workflow.hs +++ b/src/Utils/Workflow.hs @@ -10,6 +10,8 @@ module Utils.Workflow , decryptWorkflowStateIndex, encryptWorkflowStateIndex , isTopWorkflowScope, isTopWorkflowScopeSql , selectWorkflowInstanceDescription + , SharedWorkflowGraphException(..), getSharedDBWorkflowGraph, getSharedIdWorkflowGraph + , insertSharedWorkflowGraph ) where import Import.NoFoundation @@ -19,11 +21,15 @@ import qualified Crypto.MAC.KMAC as Crypto import qualified Data.ByteArray as BA import qualified Data.Binary as Binary import Crypto.Hash.Algorithms (SHAKE256) +import qualified Crypto.Hash as Crypto import Language.Haskell.TH (nameBase) +import qualified Data.Aeson as Aeson import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Utils as E +{-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-} + type RouteWorkflowScope = WorkflowScope TermId SchoolId (TermId, SchoolId, CourseShorthand) type DBWorkflowScope = WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey @@ -130,3 +136,35 @@ selectWorkflowInstanceDescription wiId = withReaderT (projectBackend @SqlReadBac return $ workflowInstanceDescription E.^. WorkflowInstanceDescriptionLanguage descLang <- traverse selectLanguage . nonEmpty $ E.unValue <$> descLangs fmap join . for descLang $ \descLang' -> getBy $ UniqueWorkflowInstanceDescription wiId descLang' + + +data SharedWorkflowGraphException + = SharedWorkflowGraphNotFound SharedWorkflowGraphId + deriving (Eq, Ord, Read, Show, Generic, Typeable) + deriving anyclass (Exception) + +getSharedDBWorkflowGraph :: ( MonadHandler m + , BackendCompatible SqlReadBackend backend + ) + => SharedWorkflowGraphId + -> ReaderT backend m DBWorkflowGraph +getSharedDBWorkflowGraph swgId = $cachedHereBinary swgId . withReaderT (projectBackend @SqlReadBackend) $ do + maybe (liftHandler . throwM $ SharedWorkflowGraphNotFound swgId) (return . sharedWorkflowGraphGraph) =<< get swgId + +getSharedIdWorkflowGraph :: ( MonadHandler m + , BackendCompatible SqlReadBackend backend + ) + => SharedWorkflowGraphId + -> ReaderT backend m IdWorkflowGraph +getSharedIdWorkflowGraph = fmap (review _DBWorkflowGraph) . getSharedDBWorkflowGraph + +insertSharedWorkflowGraph :: ( MonadIO m + , BackendCompatible SqlBackend backend + ) + => DBWorkflowGraph + -> ReaderT backend m SharedWorkflowGraphId +insertSharedWorkflowGraph graph = withReaderT (projectBackend @SqlBackend) $ + swgId' <$ repsert swgId' (SharedWorkflowGraph swgId graph) + where + swgId = WorkflowGraphReference . Crypto.hashlazy $ Aeson.encode graph + swgId' = SharedWorkflowGraphKey swgId diff --git a/stack.nix b/stack.nix index 0885ab569..53fb60f6b 100644 --- a/stack.nix +++ b/stack.nix @@ -9,7 +9,7 @@ in pkgs.haskell.lib.buildStackProject { inherit (haskellPackages) stack; name = "stackenv"; buildInputs = (with pkgs; - [ postgresql zlib libsodium gmp + [ postgresql zlib libsodium gmp llvm_9 ]) ++ (with haskellPackages; [ yesod-bin happy alex ]); diff --git a/test/Database/Fill.hs b/test/Database/Fill.hs index 654dc024d..f619a1044 100644 --- a/test/Database/Fill.hs +++ b/test/Database/Fill.hs @@ -34,6 +34,7 @@ import qualified Data.Conduit.Combinators as C import qualified Data.Yaml as Yaml +import Utils.Workflow import Utils.Workflow.Lint import System.Directory (getModificationTime) @@ -532,7 +533,7 @@ fillDb = do (Just now) now True - False + Nothing insert_ $ StudyFeatures maxMuster sdBsc @@ -543,7 +544,7 @@ fillDb = do (Just now) now True - False + Nothing insert_ $ StudyFeatures tinaTester sdBsc @@ -554,7 +555,7 @@ fillDb = do (Just now) now False - False + Nothing insert_ $ StudyFeatures tinaTester sdLAG @@ -565,7 +566,7 @@ fillDb = do (Just now) now True - False + Nothing insert_ $ StudyFeatures tinaTester sdLAR @@ -576,7 +577,7 @@ fillDb = do (Just now) now True - False + Nothing insert_ $ StudyFeatures tinaTester sdMst @@ -587,7 +588,7 @@ fillDb = do (Just now) now True - False + Nothing -- FFP let nbrs :: [Int] @@ -1330,8 +1331,9 @@ fillDb = do displayLinterIssue = liftIO . hPutStrLn stderr . displayException handleSql displayLinterIssue $ do - workflowDefinitionGraph <- Yaml.decodeFileThrow $ testdataDir "workflows" "theses.yaml" - for_ (lintWorkflowGraph workflowDefinitionGraph) $ mapM_ throwM + graph <- Yaml.decodeFileThrow $ testdataDir "workflows" "theses.yaml" + for_ (lintWorkflowGraph graph) $ mapM_ throwM + workflowDefinitionGraph <- insertSharedWorkflowGraph graph let thesesWorkflowDef = WorkflowDefinition{..} where workflowDefinitionInstanceCategory = Just "theses" @@ -1366,8 +1368,9 @@ fillDb = do } handleSql displayLinterIssue $ do - workflowDefinitionGraph <- Yaml.decodeFileThrow $ testdataDir "workflows" "recognitions-ifi.yaml" - for_ (lintWorkflowGraph workflowDefinitionGraph) $ mapM_ throwM + graph <- Yaml.decodeFileThrow $ testdataDir "workflows" "recognitions-ifi.yaml" + for_ (lintWorkflowGraph graph) $ mapM_ throwM + workflowDefinitionGraph <- insertSharedWorkflowGraph graph let recognitionsWorkflowDef = WorkflowDefinition{..} where workflowDefinitionInstanceCategory = Just "recognitions-ifi"