mirror of
https://github.com/standardnotes/server
synced 2026-04-20 11:02:33 -04:00
Compare commits
305 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f8f65c3f | |||
| 3953dbc6b4 | |||
| 0b205287d1 | |||
| 4f0bc57b1a | |||
| 7d43316597 | |||
| 65d31f011b | |||
| 80dd6efae3 | |||
| a96f2c9153 | |||
| 225e0aaf88 | |||
| f0c85910bc | |||
| 124c443528 | |||
| 37c7f8d39f | |||
| c419f1ce22 | |||
| 4949cdfe2f | |||
| cd101b96ea | |||
| 40d0e4631f | |||
| a55a995660 | |||
| 1d576d48ad | |||
| 4ff8030f87 | |||
| c15e2e2c8f | |||
| 41d31a8d75 | |||
| 10e2a26352 | |||
| 6e547f77d0 | |||
| 530a426601 | |||
| 642d6bab77 | |||
| 7980af3d82 | |||
| 2980c42e88 | |||
| b03994f9db | |||
| 41906ec2f9 | |||
| 4d1e7ff2a5 | |||
| 7f18fcfc13 | |||
| ff02ce0747 | |||
| a6056600eb | |||
| 24c94326d5 | |||
| 48c0cb5e62 | |||
| 9968efe1b2 | |||
| 6368342149 | |||
| b5f73db210 | |||
| 22d6a02d04 | |||
| 4e0bcfcccf | |||
| 104313c15d | |||
| 814289af46 | |||
| 3096cd98d5 | |||
| 45dfefbc7a | |||
| 20d92149a8 | |||
| 9c01fffca5 | |||
| 61c1cfff4b | |||
| 7e74261f62 | |||
| 32601f34f1 | |||
| aef69a1a96 | |||
| 130f90bdb6 | |||
| 851c7de87f | |||
| 118156c62d | |||
| cdad3143c9 | |||
| 00fe32136e | |||
| 52f56eeb68 | |||
| b595264e31 | |||
| bf04262170 | |||
| fd589922bb | |||
| fb7029f5c1 | |||
| cc4b4f9bf8 | |||
| b048d6d7e3 | |||
| cffc1f442f | |||
| 91fe710741 | |||
| 5a1eb9fdac | |||
| a64ef6e750 | |||
| 47d2012b3d | |||
| 2c99cd2e21 | |||
| 435cd2f66a | |||
| 372b12dfc2 | |||
| 3a12f5c1c4 | |||
| 781de224b6 | |||
| eff09454c3 | |||
| 473feba6a8 | |||
| e9f0704fb0 | |||
| 8c99469d88 | |||
| 8ec1311dfc | |||
| e48cca6b45 | |||
| d660721f95 | |||
| c52bb93d79 | |||
| ffb6bfd0c9 | |||
| 6e0855f9b3 | |||
| ec9e9ec387 | |||
| fa75aa40f0 | |||
| b865953c22 | |||
| 2542cf6f9a | |||
| cb9499b87f | |||
| c351f01f67 | |||
| c87561fca7 | |||
| a363c143fa | |||
| fb81d2b926 | |||
| 05b1b8f079 | |||
| 7848dc06d4 | |||
| 3a005719b7 | |||
| 6928988f78 | |||
| a521894d7c | |||
| b7fb1d9c08 | |||
| 5f67e45911 | |||
| fddf9fccbd | |||
| 2bedbd7bd2 | |||
| 02f3c85796 | |||
| 3b5bd6a47f | |||
| 06fd404d44 | |||
| d931c52508 | |||
| 800fe9e4c8 | |||
| 8b3d78678f | |||
| 2351cd3ad6 | |||
| dd86c5bcdf | |||
| d0c00e306e | |||
| 6cd68ddd6a | |||
| 02639cddb2 | |||
| 0f67aa4058 | |||
| 78c3403d5f | |||
| fc8f8c574d | |||
| 3972ee580d | |||
| b0a994d5be | |||
| 80df28a0c4 | |||
| 1c6c6a9296 | |||
| 7bb698e442 | |||
| 784728cd54 | |||
| 4b883b68de | |||
| dec2cc2aaf | |||
| b4e8971ad2 | |||
| 84e436265e | |||
| ac8a69f8d4 | |||
| b912e050ea | |||
| 284561d093 | |||
| efc355982c | |||
| 8907879a19 | |||
| 86f6057207 | |||
| 4c92698c73 | |||
| 8407c3b649 | |||
| ed8f82617d | |||
| 31d040d1b6 | |||
| 25a6796e63 | |||
| ff091918aa | |||
| 91b76edce1 | |||
| 5ae5c83bf5 | |||
| 9d90f276de | |||
| 245f091e22 | |||
| ae2f8f086b | |||
| 5e5eb7f937 | |||
| 748630e1f1 | |||
| 43064c8c55 | |||
| 4559a3047c | |||
| de8064ee5c | |||
| 48c8dba342 | |||
| 31a515b2f1 | |||
| 294f56e189 | |||
| 70596a0aac | |||
| 74bc79116b | |||
| e6bd50ae77 | |||
| 308662550f | |||
| d94a7e7157 | |||
| 630b264754 | |||
| 5f2be44b85 | |||
| f68ece68af | |||
| 70c829a2c9 | |||
| e3b6ac4874 | |||
| a762d5a22c | |||
| 3686a26019 | |||
| 80daec748d | |||
| 94359f1299 | |||
| 59dda1bb99 | |||
| 806a732cbc | |||
| 7816be7ba7 | |||
| 5f3bd5137f | |||
| 6c9fc5fb86 | |||
| f7e0b68643 | |||
| b283bbaca9 | |||
| 92ba759b1c | |||
| 0acc9d8d68 | |||
| daa7a9ff61 | |||
| 455f35e0c1 | |||
| 1fa655b56e | |||
| e553222b4b | |||
| f1b6f48926 | |||
| 14ab1cae69 | |||
| 5f9cf90b16 | |||
| 97b367d4ee | |||
| 47119fb346 | |||
| d77eb7f5f1 | |||
| 1b0a2bb34c | |||
| a363039fa1 | |||
| 32c740b58e | |||
| 822ee890af | |||
| b0406dd8aa | |||
| 8d152ddfcb | |||
| 1a16d2e4f4 | |||
| 1ca8531305 | |||
| 6190e7d092 | |||
| a6542dd638 | |||
| 840777a851 | |||
| 5c9dff38c9 | |||
| abfbacb8c2 | |||
| 03afdbf431 | |||
| 507d43b328 | |||
| be214c0599 | |||
| 91f36c3a3f | |||
| f60c15ed2e | |||
| 1ec072373d | |||
| a7d039082e | |||
| d5c06bfa58 | |||
| c8f3a0ce7b | |||
| edbedc181b | |||
| 94afa34780 | |||
| 74dd0ab6cd | |||
| 6c43a331d0 | |||
| 67835ba0c0 | |||
| fe1b2a0e07 | |||
| 2e82be47ed | |||
| 15dfd6dcba | |||
| dfd38943b0 | |||
| 500756d582 | |||
| f855f541d8 | |||
| 590ec6643d | |||
| b9efd35b50 | |||
| 3be1bfe58a | |||
| bfbd2de778 | |||
| 50f7ae338a | |||
| 280fdc89c1 | |||
| 0f94e2ad0c | |||
| d0036600e9 | |||
| f766fefbf0 | |||
| 2178ed2a31 | |||
| 79511aea5f | |||
| 19bb77273b | |||
| 7ca377f1b8 | |||
| 6f5e9b7b5a | |||
| f03a58079d | |||
| 8715fe1822 | |||
| e10cb9adaf | |||
| 3030832711 | |||
| 7c638ef28a | |||
| 447a4b5e04 | |||
| dd1ba6e302 | |||
| 08556b751f | |||
| 11d2190310 | |||
| 46519bb710 | |||
| 7b9290382d | |||
| 85e55cf0e4 | |||
| 7016854b7f | |||
| 01a4151763 | |||
| 311f758cd8 | |||
| 3bba36742a | |||
| ea52ba51ca | |||
| 7e404ae71a | |||
| 3ad95afa84 | |||
| 1a13861647 | |||
| 6d84c819c0 | |||
| 36ec39d2fb | |||
| eaafc12c8a | |||
| a03c5bceea | |||
| 53c51fd204 | |||
| 9b593f2a6b | |||
| 363609cb1b | |||
| 68e6d30093 | |||
| c53a40ef8d | |||
| 3c2ac05c60 | |||
| bffab433f6 | |||
| 200b6ce01f | |||
| 0d29dc1012 | |||
| b92c4ae650 | |||
| e15d1e52bd | |||
| ce3e259bde | |||
| 87361f90b1 | |||
| 81be06598c | |||
| 9492da6789 | |||
| fce47a0a37 | |||
| 92ba682198 | |||
| 8df0482eb4 | |||
| 37a5cb347d | |||
| 77e50655f6 | |||
| eacd2abc00 | |||
| 7393954ff6 | |||
| 68744379a6 | |||
| 90aef905af | |||
| c7cbc8966e | |||
| 89502bed63 | |||
| 4952b48db6 | |||
| 52a257abb1 | |||
| 7480fb089b | |||
| 0f65c051ab | |||
| 7b62c7a967 | |||
| 5c3db2cb29 | |||
| 7008cbd363 | |||
| cdb7fcf831 | |||
| 628aafdd42 | |||
| 9d3ef24ba9 | |||
| 4189f11fd7 | |||
| 5ea9941519 | |||
| 7a64494d07 | |||
| 4928685198 | |||
| 0103233d4a | |||
| ee7075fe60 | |||
| 49feadd32a | |||
| 45758bf554 | |||
| 535d566a94 | |||
| ff1d5db12c | |||
| 77a06b2fe7 | |||
| 6359030030 | |||
| 006f1fccec | |||
| c0f5817d47 | |||
| 3da952fa52 | |||
| f1834d58d2 |
@@ -187,7 +187,7 @@ jobs:
|
|||||||
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||||
|
|
||||||
- name: Run E2E test suite
|
- name: Run E2E test suite
|
||||||
uses: convictional/trigger-workflow-and-wait@v1.6.3
|
uses: convictional/trigger-workflow-and-wait@master
|
||||||
with:
|
with:
|
||||||
owner: standardnotes
|
owner: standardnotes
|
||||||
repo: e2e
|
repo: e2e
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Revisions Server
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: revisions_server
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*standardnotes/revisions-server*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
call_server_application_workflow:
|
||||||
|
name: Server Application
|
||||||
|
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
|
||||||
|
with:
|
||||||
|
service_name: revisions
|
||||||
|
workspace_name: "@standardnotes/revisions-server"
|
||||||
|
e2e_tag_parameter_name: revisions_image_tag
|
||||||
|
package_path: packages/revisions
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
newrelic:
|
||||||
|
needs: call_server_application_workflow
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create New Relic deployment marker for Web
|
||||||
|
uses: newrelic/deployment-marker-action@v1
|
||||||
|
with:
|
||||||
|
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
|
||||||
|
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
|
||||||
|
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_REVISIONS_WEB_PROD }}
|
||||||
|
revision: "${{ github.sha }}"
|
||||||
|
description: "Automated Deployment via Github Actions"
|
||||||
|
user: "${{ github.actor }}"
|
||||||
|
- name: Create New Relic deployment marker for Worker
|
||||||
|
uses: newrelic/deployment-marker-action@v1
|
||||||
|
with:
|
||||||
|
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
|
||||||
|
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
|
||||||
|
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_REVISIONS_WORKER_PROD }}
|
||||||
|
revision: "${{ github.sha }}"
|
||||||
|
description: "Automated Deployment via Github Actions"
|
||||||
|
user: "${{ github.actor }}"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
Vendored
+2
-2
@@ -326,8 +326,8 @@ ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
||||||
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/16.15.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/16.15.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/18.12.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/18.12.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
||||||
Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/build/config.gypi
|
Makefile: $(srcdir)/build/config.gypi $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/18.12.1/include/node/common.gypi $(srcdir)/binding.gyp
|
||||||
$(call do_cmd,regen_makefile)
|
$(call do_cmd,regen_makefile)
|
||||||
|
|
||||||
# "all" is a concatenation of the "all" targets from all the included
|
# "all" is a concatenation of the "all" targets from all the included
|
||||||
|
|||||||
Generated
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
cmd_Release/native_metrics.node := c++ -bundle -undefined dynamic_lookup -Wl,-search_paths_first -mmacosx-version-min=10.13 -arch x86_64 -L./Release -stdlib=libc++ -o Release/native_metrics.node Release/obj.target/native_metrics/src/native_metrics.o Release/obj.target/native_metrics/src/GCBinder.o Release/obj.target/native_metrics/src/LoopChecker.o
|
|
||||||
Generated
Vendored
-69
@@ -1,69 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/GCBinder.o := c++ -o Release/obj.target/native_metrics/src/GCBinder.o ../src/GCBinder.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/GCBinder.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/GCBinder.o: ../src/GCBinder.cpp \
|
|
||||||
../src/GCBinder.hpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/Metric.hpp
|
|
||||||
../src/GCBinder.cpp:
|
|
||||||
../src/GCBinder.hpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/LoopChecker.o := c++ -o Release/obj.target/native_metrics/src/LoopChecker.o ../src/LoopChecker.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/LoopChecker.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/LoopChecker.o: \
|
|
||||||
../src/LoopChecker.cpp \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
../src/LoopChecker.hpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/Metric.hpp
|
|
||||||
../src/LoopChecker.cpp:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
../src/LoopChecker.hpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/native_metrics.o := c++ -o Release/obj.target/native_metrics/src/native_metrics.o ../src/native_metrics.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/native_metrics.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/native_metrics.o: \
|
|
||||||
../src/native_metrics.cpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/GCBinder.hpp ../src/Metric.hpp ../src/LoopChecker.hpp
|
|
||||||
../src/native_metrics.cpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/GCBinder.hpp:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
../src/LoopChecker.hpp:
|
|
||||||
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+271
-246
@@ -19,293 +19,316 @@
|
|||||||
"error_on_warn": "false",
|
"error_on_warn": "false",
|
||||||
"force_dynamic_crt": 0,
|
"force_dynamic_crt": 0,
|
||||||
"host_arch": "x64",
|
"host_arch": "x64",
|
||||||
"icu_data_in": "../../deps/icu-tmp/icudt70l.dat",
|
"icu_data_in": "../../deps/icu-tmp/icudt71l.dat",
|
||||||
"icu_endianness": "l",
|
"icu_endianness": "l",
|
||||||
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
||||||
"icu_path": "deps/icu-small",
|
"icu_path": "deps/icu-small",
|
||||||
"icu_small": "false",
|
"icu_small": "false",
|
||||||
"icu_ver_major": "70",
|
"icu_ver_major": "71",
|
||||||
"is_debug": 0,
|
"is_debug": 0,
|
||||||
|
"libdir": "lib",
|
||||||
"llvm_version": "11.0",
|
"llvm_version": "11.0",
|
||||||
"napi_build_version": "8",
|
"napi_build_version": "8",
|
||||||
"node_byteorder": "little",
|
"node_byteorder": "little",
|
||||||
"node_debug_lib": "false",
|
"node_debug_lib": "false",
|
||||||
"node_enable_d8": "false",
|
"node_enable_d8": "false",
|
||||||
|
"node_fipsinstall": "false",
|
||||||
"node_install_corepack": "true",
|
"node_install_corepack": "true",
|
||||||
"node_install_npm": "true",
|
"node_install_npm": "true",
|
||||||
"node_library_files": [
|
"node_library_files": [
|
||||||
"lib/constants.js",
|
|
||||||
"lib/net.js",
|
|
||||||
"lib/trace_events.js",
|
|
||||||
"lib/events.js",
|
|
||||||
"lib/repl.js",
|
|
||||||
"lib/util.js",
|
|
||||||
"lib/dgram.js",
|
|
||||||
"lib/vm.js",
|
|
||||||
"lib/stream.js",
|
|
||||||
"lib/child_process.js",
|
|
||||||
"lib/assert.js",
|
|
||||||
"lib/_tls_wrap.js",
|
|
||||||
"lib/http2.js",
|
|
||||||
"lib/inspector.js",
|
|
||||||
"lib/os.js",
|
|
||||||
"lib/_http_server.js",
|
|
||||||
"lib/console.js",
|
|
||||||
"lib/perf_hooks.js",
|
|
||||||
"lib/readline.js",
|
|
||||||
"lib/punycode.js",
|
|
||||||
"lib/_http_incoming.js",
|
|
||||||
"lib/https.js",
|
|
||||||
"lib/_stream_wrap.js",
|
|
||||||
"lib/domain.js",
|
|
||||||
"lib/dns.js",
|
|
||||||
"lib/_http_client.js",
|
|
||||||
"lib/diagnostics_channel.js",
|
|
||||||
"lib/tty.js",
|
|
||||||
"lib/_http_agent.js",
|
"lib/_http_agent.js",
|
||||||
"lib/timers.js",
|
"lib/_http_client.js",
|
||||||
"lib/_http_outgoing.js",
|
|
||||||
"lib/querystring.js",
|
|
||||||
"lib/_tls_common.js",
|
|
||||||
"lib/module.js",
|
|
||||||
"lib/_stream_passthrough.js",
|
|
||||||
"lib/_stream_transform.js",
|
|
||||||
"lib/worker_threads.js",
|
|
||||||
"lib/sys.js",
|
|
||||||
"lib/_stream_duplex.js",
|
|
||||||
"lib/path.js",
|
|
||||||
"lib/_http_common.js",
|
"lib/_http_common.js",
|
||||||
"lib/string_decoder.js",
|
"lib/_http_incoming.js",
|
||||||
"lib/cluster.js",
|
"lib/_http_outgoing.js",
|
||||||
"lib/v8.js",
|
"lib/_http_server.js",
|
||||||
"lib/crypto.js",
|
"lib/_stream_duplex.js",
|
||||||
"lib/wasi.js",
|
"lib/_stream_passthrough.js",
|
||||||
"lib/_stream_readable.js",
|
"lib/_stream_readable.js",
|
||||||
"lib/zlib.js",
|
"lib/_stream_transform.js",
|
||||||
"lib/url.js",
|
"lib/_stream_wrap.js",
|
||||||
"lib/tls.js",
|
|
||||||
"lib/_stream_writable.js",
|
"lib/_stream_writable.js",
|
||||||
|
"lib/_tls_common.js",
|
||||||
|
"lib/_tls_wrap.js",
|
||||||
|
"lib/assert.js",
|
||||||
|
"lib/assert/strict.js",
|
||||||
"lib/async_hooks.js",
|
"lib/async_hooks.js",
|
||||||
"lib/process.js",
|
|
||||||
"lib/http.js",
|
|
||||||
"lib/buffer.js",
|
"lib/buffer.js",
|
||||||
|
"lib/child_process.js",
|
||||||
|
"lib/cluster.js",
|
||||||
|
"lib/console.js",
|
||||||
|
"lib/constants.js",
|
||||||
|
"lib/crypto.js",
|
||||||
|
"lib/dgram.js",
|
||||||
|
"lib/diagnostics_channel.js",
|
||||||
|
"lib/dns.js",
|
||||||
|
"lib/dns/promises.js",
|
||||||
|
"lib/domain.js",
|
||||||
|
"lib/events.js",
|
||||||
"lib/fs.js",
|
"lib/fs.js",
|
||||||
"lib/util/types.js",
|
"lib/fs/promises.js",
|
||||||
"lib/timers/promises.js",
|
"lib/http.js",
|
||||||
"lib/path/win32.js",
|
"lib/http2.js",
|
||||||
"lib/path/posix.js",
|
"lib/https.js",
|
||||||
"lib/stream/consumers.js",
|
"lib/inspector.js",
|
||||||
"lib/stream/promises.js",
|
|
||||||
"lib/stream/web.js",
|
|
||||||
"lib/internal/constants.js",
|
|
||||||
"lib/internal/abort_controller.js",
|
"lib/internal/abort_controller.js",
|
||||||
"lib/internal/net.js",
|
|
||||||
"lib/internal/v8_prof_processor.js",
|
|
||||||
"lib/internal/event_target.js",
|
|
||||||
"lib/internal/inspector_async_hook.js",
|
|
||||||
"lib/internal/validators.js",
|
|
||||||
"lib/internal/linkedlist.js",
|
|
||||||
"lib/internal/cli_table.js",
|
|
||||||
"lib/internal/repl.js",
|
|
||||||
"lib/internal/util.js",
|
|
||||||
"lib/internal/histogram.js",
|
|
||||||
"lib/internal/error_serdes.js",
|
|
||||||
"lib/internal/dgram.js",
|
|
||||||
"lib/internal/child_process.js",
|
|
||||||
"lib/internal/assert.js",
|
"lib/internal/assert.js",
|
||||||
"lib/internal/fixed_queue.js",
|
"lib/internal/assert/assertion_error.js",
|
||||||
"lib/internal/blocklist.js",
|
"lib/internal/assert/calltracker.js",
|
||||||
"lib/internal/v8_prof_polyfill.js",
|
"lib/internal/assert/snapshot.js",
|
||||||
"lib/internal/options.js",
|
|
||||||
"lib/internal/worker.js",
|
|
||||||
"lib/internal/dtrace.js",
|
|
||||||
"lib/internal/idna.js",
|
|
||||||
"lib/internal/watchdog.js",
|
|
||||||
"lib/internal/encoding.js",
|
|
||||||
"lib/internal/tty.js",
|
|
||||||
"lib/internal/freeze_intrinsics.js",
|
|
||||||
"lib/internal/timers.js",
|
|
||||||
"lib/internal/heap_utils.js",
|
|
||||||
"lib/internal/querystring.js",
|
|
||||||
"lib/internal/js_stream_socket.js",
|
|
||||||
"lib/internal/errors.js",
|
|
||||||
"lib/internal/priority_queue.js",
|
|
||||||
"lib/internal/freelist.js",
|
|
||||||
"lib/internal/blob.js",
|
|
||||||
"lib/internal/socket_list.js",
|
|
||||||
"lib/internal/socketaddress.js",
|
|
||||||
"lib/internal/promise_hooks.js",
|
|
||||||
"lib/internal/stream_base_commons.js",
|
|
||||||
"lib/internal/url.js",
|
|
||||||
"lib/internal/async_hooks.js",
|
"lib/internal/async_hooks.js",
|
||||||
"lib/internal/http.js",
|
"lib/internal/blob.js",
|
||||||
"lib/internal/buffer.js",
|
"lib/internal/blocklist.js",
|
||||||
"lib/internal/trace_events_async_hooks.js",
|
"lib/internal/bootstrap/browser.js",
|
||||||
"lib/internal/crypto/sig.js",
|
|
||||||
"lib/internal/crypto/rsa.js",
|
|
||||||
"lib/internal/crypto/aes.js",
|
|
||||||
"lib/internal/crypto/util.js",
|
|
||||||
"lib/internal/crypto/scrypt.js",
|
|
||||||
"lib/internal/crypto/random.js",
|
|
||||||
"lib/internal/crypto/keys.js",
|
|
||||||
"lib/internal/crypto/x509.js",
|
|
||||||
"lib/internal/crypto/certificate.js",
|
|
||||||
"lib/internal/crypto/ec.js",
|
|
||||||
"lib/internal/crypto/keygen.js",
|
|
||||||
"lib/internal/crypto/mac.js",
|
|
||||||
"lib/internal/crypto/diffiehellman.js",
|
|
||||||
"lib/internal/crypto/hkdf.js",
|
|
||||||
"lib/internal/crypto/cipher.js",
|
|
||||||
"lib/internal/crypto/hash.js",
|
|
||||||
"lib/internal/crypto/pbkdf2.js",
|
|
||||||
"lib/internal/crypto/webcrypto.js",
|
|
||||||
"lib/internal/crypto/dsa.js",
|
|
||||||
"lib/internal/crypto/hashnames.js",
|
|
||||||
"lib/internal/cluster/shared_handle.js",
|
|
||||||
"lib/internal/cluster/round_robin_handle.js",
|
|
||||||
"lib/internal/cluster/worker.js",
|
|
||||||
"lib/internal/cluster/primary.js",
|
|
||||||
"lib/internal/cluster/utils.js",
|
|
||||||
"lib/internal/cluster/child.js",
|
|
||||||
"lib/internal/webstreams/compression.js",
|
|
||||||
"lib/internal/webstreams/util.js",
|
|
||||||
"lib/internal/webstreams/writablestream.js",
|
|
||||||
"lib/internal/webstreams/readablestream.js",
|
|
||||||
"lib/internal/webstreams/queuingstrategies.js",
|
|
||||||
"lib/internal/webstreams/encoding.js",
|
|
||||||
"lib/internal/webstreams/transformstream.js",
|
|
||||||
"lib/internal/webstreams/adapters.js",
|
|
||||||
"lib/internal/webstreams/transfer.js",
|
|
||||||
"lib/internal/bootstrap/loaders.js",
|
"lib/internal/bootstrap/loaders.js",
|
||||||
"lib/internal/bootstrap/pre_execution.js",
|
|
||||||
"lib/internal/bootstrap/node.js",
|
"lib/internal/bootstrap/node.js",
|
||||||
"lib/internal/bootstrap/environment.js",
|
|
||||||
"lib/internal/bootstrap/switches/does_not_own_process_state.js",
|
"lib/internal/bootstrap/switches/does_not_own_process_state.js",
|
||||||
"lib/internal/bootstrap/switches/is_not_main_thread.js",
|
|
||||||
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
||||||
"lib/internal/bootstrap/switches/is_main_thread.js",
|
"lib/internal/bootstrap/switches/is_main_thread.js",
|
||||||
"lib/internal/test/binding.js",
|
"lib/internal/bootstrap/switches/is_not_main_thread.js",
|
||||||
"lib/internal/test/transfer.js",
|
"lib/internal/buffer.js",
|
||||||
"lib/internal/util/types.js",
|
"lib/internal/child_process.js",
|
||||||
"lib/internal/util/inspector.js",
|
|
||||||
"lib/internal/util/comparisons.js",
|
|
||||||
"lib/internal/util/debuglog.js",
|
|
||||||
"lib/internal/util/inspect.js",
|
|
||||||
"lib/internal/util/iterable_weak_map.js",
|
|
||||||
"lib/internal/streams/add-abort-signal.js",
|
|
||||||
"lib/internal/streams/compose.js",
|
|
||||||
"lib/internal/streams/duplexify.js",
|
|
||||||
"lib/internal/streams/destroy.js",
|
|
||||||
"lib/internal/streams/legacy.js",
|
|
||||||
"lib/internal/streams/passthrough.js",
|
|
||||||
"lib/internal/streams/operators.js",
|
|
||||||
"lib/internal/streams/readable.js",
|
|
||||||
"lib/internal/streams/from.js",
|
|
||||||
"lib/internal/streams/writable.js",
|
|
||||||
"lib/internal/streams/state.js",
|
|
||||||
"lib/internal/streams/buffer_list.js",
|
|
||||||
"lib/internal/streams/end-of-stream.js",
|
|
||||||
"lib/internal/streams/utils.js",
|
|
||||||
"lib/internal/streams/transform.js",
|
|
||||||
"lib/internal/streams/lazy_transform.js",
|
|
||||||
"lib/internal/streams/duplex.js",
|
|
||||||
"lib/internal/streams/pipeline.js",
|
|
||||||
"lib/internal/readline/interface.js",
|
|
||||||
"lib/internal/readline/utils.js",
|
|
||||||
"lib/internal/readline/emitKeypressEvents.js",
|
|
||||||
"lib/internal/readline/callbacks.js",
|
|
||||||
"lib/internal/repl/history.js",
|
|
||||||
"lib/internal/repl/utils.js",
|
|
||||||
"lib/internal/repl/await.js",
|
|
||||||
"lib/internal/legacy/processbinding.js",
|
|
||||||
"lib/internal/assert/calltracker.js",
|
|
||||||
"lib/internal/assert/assertion_error.js",
|
|
||||||
"lib/internal/http2/util.js",
|
|
||||||
"lib/internal/http2/core.js",
|
|
||||||
"lib/internal/http2/compat.js",
|
|
||||||
"lib/internal/per_context/messageport.js",
|
|
||||||
"lib/internal/per_context/primordials.js",
|
|
||||||
"lib/internal/per_context/domexception.js",
|
|
||||||
"lib/internal/vm/module.js",
|
|
||||||
"lib/internal/tls/secure-pair.js",
|
|
||||||
"lib/internal/tls/parse-cert-string.js",
|
|
||||||
"lib/internal/tls/secure-context.js",
|
|
||||||
"lib/internal/child_process/serialization.js",
|
"lib/internal/child_process/serialization.js",
|
||||||
"lib/internal/debugger/inspect_repl.js",
|
"lib/internal/cli_table.js",
|
||||||
"lib/internal/debugger/inspect_client.js",
|
"lib/internal/cluster/child.js",
|
||||||
|
"lib/internal/cluster/primary.js",
|
||||||
|
"lib/internal/cluster/round_robin_handle.js",
|
||||||
|
"lib/internal/cluster/shared_handle.js",
|
||||||
|
"lib/internal/cluster/utils.js",
|
||||||
|
"lib/internal/cluster/worker.js",
|
||||||
|
"lib/internal/console/constructor.js",
|
||||||
|
"lib/internal/console/global.js",
|
||||||
|
"lib/internal/constants.js",
|
||||||
|
"lib/internal/crypto/aes.js",
|
||||||
|
"lib/internal/crypto/certificate.js",
|
||||||
|
"lib/internal/crypto/cfrg.js",
|
||||||
|
"lib/internal/crypto/cipher.js",
|
||||||
|
"lib/internal/crypto/diffiehellman.js",
|
||||||
|
"lib/internal/crypto/ec.js",
|
||||||
|
"lib/internal/crypto/hash.js",
|
||||||
|
"lib/internal/crypto/hashnames.js",
|
||||||
|
"lib/internal/crypto/hkdf.js",
|
||||||
|
"lib/internal/crypto/keygen.js",
|
||||||
|
"lib/internal/crypto/keys.js",
|
||||||
|
"lib/internal/crypto/mac.js",
|
||||||
|
"lib/internal/crypto/pbkdf2.js",
|
||||||
|
"lib/internal/crypto/random.js",
|
||||||
|
"lib/internal/crypto/rsa.js",
|
||||||
|
"lib/internal/crypto/scrypt.js",
|
||||||
|
"lib/internal/crypto/sig.js",
|
||||||
|
"lib/internal/crypto/util.js",
|
||||||
|
"lib/internal/crypto/webcrypto.js",
|
||||||
|
"lib/internal/crypto/x509.js",
|
||||||
"lib/internal/debugger/inspect.js",
|
"lib/internal/debugger/inspect.js",
|
||||||
"lib/internal/worker/io.js",
|
"lib/internal/debugger/inspect_client.js",
|
||||||
"lib/internal/worker/js_transferable.js",
|
"lib/internal/debugger/inspect_repl.js",
|
||||||
"lib/internal/main/repl.js",
|
"lib/internal/dgram.js",
|
||||||
"lib/internal/main/print_help.js",
|
"lib/internal/dns/callback_resolver.js",
|
||||||
"lib/internal/main/eval_string.js",
|
|
||||||
"lib/internal/main/check_syntax.js",
|
|
||||||
"lib/internal/main/prof_process.js",
|
|
||||||
"lib/internal/main/worker_thread.js",
|
|
||||||
"lib/internal/main/inspect.js",
|
|
||||||
"lib/internal/main/eval_stdin.js",
|
|
||||||
"lib/internal/main/run_main_module.js",
|
|
||||||
"lib/internal/modules/run_main.js",
|
|
||||||
"lib/internal/modules/package_json_reader.js",
|
|
||||||
"lib/internal/modules/esm/module_job.js",
|
|
||||||
"lib/internal/modules/esm/assert.js",
|
|
||||||
"lib/internal/modules/esm/fetch_module.js",
|
|
||||||
"lib/internal/modules/esm/get_source.js",
|
|
||||||
"lib/internal/modules/esm/translators.js",
|
|
||||||
"lib/internal/modules/esm/resolve.js",
|
|
||||||
"lib/internal/modules/esm/create_dynamic_module.js",
|
|
||||||
"lib/internal/modules/esm/load.js",
|
|
||||||
"lib/internal/modules/esm/handle_process_exit.js",
|
|
||||||
"lib/internal/modules/esm/initialize_import_meta.js",
|
|
||||||
"lib/internal/modules/esm/module_map.js",
|
|
||||||
"lib/internal/modules/esm/get_format.js",
|
|
||||||
"lib/internal/modules/esm/formats.js",
|
|
||||||
"lib/internal/modules/esm/loader.js",
|
|
||||||
"lib/internal/modules/cjs/helpers.js",
|
|
||||||
"lib/internal/modules/cjs/loader.js",
|
|
||||||
"lib/internal/source_map/source_map.js",
|
|
||||||
"lib/internal/source_map/prepare_stack_trace.js",
|
|
||||||
"lib/internal/source_map/source_map_cache.js",
|
|
||||||
"lib/internal/dns/promises.js",
|
"lib/internal/dns/promises.js",
|
||||||
"lib/internal/dns/utils.js",
|
"lib/internal/dns/utils.js",
|
||||||
"lib/internal/fs/watchers.js",
|
"lib/internal/dtrace.js",
|
||||||
|
"lib/internal/encoding.js",
|
||||||
|
"lib/internal/error_serdes.js",
|
||||||
|
"lib/internal/errors.js",
|
||||||
|
"lib/internal/event_target.js",
|
||||||
|
"lib/internal/fixed_queue.js",
|
||||||
|
"lib/internal/freelist.js",
|
||||||
|
"lib/internal/freeze_intrinsics.js",
|
||||||
|
"lib/internal/fs/cp/cp-sync.js",
|
||||||
|
"lib/internal/fs/cp/cp.js",
|
||||||
|
"lib/internal/fs/dir.js",
|
||||||
"lib/internal/fs/promises.js",
|
"lib/internal/fs/promises.js",
|
||||||
"lib/internal/fs/read_file_context.js",
|
"lib/internal/fs/read_file_context.js",
|
||||||
"lib/internal/fs/rimraf.js",
|
"lib/internal/fs/rimraf.js",
|
||||||
"lib/internal/fs/sync_write_stream.js",
|
|
||||||
"lib/internal/fs/dir.js",
|
|
||||||
"lib/internal/fs/streams.js",
|
"lib/internal/fs/streams.js",
|
||||||
|
"lib/internal/fs/sync_write_stream.js",
|
||||||
"lib/internal/fs/utils.js",
|
"lib/internal/fs/utils.js",
|
||||||
"lib/internal/fs/cp/cp.js",
|
"lib/internal/fs/watchers.js",
|
||||||
"lib/internal/fs/cp/cp-sync.js",
|
"lib/internal/heap_utils.js",
|
||||||
"lib/internal/perf/nodetiming.js",
|
"lib/internal/histogram.js",
|
||||||
"lib/internal/perf/usertiming.js",
|
"lib/internal/http.js",
|
||||||
"lib/internal/perf/performance_entry.js",
|
"lib/internal/http2/compat.js",
|
||||||
"lib/internal/perf/performance.js",
|
"lib/internal/http2/core.js",
|
||||||
"lib/internal/perf/timerify.js",
|
"lib/internal/http2/util.js",
|
||||||
"lib/internal/perf/utils.js",
|
"lib/internal/idna.js",
|
||||||
"lib/internal/perf/observe.js",
|
"lib/internal/inspector_async_hook.js",
|
||||||
|
"lib/internal/js_stream_socket.js",
|
||||||
|
"lib/internal/legacy/processbinding.js",
|
||||||
|
"lib/internal/linkedlist.js",
|
||||||
|
"lib/internal/main/check_syntax.js",
|
||||||
|
"lib/internal/main/environment.js",
|
||||||
|
"lib/internal/main/eval_stdin.js",
|
||||||
|
"lib/internal/main/eval_string.js",
|
||||||
|
"lib/internal/main/inspect.js",
|
||||||
|
"lib/internal/main/mksnapshot.js",
|
||||||
|
"lib/internal/main/print_help.js",
|
||||||
|
"lib/internal/main/prof_process.js",
|
||||||
|
"lib/internal/main/repl.js",
|
||||||
|
"lib/internal/main/run_main_module.js",
|
||||||
|
"lib/internal/main/test_runner.js",
|
||||||
|
"lib/internal/main/watch_mode.js",
|
||||||
|
"lib/internal/main/worker_thread.js",
|
||||||
|
"lib/internal/modules/cjs/helpers.js",
|
||||||
|
"lib/internal/modules/cjs/loader.js",
|
||||||
|
"lib/internal/modules/esm/assert.js",
|
||||||
|
"lib/internal/modules/esm/create_dynamic_module.js",
|
||||||
|
"lib/internal/modules/esm/fetch_module.js",
|
||||||
|
"lib/internal/modules/esm/formats.js",
|
||||||
|
"lib/internal/modules/esm/get_format.js",
|
||||||
|
"lib/internal/modules/esm/handle_process_exit.js",
|
||||||
|
"lib/internal/modules/esm/initialize_import_meta.js",
|
||||||
|
"lib/internal/modules/esm/load.js",
|
||||||
|
"lib/internal/modules/esm/loader.js",
|
||||||
|
"lib/internal/modules/esm/module_job.js",
|
||||||
|
"lib/internal/modules/esm/module_map.js",
|
||||||
|
"lib/internal/modules/esm/package_config.js",
|
||||||
|
"lib/internal/modules/esm/resolve.js",
|
||||||
|
"lib/internal/modules/esm/translators.js",
|
||||||
|
"lib/internal/modules/package_json_reader.js",
|
||||||
|
"lib/internal/modules/run_main.js",
|
||||||
|
"lib/internal/net.js",
|
||||||
|
"lib/internal/options.js",
|
||||||
|
"lib/internal/per_context/domexception.js",
|
||||||
|
"lib/internal/per_context/messageport.js",
|
||||||
|
"lib/internal/per_context/primordials.js",
|
||||||
"lib/internal/perf/event_loop_delay.js",
|
"lib/internal/perf/event_loop_delay.js",
|
||||||
"lib/internal/perf/event_loop_utilization.js",
|
"lib/internal/perf/event_loop_utilization.js",
|
||||||
|
"lib/internal/perf/nodetiming.js",
|
||||||
|
"lib/internal/perf/observe.js",
|
||||||
|
"lib/internal/perf/performance.js",
|
||||||
|
"lib/internal/perf/performance_entry.js",
|
||||||
|
"lib/internal/perf/resource_timing.js",
|
||||||
|
"lib/internal/perf/timerify.js",
|
||||||
|
"lib/internal/perf/usertiming.js",
|
||||||
|
"lib/internal/perf/utils.js",
|
||||||
"lib/internal/policy/manifest.js",
|
"lib/internal/policy/manifest.js",
|
||||||
"lib/internal/policy/sri.js",
|
"lib/internal/policy/sri.js",
|
||||||
"lib/internal/process/task_queues.js",
|
"lib/internal/priority_queue.js",
|
||||||
"lib/internal/process/per_thread.js",
|
|
||||||
"lib/internal/process/warning.js",
|
|
||||||
"lib/internal/process/policy.js",
|
|
||||||
"lib/internal/process/promises.js",
|
|
||||||
"lib/internal/process/signal.js",
|
|
||||||
"lib/internal/process/execution.js",
|
|
||||||
"lib/internal/process/esm_loader.js",
|
"lib/internal/process/esm_loader.js",
|
||||||
|
"lib/internal/process/execution.js",
|
||||||
|
"lib/internal/process/per_thread.js",
|
||||||
|
"lib/internal/process/policy.js",
|
||||||
|
"lib/internal/process/pre_execution.js",
|
||||||
|
"lib/internal/process/promises.js",
|
||||||
"lib/internal/process/report.js",
|
"lib/internal/process/report.js",
|
||||||
|
"lib/internal/process/signal.js",
|
||||||
|
"lib/internal/process/task_queues.js",
|
||||||
|
"lib/internal/process/warning.js",
|
||||||
"lib/internal/process/worker_thread_only.js",
|
"lib/internal/process/worker_thread_only.js",
|
||||||
"lib/internal/console/constructor.js",
|
"lib/internal/promise_hooks.js",
|
||||||
"lib/internal/console/global.js",
|
"lib/internal/querystring.js",
|
||||||
"lib/assert/strict.js",
|
"lib/internal/readline/callbacks.js",
|
||||||
"lib/dns/promises.js",
|
"lib/internal/readline/emitKeypressEvents.js",
|
||||||
"lib/fs/promises.js"
|
"lib/internal/readline/interface.js",
|
||||||
|
"lib/internal/readline/promises.js",
|
||||||
|
"lib/internal/readline/utils.js",
|
||||||
|
"lib/internal/repl.js",
|
||||||
|
"lib/internal/repl/await.js",
|
||||||
|
"lib/internal/repl/history.js",
|
||||||
|
"lib/internal/repl/utils.js",
|
||||||
|
"lib/internal/socket_list.js",
|
||||||
|
"lib/internal/socketaddress.js",
|
||||||
|
"lib/internal/source_map/prepare_stack_trace.js",
|
||||||
|
"lib/internal/source_map/source_map.js",
|
||||||
|
"lib/internal/source_map/source_map_cache.js",
|
||||||
|
"lib/internal/stream_base_commons.js",
|
||||||
|
"lib/internal/streams/add-abort-signal.js",
|
||||||
|
"lib/internal/streams/buffer_list.js",
|
||||||
|
"lib/internal/streams/compose.js",
|
||||||
|
"lib/internal/streams/destroy.js",
|
||||||
|
"lib/internal/streams/duplex.js",
|
||||||
|
"lib/internal/streams/duplexify.js",
|
||||||
|
"lib/internal/streams/end-of-stream.js",
|
||||||
|
"lib/internal/streams/from.js",
|
||||||
|
"lib/internal/streams/lazy_transform.js",
|
||||||
|
"lib/internal/streams/legacy.js",
|
||||||
|
"lib/internal/streams/operators.js",
|
||||||
|
"lib/internal/streams/passthrough.js",
|
||||||
|
"lib/internal/streams/pipeline.js",
|
||||||
|
"lib/internal/streams/readable.js",
|
||||||
|
"lib/internal/streams/state.js",
|
||||||
|
"lib/internal/streams/transform.js",
|
||||||
|
"lib/internal/streams/utils.js",
|
||||||
|
"lib/internal/streams/writable.js",
|
||||||
|
"lib/internal/structured_clone.js",
|
||||||
|
"lib/internal/test/binding.js",
|
||||||
|
"lib/internal/test/transfer.js",
|
||||||
|
"lib/internal/test_runner/harness.js",
|
||||||
|
"lib/internal/test_runner/runner.js",
|
||||||
|
"lib/internal/test_runner/tap_stream.js",
|
||||||
|
"lib/internal/test_runner/test.js",
|
||||||
|
"lib/internal/test_runner/utils.js",
|
||||||
|
"lib/internal/timers.js",
|
||||||
|
"lib/internal/tls/secure-context.js",
|
||||||
|
"lib/internal/tls/secure-pair.js",
|
||||||
|
"lib/internal/trace_events_async_hooks.js",
|
||||||
|
"lib/internal/tty.js",
|
||||||
|
"lib/internal/url.js",
|
||||||
|
"lib/internal/util.js",
|
||||||
|
"lib/internal/util/colors.js",
|
||||||
|
"lib/internal/util/comparisons.js",
|
||||||
|
"lib/internal/util/debuglog.js",
|
||||||
|
"lib/internal/util/inspect.js",
|
||||||
|
"lib/internal/util/inspector.js",
|
||||||
|
"lib/internal/util/iterable_weak_map.js",
|
||||||
|
"lib/internal/util/parse_args/parse_args.js",
|
||||||
|
"lib/internal/util/parse_args/utils.js",
|
||||||
|
"lib/internal/util/types.js",
|
||||||
|
"lib/internal/v8/startup_snapshot.js",
|
||||||
|
"lib/internal/v8_prof_polyfill.js",
|
||||||
|
"lib/internal/v8_prof_processor.js",
|
||||||
|
"lib/internal/validators.js",
|
||||||
|
"lib/internal/vm/module.js",
|
||||||
|
"lib/internal/wasm_web_api.js",
|
||||||
|
"lib/internal/watch_mode/files_watcher.js",
|
||||||
|
"lib/internal/watchdog.js",
|
||||||
|
"lib/internal/webstreams/adapters.js",
|
||||||
|
"lib/internal/webstreams/compression.js",
|
||||||
|
"lib/internal/webstreams/encoding.js",
|
||||||
|
"lib/internal/webstreams/queuingstrategies.js",
|
||||||
|
"lib/internal/webstreams/readablestream.js",
|
||||||
|
"lib/internal/webstreams/transfer.js",
|
||||||
|
"lib/internal/webstreams/transformstream.js",
|
||||||
|
"lib/internal/webstreams/util.js",
|
||||||
|
"lib/internal/webstreams/writablestream.js",
|
||||||
|
"lib/internal/worker.js",
|
||||||
|
"lib/internal/worker/io.js",
|
||||||
|
"lib/internal/worker/js_transferable.js",
|
||||||
|
"lib/module.js",
|
||||||
|
"lib/net.js",
|
||||||
|
"lib/os.js",
|
||||||
|
"lib/path.js",
|
||||||
|
"lib/path/posix.js",
|
||||||
|
"lib/path/win32.js",
|
||||||
|
"lib/perf_hooks.js",
|
||||||
|
"lib/process.js",
|
||||||
|
"lib/punycode.js",
|
||||||
|
"lib/querystring.js",
|
||||||
|
"lib/readline.js",
|
||||||
|
"lib/readline/promises.js",
|
||||||
|
"lib/repl.js",
|
||||||
|
"lib/stream.js",
|
||||||
|
"lib/stream/consumers.js",
|
||||||
|
"lib/stream/promises.js",
|
||||||
|
"lib/stream/web.js",
|
||||||
|
"lib/string_decoder.js",
|
||||||
|
"lib/sys.js",
|
||||||
|
"lib/test.js",
|
||||||
|
"lib/timers.js",
|
||||||
|
"lib/timers/promises.js",
|
||||||
|
"lib/tls.js",
|
||||||
|
"lib/trace_events.js",
|
||||||
|
"lib/tty.js",
|
||||||
|
"lib/url.js",
|
||||||
|
"lib/util.js",
|
||||||
|
"lib/util/types.js",
|
||||||
|
"lib/v8.js",
|
||||||
|
"lib/vm.js",
|
||||||
|
"lib/wasi.js",
|
||||||
|
"lib/worker_threads.js",
|
||||||
|
"lib/zlib.js"
|
||||||
],
|
],
|
||||||
"node_module_version": 93,
|
"node_module_version": 108,
|
||||||
"node_no_browser_globals": "false",
|
"node_no_browser_globals": "false",
|
||||||
"node_prefix": "/",
|
"node_prefix": "/",
|
||||||
"node_release_urlbase": "https://nodejs.org/download/release/",
|
"node_release_urlbase": "https://nodejs.org/download/release/",
|
||||||
@@ -330,20 +353,22 @@
|
|||||||
"node_use_v8_platform": "true",
|
"node_use_v8_platform": "true",
|
||||||
"node_with_ltcg": "false",
|
"node_with_ltcg": "false",
|
||||||
"node_without_node_options": "false",
|
"node_without_node_options": "false",
|
||||||
"openssl_fips": "",
|
|
||||||
"openssl_is_fips": "false",
|
"openssl_is_fips": "false",
|
||||||
"openssl_quic": "true",
|
"openssl_quic": "true",
|
||||||
"ossfuzz": "false",
|
"ossfuzz": "false",
|
||||||
"shlib_suffix": "93.dylib",
|
"shlib_suffix": "108.dylib",
|
||||||
"target_arch": "x64",
|
"target_arch": "x64",
|
||||||
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
||||||
"v8_enable_gdbjit": 0,
|
"v8_enable_gdbjit": 0,
|
||||||
"v8_enable_hugepage": 0,
|
"v8_enable_hugepage": 0,
|
||||||
"v8_enable_i18n_support": 1,
|
"v8_enable_i18n_support": 1,
|
||||||
"v8_enable_inspector": 1,
|
"v8_enable_inspector": 1,
|
||||||
|
"v8_enable_javascript_promise_hooks": 1,
|
||||||
"v8_enable_lite_mode": 0,
|
"v8_enable_lite_mode": 0,
|
||||||
"v8_enable_object_print": 1,
|
"v8_enable_object_print": 1,
|
||||||
"v8_enable_pointer_compression": 0,
|
"v8_enable_pointer_compression": 0,
|
||||||
|
"v8_enable_shared_ro_heap": 1,
|
||||||
|
"v8_enable_short_builtin_calls": 1,
|
||||||
"v8_enable_webassembly": 1,
|
"v8_enable_webassembly": 1,
|
||||||
"v8_no_strict_aliasing": 1,
|
"v8_no_strict_aliasing": 1,
|
||||||
"v8_optimized_debug": 1,
|
"v8_optimized_debug": 1,
|
||||||
@@ -353,8 +378,8 @@
|
|||||||
"v8_use_siphash": 1,
|
"v8_use_siphash": 1,
|
||||||
"want_separate_host_toolset": 0,
|
"want_separate_host_toolset": 0,
|
||||||
"xcode_version": "11.0",
|
"xcode_version": "11.0",
|
||||||
"nodedir": "/Users/karolsojko/Library/Caches/node-gyp/16.15.1",
|
"nodedir": "/Users/karolsojko/Library/Caches/node-gyp/18.12.1",
|
||||||
"standalone_static_library": 1,
|
"standalone_static_library": 1,
|
||||||
"user_agent": "yarn/3.2.1 npm/? node/v16.15.1 darwin x64"
|
"user_agent": "yarn/4.0.0-rc.25 npm/? node/v18.12.1 darwin x64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+20
-20
@@ -25,7 +25,7 @@ DEFS_Debug := \
|
|||||||
CFLAGS_Debug := \
|
CFLAGS_Debug := \
|
||||||
-O0 \
|
-O0 \
|
||||||
-gdwarf-2 \
|
-gdwarf-2 \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-Wall \
|
-Wall \
|
||||||
-Wendif-labels \
|
-Wendif-labels \
|
||||||
@@ -38,7 +38,7 @@ CFLAGS_C_Debug := \
|
|||||||
|
|
||||||
# Flags passed to only C++ files.
|
# Flags passed to only C++ files.
|
||||||
CFLAGS_CC_Debug := \
|
CFLAGS_CC_Debug := \
|
||||||
-std=gnu++14 \
|
-std=gnu++17 \
|
||||||
-stdlib=libc++ \
|
-stdlib=libc++ \
|
||||||
-fno-rtti \
|
-fno-rtti \
|
||||||
-fno-exceptions \
|
-fno-exceptions \
|
||||||
@@ -51,13 +51,13 @@ CFLAGS_OBJC_Debug :=
|
|||||||
CFLAGS_OBJCC_Debug :=
|
CFLAGS_OBJCC_Debug :=
|
||||||
|
|
||||||
INCS_Debug := \
|
INCS_Debug := \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||||
-I$(srcdir)/src \
|
-I$(srcdir)/src \
|
||||||
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ DEFS_Release := \
|
|||||||
CFLAGS_Release := \
|
CFLAGS_Release := \
|
||||||
-O3 \
|
-O3 \
|
||||||
-gdwarf-2 \
|
-gdwarf-2 \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-Wall \
|
-Wall \
|
||||||
-Wendif-labels \
|
-Wendif-labels \
|
||||||
@@ -94,7 +94,7 @@ CFLAGS_C_Release := \
|
|||||||
|
|
||||||
# Flags passed to only C++ files.
|
# Flags passed to only C++ files.
|
||||||
CFLAGS_CC_Release := \
|
CFLAGS_CC_Release := \
|
||||||
-std=gnu++14 \
|
-std=gnu++17 \
|
||||||
-stdlib=libc++ \
|
-stdlib=libc++ \
|
||||||
-fno-rtti \
|
-fno-rtti \
|
||||||
-fno-exceptions \
|
-fno-exceptions \
|
||||||
@@ -107,13 +107,13 @@ CFLAGS_OBJC_Release :=
|
|||||||
CFLAGS_OBJCC_Release :=
|
CFLAGS_OBJCC_Release :=
|
||||||
|
|
||||||
INCS_Release := \
|
INCS_Release := \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||||
-I$(srcdir)/src \
|
-I$(srcdir)/src \
|
||||||
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
|
|||||||
LDFLAGS_Debug := \
|
LDFLAGS_Debug := \
|
||||||
-undefined dynamic_lookup \
|
-undefined dynamic_lookup \
|
||||||
-Wl,-search_paths_first \
|
-Wl,-search_paths_first \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-L$(builddir) \
|
-L$(builddir) \
|
||||||
-stdlib=libc++
|
-stdlib=libc++
|
||||||
@@ -163,7 +163,7 @@ LIBTOOLFLAGS_Debug := \
|
|||||||
LDFLAGS_Release := \
|
LDFLAGS_Release := \
|
||||||
-undefined dynamic_lookup \
|
-undefined dynamic_lookup \
|
||||||
-Wl,-search_paths_first \
|
-Wl,-search_paths_first \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-L$(builddir) \
|
-L$(builddir) \
|
||||||
-stdlib=libc++
|
-stdlib=libc++
|
||||||
|
|||||||
+7
-5
@@ -8,7 +8,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
|
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
|
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
|
||||||
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
|
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
|
||||||
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
|
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
|
||||||
|
"lint:revisions": "yarn workspace @standardnotes/revisions-server lint",
|
||||||
"clean": "yarn workspaces foreach -p --verbose run clean",
|
"clean": "yarn workspaces foreach -p --verbose run clean",
|
||||||
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
|
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
|
||||||
"start:auth": "yarn workspace @standardnotes/auth-server start",
|
"start:auth": "yarn workspace @standardnotes/auth-server start",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
|
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
|
||||||
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
|
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
|
||||||
"start:analytics": "yarn workspace @standardnotes/analytics worker",
|
"start:analytics": "yarn workspace @standardnotes/analytics worker",
|
||||||
|
"start:revisions": "yarn workspace @standardnotes/revisions-server start",
|
||||||
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
|
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
|
||||||
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||||
@@ -46,8 +48,8 @@
|
|||||||
"@lerna-lite/list": "^1.5.1",
|
"@lerna-lite/list": "^1.5.1",
|
||||||
"@lerna-lite/run": "^1.5.1",
|
"@lerna-lite/run": "^1.5.1",
|
||||||
"@types/jest": "^29.1.1",
|
"@types/jest": "^29.1.1",
|
||||||
"@types/newrelic": "^7.0.3",
|
"@types/newrelic": "^7.0.4",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.11.9",
|
||||||
"@typescript-eslint/parser": "^5.40.1",
|
"@typescript-eslint/parser": "^5.40.1",
|
||||||
"eslint": "^8.17.0",
|
"eslint": "^8.17.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
@@ -59,7 +61,7 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "yarn@4.0.0-rc.25",
|
"packageManager": "yarn@4.0.0-rc.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"newrelic": "^9.0.0"
|
"newrelic": "^9.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,373 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.12.25](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.24...@standardnotes/analytics@2.12.25) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.23...@standardnotes/analytics@2.12.24) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** daily analytics report template ([41906ec](https://github.com/standardnotes/server/commit/41906ec2f9fd4d605b1c002826173e14fb534e00))
|
||||||
|
|
||||||
|
## [2.12.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.22...@standardnotes/analytics@2.12.23) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.21...@standardnotes/analytics@2.12.22) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** report event publishing ([a605660](https://github.com/standardnotes/server/commit/a6056600eb96bf175189ad6d62870c9d736f331b))
|
||||||
|
|
||||||
|
## [2.12.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.20...@standardnotes/analytics@2.12.21) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add debug logs for report ([48c0cb5](https://github.com/standardnotes/server/commit/48c0cb5e62dc8af930de191deaa1eb3ff6c5a29f))
|
||||||
|
|
||||||
|
## [2.12.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.19...@standardnotes/analytics@2.12.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.18...@standardnotes/analytics@2.12.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.17...@standardnotes/analytics@2.12.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.16...@standardnotes/analytics@2.12.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.15...@standardnotes/analytics@2.12.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.14...@standardnotes/analytics@2.12.15) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.13...@standardnotes/analytics@2.12.14) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.12...@standardnotes/analytics@2.12.13) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.11...@standardnotes/analytics@2.12.12) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.10...@standardnotes/analytics@2.12.11) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.9...@standardnotes/analytics@2.12.10) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.8...@standardnotes/analytics@2.12.9) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.7...@standardnotes/analytics@2.12.8) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.6...@standardnotes/analytics@2.12.7) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.5...@standardnotes/analytics@2.12.6) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.4...@standardnotes/analytics@2.12.5) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.3...@standardnotes/analytics@2.12.4) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.2...@standardnotes/analytics@2.12.3) (2022-12-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.1...@standardnotes/analytics@2.12.2) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.0...@standardnotes/analytics@2.12.1) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
# [2.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.17...@standardnotes/analytics@2.12.0) (2022-12-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-core:** distinguish between username and email ([06fd404](https://github.com/standardnotes/server/commit/06fd404d44b44a53733f889aabd4da63f21e2f36))
|
||||||
|
|
||||||
|
## [2.11.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.16...@standardnotes/analytics@2.11.17) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.15...@standardnotes/analytics@2.11.16) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.13...@standardnotes/analytics@2.11.14) (2022-11-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.12...@standardnotes/analytics@2.11.13) (2022-11-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.11...@standardnotes/analytics@2.11.12) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.10...@standardnotes/analytics@2.11.11) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.9...@standardnotes/analytics@2.11.10) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.8...@standardnotes/analytics@2.11.9) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.7...@standardnotes/analytics@2.11.8) (2022-11-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.6...@standardnotes/analytics@2.11.7) (2022-11-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* binding of sns and sqs with additional config ([74bc791](https://github.com/standardnotes/server/commit/74bc79116bc50d9a5af1a558db1b7108dcda6d0e))
|
||||||
|
|
||||||
|
## [2.11.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.5...@standardnotes/analytics@2.11.6) (2022-11-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.4...@standardnotes/analytics@2.11.5) (2022-11-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.3...@standardnotes/analytics@2.11.4) (2022-11-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.2...@standardnotes/analytics@2.11.3) (2022-11-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.1...@standardnotes/analytics@2.11.2) (2022-11-18)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** specs ([507d43b](https://github.com/standardnotes/server/commit/507d43b3289d1e178644df6d3e15d1d55e56c7bb))
|
||||||
|
|
||||||
|
## [2.11.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.0...@standardnotes/analytics@2.11.1) (2022-11-18)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mapper interface imports ([1ec0723](https://github.com/standardnotes/server/commit/1ec072373d640c4e2f24b9bb12fec0c678b48032))
|
||||||
|
|
||||||
|
# [2.11.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.3...@standardnotes/analytics@2.11.0) (2022-11-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add publishing churn calculation values in the report ([6c43a33](https://github.com/standardnotes/server/commit/6c43a331d09c2dcf1300742509da6a1d8ef2f5b7))
|
||||||
|
|
||||||
|
## [2.10.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.2...@standardnotes/analytics@2.10.3) (2022-11-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** exclude five year plans from mrr stats ([fe1b2a0](https://github.com/standardnotes/server/commit/fe1b2a0e0744417e592f3f61f42610765b416ce6))
|
||||||
|
|
||||||
|
## [2.10.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.1...@standardnotes/analytics@2.10.2) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** imports from domain-core ([15dfd6d](https://github.com/standardnotes/server/commit/15dfd6dcba75a772000eeb01b78a532067b01d5b))
|
||||||
|
|
||||||
|
## [2.10.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.0...@standardnotes/analytics@2.10.1) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** retrieving revisions ([50f7ae3](https://github.com/standardnotes/server/commit/50f7ae338ad66d3465fa16c31e7c47c57b1e0c3c))
|
||||||
|
|
||||||
|
# [2.10.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.9...@standardnotes/analytics@2.10.0) (2022-11-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** extract domain core into a separate package ([0f94e2a](https://github.com/standardnotes/server/commit/0f94e2ad0c8927733eac31f130cbe649dce765f9))
|
||||||
|
|
||||||
|
## [2.9.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.6...@standardnotes/analytics@2.9.9) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** bump version ([8715fe1](https://github.com/standardnotes/server/commit/8715fe182221f67f02b5f49e566366db3db581f4))
|
||||||
|
|
||||||
|
## [2.9.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.5...@standardnotes/analytics@2.9.6) (2022-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.4...@standardnotes/analytics@2.9.5) (2022-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.3...@standardnotes/analytics@2.9.4) (2022-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.2...@standardnotes/analytics@2.9.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add five year plans mrr calculation ([a03c5bc](https://github.com/standardnotes/server/commit/a03c5bceea2a9b166b1d5ad75181021462a86627))
|
||||||
|
|
||||||
|
## [2.9.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.1...@standardnotes/analytics@2.9.2) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add missing period for stats report ([9b593f2](https://github.com/standardnotes/server/commit/9b593f2a6b105ab8f9c7cef8bdda6892c42e20ef))
|
||||||
|
|
||||||
|
## [2.9.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.0...@standardnotes/analytics@2.9.1) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** generate mrr stats for last 30 days including Today ([b92c4ae](https://github.com/standardnotes/server/commit/b92c4ae650b53db5c0bb2a9cf9afb01caeb8d822))
|
||||||
|
|
||||||
|
# [2.9.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.3...@standardnotes/analytics@2.9.0) (2022-11-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add mrr for annual, monthly, pro and plus subscription plans ([ce3e259](https://github.com/standardnotes/server/commit/ce3e259bdedd10796fb4469f0eabd64bc326a115))
|
||||||
|
|
||||||
|
## [2.8.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.2...@standardnotes/analytics@2.8.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add subscription id to error logs ([81be065](https://github.com/standardnotes/server/commit/81be06598c918279f98a8ba6b59ea1b3803c949c))
|
||||||
|
|
||||||
|
## [2.8.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.1...@standardnotes/analytics@2.8.2) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add monthly mrr to the report ([fce47a0](https://github.com/standardnotes/server/commit/fce47a0a37a67b3edf3ea0b6ccda43c54dbd9870))
|
||||||
|
|
||||||
|
## [2.8.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.0...@standardnotes/analytics@2.8.1) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add persisting mrr for this month and this year as well ([8df0482](https://github.com/standardnotes/server/commit/8df0482eb4bfd63b9639fd786c9b6952ad7f801d))
|
||||||
|
|
||||||
|
# [2.8.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.3...@standardnotes/analytics@2.8.0) (2022-11-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add calculating monthly recurring revenue ([77e5065](https://github.com/standardnotes/server/commit/77e50655f6fa7f9c28e13f8b8bc6de246c0454f0))
|
||||||
|
|
||||||
|
## [2.7.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.2...@standardnotes/analytics@2.7.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** arhcitecture arrangements for use case execution ([7393954](https://github.com/standardnotes/server/commit/7393954ff6ece6143f7661104299172548db90ee))
|
||||||
|
|
||||||
|
## [2.7.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.1...@standardnotes/analytics@2.7.2) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** mrr column types ([90aef90](https://github.com/standardnotes/server/commit/90aef905af05b8c1c86c7bd383df6b2b502f7c91))
|
||||||
|
|
||||||
|
## [2.7.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.0...@standardnotes/analytics@2.7.1) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add missing created at column ([89502be](https://github.com/standardnotes/server/commit/89502bed638b17301e42e0d5916635b0a59f585d))
|
||||||
|
|
||||||
|
# [2.7.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.6.0...@standardnotes/analytics@2.7.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription canceled ([52a257a](https://github.com/standardnotes/server/commit/52a257abb16034134a50474fbbb2493a00c58b99))
|
||||||
|
|
||||||
|
# [2.6.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.5.0...@standardnotes/analytics@2.6.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription refunded ([0f65c05](https://github.com/standardnotes/server/commit/0f65c051abcff805e920f91d338e5fadda7905a9))
|
||||||
|
|
||||||
|
# [2.5.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.4.0...@standardnotes/analytics@2.5.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription expired ([5c3db2c](https://github.com/standardnotes/server/commit/5c3db2cb29a929e44b63eb8226ce4ad1d14f8a99))
|
||||||
|
|
||||||
|
# [2.4.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.3.1...@standardnotes/analytics@2.4.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription renewed ([cdb7fcf](https://github.com/standardnotes/server/commit/cdb7fcf8311fecfabe3ef9eb656cd6ec57b87de0))
|
||||||
|
|
||||||
|
## [2.3.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.3.0...@standardnotes/analytics@2.3.1) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** missing injectable annotation ([9d3ef24](https://github.com/standardnotes/server/commit/9d3ef24ba94ad28976a211d40f94f1bce8d0d305))
|
||||||
|
|
||||||
|
# [2.3.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.2.0...@standardnotes/analytics@2.3.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription purchased ([5ea9941](https://github.com/standardnotes/server/commit/5ea9941519ffb3027527130ec869da14abc5e994))
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.1.0...@standardnotes/analytics@2.2.0) (2022-11-08)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add persistence for revenue modifications ([4928685](https://github.com/standardnotes/server/commit/49286851989f557d3b391b6b535a9aa307fbef50))
|
||||||
|
* **analytics:** create new ddd architecture for persisting revenue modifications ([0103233](https://github.com/standardnotes/server/commit/0103233d4a1e222e7c9b059475c1cdc3b2617455))
|
||||||
|
|
||||||
|
# [2.1.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.52.0...@standardnotes/analytics@2.1.0) (2022-11-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove analytics scope from other services in favor of a separate service ([ff1d5db](https://github.com/standardnotes/server/commit/ff1d5db12c93f8e51c07c3aecb9fed4be48ea96a))
|
||||||
|
|
||||||
|
# [1.52.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.51.0...@standardnotes/analytics@1.52.0) (2022-11-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add handling subscription reactivated events ([6359030](https://github.com/standardnotes/server/commit/63590300308975097f4d092a4f140f479093a1ae))
|
||||||
|
|
||||||
|
# [1.51.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.50.0...@standardnotes/analytics@1.51.0) (2022-11-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add handling subscription expired events ([c0f5817](https://github.com/standardnotes/server/commit/c0f5817d4753410ee5d997c1b94e340b400cb5d9))
|
||||||
|
|
||||||
|
# [1.50.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.49.0...@standardnotes/analytics@1.50.0) (2022-11-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add handling subscription purchased events ([f1834d5](https://github.com/standardnotes/server/commit/f1834d58d2215c81322e82a0ec279617103b3260))
|
||||||
|
|
||||||
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.48.0...@standardnotes/analytics@1.49.0) (2022-11-07)
|
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.48.0...@standardnotes/analytics@1.49.0) (2022-11-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16.15.1-alpine
|
FROM node:18.12.1-alpine
|
||||||
|
|
||||||
RUN apk add --update \
|
RUN apk add --update \
|
||||||
curl \
|
curl \
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
|||||||
|
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||||
import { Period } from '../src/Domain/Time/Period'
|
import { Period } from '../src/Domain/Time/Period'
|
||||||
@@ -15,6 +16,9 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
|||||||
import TYPES from '../src/Bootstrap/Types'
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
|
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
const requestReport = async (
|
const requestReport = async (
|
||||||
analyticsStore: AnalyticsStoreInterface,
|
analyticsStore: AnalyticsStoreInterface,
|
||||||
@@ -22,7 +26,12 @@ const requestReport = async (
|
|||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||||
|
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||||
|
timer: TimerInterface,
|
||||||
|
adminEmails: string[],
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
await calculateMonthlyRecurringRevenue.execute({})
|
||||||
|
|
||||||
const analyticsOverTime: Array<{
|
const analyticsOverTime: Array<{
|
||||||
name: string
|
name: string
|
||||||
period: number
|
period: number
|
||||||
@@ -34,8 +43,6 @@ const requestReport = async (
|
|||||||
}> = []
|
}> = []
|
||||||
|
|
||||||
const thirtyDaysAnalyticsNames = [
|
const thirtyDaysAnalyticsNames = [
|
||||||
AnalyticsActivity.GeneralActivity,
|
|
||||||
AnalyticsActivity.EditingItems,
|
|
||||||
AnalyticsActivity.SubscriptionPurchased,
|
AnalyticsActivity.SubscriptionPurchased,
|
||||||
AnalyticsActivity.Register,
|
AnalyticsActivity.Register,
|
||||||
AnalyticsActivity.SubscriptionRenewed,
|
AnalyticsActivity.SubscriptionRenewed,
|
||||||
@@ -80,9 +87,6 @@ const requestReport = async (
|
|||||||
}> = []
|
}> = []
|
||||||
const yesterdayActivityNames = [
|
const yesterdayActivityNames = [
|
||||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||||
AnalyticsActivity.GeneralActivity,
|
|
||||||
AnalyticsActivity.GeneralActivityFreeUsers,
|
|
||||||
AnalyticsActivity.GeneralActivityPaidUsers,
|
|
||||||
AnalyticsActivity.PaymentFailed,
|
AnalyticsActivity.PaymentFailed,
|
||||||
AnalyticsActivity.PaymentSuccess,
|
AnalyticsActivity.PaymentSuccess,
|
||||||
AnalyticsActivity.NewCustomersChurn,
|
AnalyticsActivity.NewCustomersChurn,
|
||||||
@@ -101,6 +105,40 @@ const requestReport = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statisticsOverTime: Array<{
|
||||||
|
name: string
|
||||||
|
period: number
|
||||||
|
counts: Array<{
|
||||||
|
periodKey: string
|
||||||
|
totalCount: number
|
||||||
|
}>
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
const thirtyDaysStatisticsNames = [
|
||||||
|
StatisticsMeasure.MRR,
|
||||||
|
StatisticsMeasure.AnnualPlansMRR,
|
||||||
|
StatisticsMeasure.MonthlyPlansMRR,
|
||||||
|
StatisticsMeasure.FiveYearPlansMRR,
|
||||||
|
StatisticsMeasure.PlusPlansMRR,
|
||||||
|
StatisticsMeasure.ProPlansMRR,
|
||||||
|
]
|
||||||
|
for (const statisticName of thirtyDaysStatisticsNames) {
|
||||||
|
statisticsOverTime.push({
|
||||||
|
name: statisticName,
|
||||||
|
period: Period.Last30DaysIncludingToday,
|
||||||
|
counts: await statisticsStore.calculateTotalCountOverPeriod(statisticName, Period.Last30DaysIncludingToday),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyStatisticsNames = [StatisticsMeasure.MRR]
|
||||||
|
for (const statisticName of monthlyStatisticsNames) {
|
||||||
|
statisticsOverTime.push({
|
||||||
|
name: statisticName,
|
||||||
|
period: Period.ThisYear,
|
||||||
|
counts: await statisticsStore.calculateTotalCountOverPeriod(statisticName, Period.ThisYear),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const statisticMeasureNames = [
|
const statisticMeasureNames = [
|
||||||
StatisticsMeasure.Income,
|
StatisticsMeasure.Income,
|
||||||
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||||
@@ -116,9 +154,6 @@ const requestReport = async (
|
|||||||
StatisticsMeasure.SubscriptionLength,
|
StatisticsMeasure.SubscriptionLength,
|
||||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||||
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||||
StatisticsMeasure.NotesCountFreeUsers,
|
|
||||||
StatisticsMeasure.NotesCountPaidUsers,
|
|
||||||
StatisticsMeasure.FilesCount,
|
|
||||||
StatisticsMeasure.NewCustomers,
|
StatisticsMeasure.NewCustomers,
|
||||||
StatisticsMeasure.TotalCustomers,
|
StatisticsMeasure.TotalCustomers,
|
||||||
]
|
]
|
||||||
@@ -141,33 +176,13 @@ const requestReport = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const periodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.Last7Days)
|
|
||||||
const retentionOverDays: Array<{
|
|
||||||
firstPeriodKey: string
|
|
||||||
secondPeriodKey: string
|
|
||||||
value: number
|
|
||||||
}> = []
|
|
||||||
for (let i = 0; i < periodKeys.length; i++) {
|
|
||||||
for (let j = 0; j < periodKeys.length - i; j++) {
|
|
||||||
const dailyRetention = await analyticsStore.calculateActivitiesRetention({
|
|
||||||
firstActivity: AnalyticsActivity.Register,
|
|
||||||
firstActivityPeriodKey: periodKeys[i],
|
|
||||||
secondActivity: AnalyticsActivity.GeneralActivity,
|
|
||||||
secondActivityPeriodKey: periodKeys[i + j],
|
|
||||||
})
|
|
||||||
|
|
||||||
retentionOverDays.push({
|
|
||||||
firstPeriodKey: periodKeys[i],
|
|
||||||
secondPeriodKey: periodKeys[i + j],
|
|
||||||
value: dailyRetention,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const monthlyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.ThisYear)
|
const monthlyPeriodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.ThisYear)
|
||||||
const churnRates: Array<{
|
const churnRates: Array<{
|
||||||
rate: number
|
rate: number
|
||||||
periodKey: string
|
periodKey: string
|
||||||
|
averageCustomersCount: number
|
||||||
|
existingCustomersChurn: number
|
||||||
|
newCustomersChurn: number
|
||||||
}> = []
|
}> = []
|
||||||
for (const monthPeriodKey of monthlyPeriodKeys) {
|
for (const monthPeriodKey of monthlyPeriodKeys) {
|
||||||
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
|
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
|
||||||
@@ -197,33 +212,35 @@ const requestReport = async (
|
|||||||
churnRates.push({
|
churnRates.push({
|
||||||
periodKey: monthPeriodKey,
|
periodKey: monthPeriodKey,
|
||||||
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
|
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
|
||||||
|
averageCustomersCount,
|
||||||
|
existingCustomersChurn,
|
||||||
|
newCustomersChurn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
for (const adminEmail of adminEmails) {
|
||||||
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
|
await domainEventPublisher.publish(
|
||||||
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
|
domainEventFactory.createEmailRequestedEvent({
|
||||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||||
activityStatistics: yesterdayActivityStatistics,
|
subject: getSubject(),
|
||||||
activityStatisticsOverTime: analyticsOverTime,
|
body: getBody(
|
||||||
statisticMeasures,
|
{
|
||||||
retentionStatistics: [
|
activityStatistics: yesterdayActivityStatistics,
|
||||||
{
|
activityStatisticsOverTime: analyticsOverTime,
|
||||||
firstActivity: AnalyticsActivity.Register,
|
statisticsOverTime,
|
||||||
secondActivity: AnalyticsActivity.GeneralActivity,
|
statisticMeasures,
|
||||||
retention: {
|
churn: {
|
||||||
periodKeys,
|
periodKeys: monthlyPeriodKeys,
|
||||||
values: retentionOverDays,
|
values: churnRates,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
timer,
|
||||||
churn: {
|
),
|
||||||
periodKeys: monthlyPeriodKeys,
|
level: EmailLevel.LEVELS.System,
|
||||||
values: churnRates,
|
userEmail: adminEmail,
|
||||||
},
|
}),
|
||||||
})
|
)
|
||||||
|
}
|
||||||
await domainEventPublisher.publish(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
const container = new ContainerConfigLoader()
|
||||||
@@ -240,9 +257,25 @@ void container.load().then((container) => {
|
|||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||||
|
const timer: TimerInterface = container.get(TYPES.Timer)
|
||||||
|
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
||||||
|
TYPES.CalculateMonthlyRecurringRevenue,
|
||||||
|
)
|
||||||
|
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||||
|
|
||||||
|
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||||
|
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
requestReport(analyticsStore, statisticsStore, domainEventFactory, domainEventPublisher, periodKeyGenerator),
|
requestReport(
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
domainEventFactory,
|
||||||
|
domainEventPublisher,
|
||||||
|
periodKeyGenerator,
|
||||||
|
calculateMonthlyRecurringRevenue,
|
||||||
|
timer,
|
||||||
|
adminEmails,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Usage report generation complete')
|
logger.info('Usage report generation complete')
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/analytics worker
|
yarn workspace @standardnotes/analytics worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'report' )
|
'report' )
|
||||||
echo "Starting Usage Report Generation..."
|
echo "[Docker] Starting Usage Report Generation..."
|
||||||
yarn workspace @standardnotes/analytics report
|
yarn workspace @standardnotes/analytics report
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
|
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addRevenueModifications1667912580964 implements MigrationInterface {
|
||||||
|
name = 'addRevenueModifications1667912580964'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE `revenue_modifications` (`uuid` varchar(36) NOT NULL, `subscription_id` int NOT NULL, `user_email` varchar(255) NOT NULL, `user_uuid` varchar(36) NOT NULL, `event_type` varchar(255) NOT NULL, `subscription_plan` varchar(255) NOT NULL, `billing_frequency` int NOT NULL, `new_customer` tinyint NOT NULL, `previous_mrr` int NOT NULL, `new_mrr` int NOT NULL, INDEX `email` (`user_email`), INDEX `user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX `user_uuid` ON `revenue_modifications`')
|
||||||
|
await queryRunner.query('DROP INDEX `email` ON `revenue_modifications`')
|
||||||
|
await queryRunner.query('DROP TABLE `revenue_modifications`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addMissingCreatedAt1667994036734 implements MigrationInterface {
|
||||||
|
name = 'addMissingCreatedAt1667994036734'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `created_at` bigint NOT NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `created_at`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class fixMrrFloatingColumns1667995681714 implements MigrationInterface {
|
||||||
|
name = 'fixMrrFloatingColumns1667995681714'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `previous_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `previous_mrr` float NOT NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `new_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `new_mrr` float NOT NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `new_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `new_mrr` int NOT NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `previous_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `previous_mrr` int NOT NULL')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "1.49.0",
|
"version": "2.12.25",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0 <17.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
"private": true,
|
||||||
"description": "Analytics tools for Standard Notes projects",
|
"description": "Analytics tools for Standard Notes projects",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"author": "Standard Notes",
|
"author": "Standard Notes",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
"files": [
|
|
||||||
"dist/src/**/*.js",
|
|
||||||
"dist/src/**/*.d.ts"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -28,10 +25,10 @@
|
|||||||
"typeorm": "typeorm-ts-node-commonjs"
|
"typeorm": "typeorm-ts-node-commonjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^5.0.0",
|
||||||
"@types/jest": "^29.1.1",
|
"@types/jest": "^29.1.1",
|
||||||
"@types/newrelic": "^7.0.3",
|
"@types/newrelic": "^7.0.4",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.11.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
@@ -41,20 +38,21 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
"aws-sdk": "^2.1158.0",
|
"aws-sdk": "^2.1260.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"inversify": "^6.0.1",
|
"inversify": "^6.0.1",
|
||||||
"ioredis": "^5.2.3",
|
"ioredis": "^5.2.4",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"newrelic": "^9.0.0",
|
"newrelic": "^9.6.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"typeorm": "^0.3.6",
|
"typeorm": "^0.3.10",
|
||||||
"winston": "^3.8.1"
|
"winston": "^3.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DomainEventMessageHandlerInterface,
|
DomainEventMessageHandlerInterface,
|
||||||
DomainEventSubscriberFactoryInterface,
|
DomainEventSubscriberFactoryInterface,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
|
import { MapperInterface } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
import TYPES from './Types'
|
import TYPES from './Types'
|
||||||
@@ -40,6 +41,17 @@ import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEven
|
|||||||
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
||||||
import { SubscriptionRenewedEventHandler } from '../Domain/Handler/SubscriptionRenewedEventHandler'
|
import { SubscriptionRenewedEventHandler } from '../Domain/Handler/SubscriptionRenewedEventHandler'
|
||||||
import { SubscriptionRefundedEventHandler } from '../Domain/Handler/SubscriptionRefundedEventHandler'
|
import { SubscriptionRefundedEventHandler } from '../Domain/Handler/SubscriptionRefundedEventHandler'
|
||||||
|
import { SubscriptionPurchasedEventHandler } from '../Domain/Handler/SubscriptionPurchasedEventHandler'
|
||||||
|
import { SubscriptionExpiredEventHandler } from '../Domain/Handler/SubscriptionExpiredEventHandler'
|
||||||
|
import { SubscriptionReactivatedEventHandler } from '../Domain/Handler/SubscriptionReactivatedEventHandler'
|
||||||
|
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository'
|
||||||
|
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
import { RevenueModification } from '../Domain/Revenue/RevenueModification'
|
||||||
|
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
|
||||||
|
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { CalculateMonthlyRecurringRevenue } from '../Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
@@ -77,13 +89,24 @@ export class ContainerConfigLoader {
|
|||||||
})
|
})
|
||||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||||
|
|
||||||
if (env.get('SNS_AWS_REGION', true)) {
|
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||||
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
|
const snsConfig: AWS.SNS.Types.ClientConfiguration = {
|
||||||
new AWS.SNS({
|
apiVersion: 'latest',
|
||||||
apiVersion: 'latest',
|
region: env.get('SNS_AWS_REGION', true),
|
||||||
region: env.get('SNS_AWS_REGION', true),
|
}
|
||||||
}),
|
if (env.get('SNS_ENDPOINT', true)) {
|
||||||
)
|
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
|
||||||
|
}
|
||||||
|
if (env.get('SNS_DISABLE_SSL', true) === 'true') {
|
||||||
|
snsConfig.sslEnabled = false
|
||||||
|
}
|
||||||
|
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
|
||||||
|
snsConfig.credentials = {
|
||||||
|
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
|
||||||
|
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(new AWS.SNS(snsConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
@@ -107,19 +130,30 @@ export class ContainerConfigLoader {
|
|||||||
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
||||||
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||||
|
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
container
|
container
|
||||||
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
|
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
|
||||||
.to(MySQLAnalyticsEntityRepository)
|
.to(MySQLAnalyticsEntityRepository)
|
||||||
|
container
|
||||||
|
.bind<RevenueModificationRepositoryInterface>(TYPES.RevenueModificationRepository)
|
||||||
|
.to(MySQLRevenueModificationRepository)
|
||||||
|
|
||||||
// ORM
|
// ORM
|
||||||
container
|
container
|
||||||
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
|
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
|
||||||
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
|
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
|
||||||
|
container
|
||||||
|
.bind<Repository<TypeORMRevenueModification>>(TYPES.ORMRevenueModificationRepository)
|
||||||
|
.toConstantValue(AppDataSource.getRepository(TypeORMRevenueModification))
|
||||||
|
|
||||||
// Use Case
|
// Use Case
|
||||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||||
|
container.bind<SaveRevenueModification>(TYPES.SaveRevenueModification).to(SaveRevenueModification)
|
||||||
|
container
|
||||||
|
.bind<CalculateMonthlyRecurringRevenue>(TYPES.CalculateMonthlyRecurringRevenue)
|
||||||
|
.to(CalculateMonthlyRecurringRevenue)
|
||||||
|
|
||||||
// Hanlders
|
// Hanlders
|
||||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||||
@@ -137,6 +171,21 @@ export class ContainerConfigLoader {
|
|||||||
container
|
container
|
||||||
.bind<SubscriptionRefundedEventHandler>(TYPES.SubscriptionRefundedEventHandler)
|
.bind<SubscriptionRefundedEventHandler>(TYPES.SubscriptionRefundedEventHandler)
|
||||||
.to(SubscriptionRefundedEventHandler)
|
.to(SubscriptionRefundedEventHandler)
|
||||||
|
container
|
||||||
|
.bind<SubscriptionPurchasedEventHandler>(TYPES.SubscriptionPurchasedEventHandler)
|
||||||
|
.to(SubscriptionPurchasedEventHandler)
|
||||||
|
container
|
||||||
|
.bind<SubscriptionExpiredEventHandler>(TYPES.SubscriptionExpiredEventHandler)
|
||||||
|
.to(SubscriptionExpiredEventHandler)
|
||||||
|
container
|
||||||
|
.bind<SubscriptionReactivatedEventHandler>(TYPES.SubscriptionReactivatedEventHandler)
|
||||||
|
.to(SubscriptionReactivatedEventHandler)
|
||||||
|
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||||
|
|
||||||
|
// Maps
|
||||||
|
container
|
||||||
|
.bind<MapperInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
||||||
|
.to(RevenueModificationMap)
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||||
@@ -163,6 +212,16 @@ export class ContainerConfigLoader {
|
|||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||||
|
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
||||||
|
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
|
||||||
|
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
|
||||||
|
['SUBSCRIPTION_CANCELLED', container.get(TYPES.SubscriptionCancelledEventHandler)],
|
||||||
|
['SUBSCRIPTION_RENEWED', container.get(TYPES.SubscriptionRenewedEventHandler)],
|
||||||
|
['SUBSCRIPTION_REFUNDED', container.get(TYPES.SubscriptionRefundedEventHandler)],
|
||||||
|
['SUBSCRIPTION_PURCHASED', container.get(TYPES.SubscriptionPurchasedEventHandler)],
|
||||||
|
['SUBSCRIPTION_EXPIRED', container.get(TYPES.SubscriptionExpiredEventHandler)],
|
||||||
|
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.SubscriptionReactivatedEventHandler)],
|
||||||
|
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DataSource, LoggerOptions } from 'typeorm'
|
import { DataSource, LoggerOptions } from 'typeorm'
|
||||||
|
|
||||||
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
|
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
|
||||||
|
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ export const AppDataSource = new DataSource({
|
|||||||
],
|
],
|
||||||
removeNodeErrorCount: 10,
|
removeNodeErrorCount: 10,
|
||||||
},
|
},
|
||||||
entities: [AnalyticsEntity],
|
entities: [AnalyticsEntity, TypeORMRevenueModification],
|
||||||
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ const TYPES = {
|
|||||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||||
|
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||||
// Repositories
|
// Repositories
|
||||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||||
|
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||||
// ORM
|
// ORM
|
||||||
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
||||||
|
ORMRevenueModificationRepository: Symbol.for('ORMRevenueModificationRepository'),
|
||||||
// Use Case
|
// Use Case
|
||||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||||
|
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
||||||
|
CalculateMonthlyRecurringRevenue: Symbol.for('CalculateMonthlyRecurringRevenue'),
|
||||||
// Handlers
|
// Handlers
|
||||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||||
@@ -25,6 +30,12 @@ const TYPES = {
|
|||||||
SubscriptionCancelledEventHandler: Symbol.for('SubscriptionCancelledEventHandler'),
|
SubscriptionCancelledEventHandler: Symbol.for('SubscriptionCancelledEventHandler'),
|
||||||
SubscriptionRenewedEventHandler: Symbol.for('SubscriptionRenewedEventHandler'),
|
SubscriptionRenewedEventHandler: Symbol.for('SubscriptionRenewedEventHandler'),
|
||||||
SubscriptionRefundedEventHandler: Symbol.for('SubscriptionRefundedEventHandler'),
|
SubscriptionRefundedEventHandler: Symbol.for('SubscriptionRefundedEventHandler'),
|
||||||
|
SubscriptionPurchasedEventHandler: Symbol.for('SubscriptionPurchasedEventHandler'),
|
||||||
|
SubscriptionExpiredEventHandler: Symbol.for('SubscriptionExpiredEventHandler'),
|
||||||
|
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
|
||||||
|
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||||
|
// Maps
|
||||||
|
RevenueModificationMap: Symbol.for('RevenueModificationMap'),
|
||||||
// Services
|
// Services
|
||||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
export enum AnalyticsActivity {
|
export enum AnalyticsActivity {
|
||||||
GeneralActivity = 'general-activity',
|
|
||||||
GeneralActivityFreeUsers = 'general-activity-free-users',
|
|
||||||
GeneralActivityPaidUsers = 'general-activity-paid-users',
|
|
||||||
EditingItems = 'editing-items',
|
|
||||||
CheckingIntegrity = 'checking-integrity',
|
|
||||||
Login = 'login',
|
|
||||||
Register = 'register',
|
Register = 'register',
|
||||||
DeleteAccount = 'DeleteAccount',
|
DeleteAccount = 'DeleteAccount',
|
||||||
SubscriptionPurchased = 'subscription-purchased',
|
SubscriptionPurchased = 'subscription-purchased',
|
||||||
@@ -13,8 +7,6 @@ export enum AnalyticsActivity {
|
|||||||
SubscriptionCancelled = 'subscription-cancelled',
|
SubscriptionCancelled = 'subscription-cancelled',
|
||||||
SubscriptionExpired = 'subscription-expired',
|
SubscriptionExpired = 'subscription-expired',
|
||||||
SubscriptionReactivated = 'subscription-reactivated',
|
SubscriptionReactivated = 'subscription-reactivated',
|
||||||
EmailUnbackedUpData = 'email-unbacked-up-data',
|
|
||||||
EmailBackup = 'email-backup',
|
|
||||||
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',
|
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',
|
||||||
PaymentFailed = 'payment-failed',
|
PaymentFailed = 'payment-failed',
|
||||||
PaymentSuccess = 'payment-success',
|
PaymentSuccess = 'payment-success',
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { html } from './daily-analytics-report.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return `Daily analytics report ${new Date().toLocaleDateString('en-US')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(data: unknown, timer: TimerInterface): string {
|
||||||
|
return html(data, timer)
|
||||||
|
}
|
||||||
@@ -0,0 +1,966 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
|
const getChartUrls = (
|
||||||
|
data: any,
|
||||||
|
): {
|
||||||
|
subscriptions: string
|
||||||
|
users: string
|
||||||
|
quarterlyPerformance: string
|
||||||
|
churn: string
|
||||||
|
mrr: string
|
||||||
|
mrrMonthly: string
|
||||||
|
} => {
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionsLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: subscriptionPurchasingOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: subscriptionPurchasingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: subscriptionRenewingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Refunds',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: subscriptionRefundingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Cancels',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: subscriptionCancelledOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Reactivations',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: subscriptionReactivatedOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const usersLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: userRegistrationOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: userRegistrationOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Account Deletions',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: userDeletionOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarters = [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]
|
||||||
|
const quarterlyUserRegistrations = []
|
||||||
|
const quarterlySubscriptionPurchases = []
|
||||||
|
const quarterlySubscriptionRenewals = []
|
||||||
|
for (const quarter of quarters) {
|
||||||
|
const registrations =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const purchases =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const renewals =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
quarterlyUserRegistrations.push(registrations)
|
||||||
|
quarterlySubscriptionPurchases.push(purchases)
|
||||||
|
quarterlySubscriptionRenewals.push(renewals)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarterlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlyUserRegistrations,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionPurchases,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionRenewals,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Quarterly Performance',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyChurnRates = data.churn.values.map((value: { rate: number }) => +value.rate.toFixed(2))
|
||||||
|
|
||||||
|
const churnConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Churn Percent',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: monthlyChurnRates,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly Churn Rate',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Monthly Plans',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Annual Plans',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Five Year Plans',
|
||||||
|
backgroundColor: 'rgb(255, 120, 120)',
|
||||||
|
borderColor: 'rgb(255, 120, 120)',
|
||||||
|
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PRO Plans',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PLUS Plans',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||||
|
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||||
|
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||||
|
|
||||||
|
const mrrMonthlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgba(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: mrrMonthlyOverTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly MRR',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptions: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(subscriptionsLinerOverTimeConfig),
|
||||||
|
)}`,
|
||||||
|
users: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(usersLinerOverTimeConfig))}`,
|
||||||
|
quarterlyPerformance: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(quarterlyConfig),
|
||||||
|
)}`,
|
||||||
|
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||||
|
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||||
|
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const html = (data: any, timer: TimerInterface) => {
|
||||||
|
const chartUrls = getChartUrls(data)
|
||||||
|
|
||||||
|
const successfullPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentSuccess && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const failedPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentFailed && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const limitedDiscountPurchasedActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.LimitedDiscountOfferPurchased && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const incomeMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const refundMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
|
||||||
|
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
|
||||||
|
const revenueYesterday = incomeYesterday - refundsYesterday
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageYesterday = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const incomeMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const refundMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const revenueThisMonth = incomeThisMonth - refundsThisMonth
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const today = new Date()
|
||||||
|
const thisMonthPeriodKey = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString()}`
|
||||||
|
const thisMonthChurn = data.churn.values.find(
|
||||||
|
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ` <div>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from yesterday:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueYesterday.toLocaleString('en-US')}</b> (Income: $
|
||||||
|
${incomeYesterday.toLocaleString('en-US')}, Refunds: $${refundsYesterday.toLocaleString('en-US')})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Successfull payments: <b>${successfullPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Failed payments: <b>${failedPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>MRR Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Total:</b> $${mrrOverTime?.counts[mrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Subscription Type:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>PLUS:</b> $
|
||||||
|
${plusPlansMrrOverTime?.counts[plusPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>PRO:</b> $
|
||||||
|
${proPlansMrrOverTime?.counts[proPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Billing Frequency:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Monthly:</b> $
|
||||||
|
${monthlyPlansMrrOverTime?.counts[monthlyPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Annual:</b> $
|
||||||
|
${annualPlansMrrOverTime?.counts[annualPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>5-year:</b> $
|
||||||
|
${fiveYearPlansMrrOverTime?.counts[fiveYearPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered:${' '}
|
||||||
|
<b>
|
||||||
|
${userRegistrationOverTime?.counts[userRegistrationOverTime?.counts.length - 1]?.totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered:${' '}
|
||||||
|
<b>
|
||||||
|
${userDeletionOverTime?.counts[userDeletionOverTime?.counts.length - 1]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average account duration: ${registrationLengthDurationYesterday.days} days${' '}
|
||||||
|
${registrationLengthDurationYesterday.hours} hours ${registrationLengthDurationYesterday.minutes} minutes)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionPurchasingOverTime?.counts[
|
||||||
|
subscriptionPurchasingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(includes <b>${limitedDiscountPurchasedActivity?.totalCount.toLocaleString('en-US')}</b> limited time
|
||||||
|
offer purchases)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRenewingOverTime?.counts[
|
||||||
|
subscriptionRenewingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRefundingOverTime?.counts[
|
||||||
|
subscriptionRefundingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionCancelledOverTime?.counts[
|
||||||
|
subscriptionCancelledOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average subscription duration: ${subscriptionLengthDurationYesterday.days} days${' '}
|
||||||
|
${subscriptionLengthDurationYesterday.hours} hours ${subscriptionLengthDurationYesterday.minutes} minutes,
|
||||||
|
average remaining subscription percentage: ${subscriptionRemainingTimePercentageYesterday}%)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionReactivatedOverTime?.counts[
|
||||||
|
subscriptionReactivatedOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationYesterday.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from last 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Income: <b>$${incomeThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Refunds: <b>$${refundsThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered: <b>${userRegistrationOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered: <b>${userDeletionOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average account duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationLengthDurationThisMonth.days} days ${registrationLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>${subscriptionPurchasingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>${subscriptionRenewingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>${subscriptionRefundingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>${subscriptionCancelledOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>${subscriptionReactivatedOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionLengthDurationThisMonth.days} days ${subscriptionLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${subscriptionLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription remaining percentage this month:${' '}
|
||||||
|
<b>${subscriptionRemainingTimePercentageThisMonth}%</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationThisMonth.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrr}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrrMonthly}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the subscription chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.subscriptions}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the users chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.users}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the monthly churn rate percentage:</strong>
|
||||||
|
</p>
|
||||||
|
<p>✅ GREAT! Up to 7% 🔶 OKAY: 8-10% 🩸 BAD: 11 -15 % 🚨 TERRIBLE! 16-20%</p>
|
||||||
|
<p>Churn is calculated by the following formula:</p>
|
||||||
|
<p>
|
||||||
|
( Existing Customers Churn [${thisMonthChurn?.existingCustomersChurn}] + New Customers Churn [
|
||||||
|
${thisMonthChurn?.newCustomersChurn}] ) * 100 / Average Customers Count This Month [
|
||||||
|
${thisMonthChurn?.averageCustomersCount}]
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.churn}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is quarterly performance chart:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.quarterlyPerformance}></img>
|
||||||
|
<p>Thanks,SN</p>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
@@ -18,5 +18,5 @@ export class AnalyticsEntity {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
@Index('email')
|
@Index('email')
|
||||||
declare userEmail: string
|
declare username: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
|
||||||
|
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
|
||||||
import { Period } from '../Time/Period'
|
|
||||||
|
|
||||||
import { DomainEventFactory } from './DomainEventFactory'
|
|
||||||
|
|
||||||
describe('DomainEventFactory', () => {
|
|
||||||
let timer: TimerInterface
|
|
||||||
|
|
||||||
const createFactory = () => new DomainEventFactory(timer)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
|
||||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
|
||||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create a DAILY_ANALYTICS_REPORT_GENERATED event', () => {
|
|
||||||
expect(
|
|
||||||
createFactory().createDailyAnalyticsReportGeneratedEvent({
|
|
||||||
snjsStatistics: [
|
|
||||||
{
|
|
||||||
version: '1-2-3',
|
|
||||||
count: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
applicationStatistics: [
|
|
||||||
{
|
|
||||||
version: '2-3-4',
|
|
||||||
count: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatistics: [
|
|
||||||
{
|
|
||||||
name: AnalyticsActivity.Register,
|
|
||||||
retention: 24,
|
|
||||||
totalCount: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
statisticMeasures: [
|
|
||||||
{
|
|
||||||
name: StatisticsMeasure.Income,
|
|
||||||
totalValue: 43,
|
|
||||||
average: 23,
|
|
||||||
increments: 5,
|
|
||||||
period: Period.Today,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatisticsOverTime: [
|
|
||||||
{
|
|
||||||
name: AnalyticsActivity.Register,
|
|
||||||
period: Period.Last30Days,
|
|
||||||
counts: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
totalCount: 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
totalCount: 123,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outOfSyncIncidents: 324,
|
|
||||||
retentionStatistics: [
|
|
||||||
{
|
|
||||||
firstActivity: AnalyticsActivity.Register,
|
|
||||||
secondActivity: AnalyticsActivity.Login,
|
|
||||||
retention: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
firstPeriodKey: AnalyticsActivity.Register,
|
|
||||||
secondPeriodKey: AnalyticsActivity.Login,
|
|
||||||
value: 12,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
churn: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
rate: 12,
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
createdAt: expect.any(Date),
|
|
||||||
meta: {
|
|
||||||
correlation: {
|
|
||||||
userIdentifier: '',
|
|
||||||
userIdentifierType: 'uuid',
|
|
||||||
},
|
|
||||||
origin: 'analytics',
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
activityStatistics: [
|
|
||||||
{
|
|
||||||
name: 'register',
|
|
||||||
retention: 24,
|
|
||||||
totalCount: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatisticsOverTime: [
|
|
||||||
{
|
|
||||||
counts: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
totalCount: 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
name: 'register',
|
|
||||||
period: 9,
|
|
||||||
totalCount: 123,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
applicationStatistics: [
|
|
||||||
{
|
|
||||||
count: 45,
|
|
||||||
version: '2-3-4',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
churn: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
rate: 12,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
outOfSyncIncidents: 324,
|
|
||||||
retentionStatistics: [
|
|
||||||
{
|
|
||||||
firstActivity: 'register',
|
|
||||||
retention: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
firstPeriodKey: 'register',
|
|
||||||
secondPeriodKey: 'login',
|
|
||||||
value: 12,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
secondActivity: 'login',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
snjsStatistics: [
|
|
||||||
{
|
|
||||||
count: 2,
|
|
||||||
version: '1-2-3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
statisticMeasures: [
|
|
||||||
{
|
|
||||||
average: 23,
|
|
||||||
increments: 5,
|
|
||||||
name: 'income',
|
|
||||||
period: 0,
|
|
||||||
totalValue: 43,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -7,65 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||||
|
createEmailRequestedEvent(dto: {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
userEmail: string
|
||||||
snjsStatistics: Array<{
|
messageIdentifier: string
|
||||||
version: string
|
level: string
|
||||||
count: number
|
body: string
|
||||||
}>
|
subject: string
|
||||||
applicationStatistics: Array<{
|
}): EmailRequestedEvent {
|
||||||
version: string
|
|
||||||
count: number
|
|
||||||
}>
|
|
||||||
activityStatistics: Array<{
|
|
||||||
name: string
|
|
||||||
retention: number
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticMeasures: Array<{
|
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
outOfSyncIncidents: number
|
|
||||||
retentionStatistics: Array<{
|
|
||||||
firstActivity: string
|
|
||||||
secondActivity: string
|
|
||||||
retention: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
firstPeriodKey: string
|
|
||||||
secondPeriodKey: string
|
|
||||||
value: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent {
|
|
||||||
return {
|
return {
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
type: 'EMAIL_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
meta: {
|
meta: {
|
||||||
correlation: {
|
correlation: {
|
||||||
userIdentifier: '',
|
userIdentifier: dto.userEmail,
|
||||||
userIdentifierType: 'uuid',
|
userIdentifierType: 'email',
|
||||||
},
|
},
|
||||||
origin: DomainEventService.Analytics,
|
origin: DomainEventService.Analytics,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,55 +1,11 @@
|
|||||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
snjsStatistics: Array<{
|
userEmail: string
|
||||||
version: string
|
messageIdentifier: string
|
||||||
count: number
|
level: string
|
||||||
}>
|
body: string
|
||||||
applicationStatistics: Array<{
|
subject: string
|
||||||
version: string
|
}): EmailRequestedEvent
|
||||||
count: number
|
|
||||||
}>
|
|
||||||
activityStatistics: Array<{
|
|
||||||
name: string
|
|
||||||
retention: number
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticMeasures: Array<{
|
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
outOfSyncIncidents: number
|
|
||||||
retentionStatistics: Array<{
|
|
||||||
firstActivity: string
|
|
||||||
secondActivity: string
|
|
||||||
retention: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
firstPeriodKey: string
|
|
||||||
secondPeriodKey: string
|
|
||||||
value: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1,9 +1,11 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { RefundProcessedEvent } from '@standardnotes/domain-events'
|
import { RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||||
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
|
||||||
|
|
||||||
import { RefundProcessedEventHandler } from './RefundProcessedEventHandler'
|
import { RefundProcessedEventHandler } from './RefundProcessedEventHandler'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
describe('RefundProcessedEventHandler', () => {
|
describe('RefundProcessedEventHandler', () => {
|
||||||
let event: RefundProcessedEvent
|
let event: RefundProcessedEvent
|
||||||
+3
-1
@@ -1,8 +1,10 @@
|
|||||||
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
|
||||||
import { DomainEventHandlerInterface, RefundProcessedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class RefundProcessedEventHandler implements DomainEventHandlerInterface {
|
export class RefundProcessedEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||||
@@ -9,16 +10,31 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
|||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionCancelledEventHandler', () => {
|
describe('SubscriptionCancelledEventHandler', () => {
|
||||||
let event: SubscriptionCancelledEvent
|
let event: SubscriptionCancelledEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionCancelledEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionCancelledEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||||
|
|
||||||
@@ -30,6 +46,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionCancelledEvent>
|
event = {} as jest.Mocked<SubscriptionCancelledEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_CANCELLED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -41,7 +58,13 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
timestamp: 1,
|
timestamp: 1,
|
||||||
offline: false,
|
offline: false,
|
||||||
replaced: false,
|
replaced: false,
|
||||||
|
userExistingSubscriptionsCount: 1,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should track subscription cancelled statistics', async () => {
|
it('should track subscription cancelled statistics', async () => {
|
||||||
@@ -55,6 +78,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not track statistics for subscriptions that are in a legacy 5 year plan', async () => {
|
it('should not track statistics for subscriptions that are in a legacy 5 year plan', async () => {
|
||||||
@@ -65,5 +89,16 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
event.payload.timestamp = 1642395451516000
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -15,10 +20,12 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -26,6 +33,23 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
])
|
])
|
||||||
|
|
||||||
await this.trackSubscriptionStatistics(event)
|
await this.trackSubscriptionStatistics(event)
|
||||||
|
|
||||||
|
const result = await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {
|
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
|
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
|
||||||
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
describe('SubscriptionExpiredEventHandler', () => {
|
||||||
|
let event: SubscriptionExpiredEvent
|
||||||
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
|
const createHandler = () =>
|
||||||
|
new SubscriptionExpiredEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
|
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
||||||
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_EXPIRED'
|
||||||
|
event.payload = {
|
||||||
|
subscriptionId: 1,
|
||||||
|
userEmail: 'test@test.com',
|
||||||
|
subscriptionName: SubscriptionName.PlusPlan,
|
||||||
|
timestamp: 1,
|
||||||
|
offline: false,
|
||||||
|
totalActiveSubscriptionsCount: 123,
|
||||||
|
userExistingSubscriptionsCount: 2,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
|
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||||
|
|
||||||
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
|
analyticsStore.markActivity = jest.fn()
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.setMeasure = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update analytics and statistics', async () => {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
|
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||||
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
|
await this.analyticsStore.markActivity(
|
||||||
|
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
|
||||||
|
analyticsId,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
|
)
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(
|
||||||
|
StatisticsMeasure.TotalCustomers,
|
||||||
|
event.payload.totalActiveSubscriptionsCount,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
|
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
|
||||||
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
describe('SubscriptionPurchasedEventHandler', () => {
|
||||||
|
let event: SubscriptionPurchasedEvent
|
||||||
|
let subscriptionExpiresAt: number
|
||||||
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
|
const createHandler = () =>
|
||||||
|
new SubscriptionPurchasedEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
statisticsStore.setMeasure = jest.fn()
|
||||||
|
|
||||||
|
event = {} as jest.Mocked<SubscriptionPurchasedEvent>
|
||||||
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_PURCHASED'
|
||||||
|
event.payload = {
|
||||||
|
subscriptionId: 1,
|
||||||
|
userEmail: 'test@test.com',
|
||||||
|
subscriptionName: SubscriptionName.ProPlan,
|
||||||
|
subscriptionExpiresAt,
|
||||||
|
timestamp: 60,
|
||||||
|
offline: false,
|
||||||
|
discountCode: null,
|
||||||
|
limitedDiscountPurchased: false,
|
||||||
|
newSubscriber: true,
|
||||||
|
totalActiveSubscriptionsCount: 123,
|
||||||
|
userRegisteredAt: 23,
|
||||||
|
billingFrequency: 12,
|
||||||
|
payAmount: 29.99,
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
|
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||||
|
|
||||||
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
|
analyticsStore.markActivity = jest.fn()
|
||||||
|
analyticsStore.unmarkActivity = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should mark subscription creation statistics', async () => {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
||||||
|
event.payload.newSubscriber = false
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update analytics on limited discount offer purchasing', async () => {
|
||||||
|
event.payload.limitedDiscountPurchased = true
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['limited-discount-offer-purchased'], 3, [Period.Today])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||||
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
|
await this.analyticsStore.unmarkActivity(
|
||||||
|
[AnalyticsActivity.ExistingCustomersChurn, AnalyticsActivity.NewCustomersChurn],
|
||||||
|
analyticsId,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (event.payload.limitedDiscountPurchased) {
|
||||||
|
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
|
||||||
|
Period.Today,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.payload.newSubscriber) {
|
||||||
|
await this.statisticsStore.incrementMeasure(
|
||||||
|
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||||
|
event.payload.timestamp - event.payload.userRegisteredAt,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
|
)
|
||||||
|
await this.statisticsStore.incrementMeasure(StatisticsMeasure.NewCustomers, 1, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
await this.statisticsStore.setMeasure(
|
||||||
|
StatisticsMeasure.TotalCustomers,
|
||||||
|
event.payload.totalActiveSubscriptionsCount,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.newSubscriber,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-36
@@ -1,41 +1,21 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
||||||
import { Logger } from 'winston'
|
|
||||||
|
|
||||||
import { User } from '../User/User'
|
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|
||||||
import { SubscriptionReactivatedEventHandler } from './SubscriptionReactivatedEventHandler'
|
import { SubscriptionReactivatedEventHandler } from './SubscriptionReactivatedEventHandler'
|
||||||
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
describe('SubscriptionReactivatedEventHandler', () => {
|
describe('SubscriptionReactivatedEventHandler', () => {
|
||||||
let userRepository: UserRepositoryInterface
|
|
||||||
let logger: Logger
|
|
||||||
let user: User
|
|
||||||
let event: SubscriptionReactivatedEvent
|
let event: SubscriptionReactivatedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () => new SubscriptionReactivatedEventHandler(analyticsStore, getUserAnalyticsId)
|
||||||
new SubscriptionReactivatedEventHandler(userRepository, analyticsStore, getUserAnalyticsId, logger)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = {
|
|
||||||
uuid: '123',
|
|
||||||
email: 'test@test.com',
|
|
||||||
roles: Promise.resolve([
|
|
||||||
{
|
|
||||||
name: RoleName.ProUser,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
} as jest.Mocked<User>
|
|
||||||
|
|
||||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
|
||||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
|
||||||
userRepository.save = jest.fn().mockReturnValue(user)
|
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionReactivatedEvent>
|
event = {} as jest.Mocked<SubscriptionReactivatedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
event.payload = {
|
event.payload = {
|
||||||
@@ -52,10 +32,6 @@ describe('SubscriptionReactivatedEventHandler', () => {
|
|||||||
|
|
||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
|
||||||
logger.info = jest.fn()
|
|
||||||
logger.warn = jest.fn()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mark subscription reactivated activity for analytics', async () => {
|
it('should mark subscription reactivated activity for analytics', async () => {
|
||||||
@@ -67,12 +43,4 @@ describe('SubscriptionReactivatedEventHandler', () => {
|
|||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not do anything if no user is found for specified email', async () => {
|
|
||||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
|
||||||
|
|
||||||
await createHandler().handle(event)
|
|
||||||
|
|
||||||
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
+4
-13
@@ -1,30 +1,21 @@
|
|||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
|
||||||
import { DomainEventHandlerInterface, SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionReactivatedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionReactivatedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.Logger) private logger: Logger,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
|
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
|
||||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
|
|
||||||
if (user === null) {
|
|
||||||
this.logger.warn(`Could not find user with email: ${event.payload.userEmail}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionReactivated], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionReactivated], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -2,6 +2,7 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
@@ -10,18 +11,34 @@ import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHan
|
|||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionRefundedEventHandler', () => {
|
describe('SubscriptionRefundedEventHandler', () => {
|
||||||
let event: SubscriptionRefundedEvent
|
let event: SubscriptionRefundedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionRefundedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionRefundedEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_REFUNDED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -30,6 +47,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
offline: false,
|
offline: false,
|
||||||
userExistingSubscriptionsCount: 3,
|
userExistingSubscriptionsCount: 3,
|
||||||
totalActiveSubscriptionsCount: 1,
|
totalActiveSubscriptionsCount: 1,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
@@ -41,6 +60,9 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
|
|
||||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
statisticsStore.setMeasure = jest.fn()
|
statisticsStore.setMeasure = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mark churn for new customer', async () => {
|
it('should mark churn for new customer', async () => {
|
||||||
@@ -56,6 +78,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.NewCustomersChurn], 3, [
|
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.NewCustomersChurn], 3, [
|
||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mark churn for existing customer', async () => {
|
it('should mark churn for existing customer', async () => {
|
||||||
@@ -75,4 +99,12 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -15,10 +20,12 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -26,6 +33,23 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
])
|
])
|
||||||
|
|
||||||
await this.markChurnActivity(analyticsId, event)
|
await this.markChurnActivity(analyticsId, event)
|
||||||
|
|
||||||
|
const result = await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async markChurnActivity(analyticsId: number, event: SubscriptionRefundedEvent): Promise<void> {
|
private async markChurnActivity(analyticsId: number, event: SubscriptionRefundedEvent): Promise<void> {
|
||||||
|
|||||||
@@ -2,21 +2,32 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionRenewedEventHandler', () => {
|
describe('SubscriptionRenewedEventHandler', () => {
|
||||||
let event: SubscriptionRenewedEvent
|
let event: SubscriptionRenewedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_RENEWED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -24,6 +35,8 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
subscriptionExpiresAt: 2,
|
subscriptionExpiresAt: 2,
|
||||||
timestamp: 1,
|
timestamp: 1,
|
||||||
offline: false,
|
offline: false,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
@@ -32,6 +45,9 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
analyticsStore.unmarkActivity = jest.fn()
|
analyticsStore.unmarkActivity = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should track subscription renewed statistics', async () => {
|
it('should track subscription renewed statistics', async () => {
|
||||||
@@ -39,5 +55,14 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
|
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -26,5 +33,22 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
analyticsId,
|
analyticsId,
|
||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const result = await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: false,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
|||||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||||
let analyticsEntity = new AnalyticsEntity()
|
let analyticsEntity = new AnalyticsEntity()
|
||||||
analyticsEntity.userUuid = event.payload.userUuid
|
analyticsEntity.userUuid = event.payload.userUuid
|
||||||
analyticsEntity.userEmail = event.payload.email
|
analyticsEntity.username = event.payload.email
|
||||||
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
||||||
|
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { injectable } from 'inversify'
|
||||||
|
import { MapperInterface, UniqueEntityId, Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class RevenueModificationMap implements MapperInterface<RevenueModification, TypeORMRevenueModification> {
|
||||||
|
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
||||||
|
const userOrError = User.create(
|
||||||
|
{
|
||||||
|
username: Username.create(persistence.username).getValue(),
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.userUuid),
|
||||||
|
)
|
||||||
|
if (userOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create user: ${userOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const user = userOrError.getValue()
|
||||||
|
|
||||||
|
const subscriptionOrError = Subscription.create(
|
||||||
|
{
|
||||||
|
billingFrequency: persistence.billingFrequency,
|
||||||
|
isFirstSubscriptionForUser: persistence.isNewCustomer,
|
||||||
|
payedAmount: persistence.billingFrequency * persistence.newMonthlyRevenue,
|
||||||
|
planName: SubscriptionPlanName.create(persistence.subscriptionPlan).getValue(),
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.subscriptionId),
|
||||||
|
)
|
||||||
|
if (subscriptionOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create subscription: ${subscriptionOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const subscription = subscriptionOrError.getValue()
|
||||||
|
|
||||||
|
const previousMonthlyRevenueOrError = MonthlyRevenue.create(persistence.previousMonthlyRevenue)
|
||||||
|
const newMonthlyRevenueOrError = MonthlyRevenue.create(persistence.newMonthlyRevenue)
|
||||||
|
|
||||||
|
const revenuModificationOrError = RevenueModification.create(
|
||||||
|
{
|
||||||
|
user,
|
||||||
|
subscription,
|
||||||
|
eventType: SubscriptionEventType.create(persistence.eventType).getValue(),
|
||||||
|
previousMonthlyRevenue: previousMonthlyRevenueOrError.getValue(),
|
||||||
|
newMonthlyRevenue: newMonthlyRevenueOrError.getValue(),
|
||||||
|
createdAt: persistence.createdAt,
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.uuid),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (revenuModificationOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not map revenue modification to domain: ${revenuModificationOrError.getError()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return revenuModificationOrError.getValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
toProjection(domain: RevenueModification): TypeORMRevenueModification {
|
||||||
|
const { subscription, user } = domain.props
|
||||||
|
const persistence = new TypeORMRevenueModification()
|
||||||
|
persistence.uuid = domain.id.toString()
|
||||||
|
persistence.billingFrequency = subscription.props.billingFrequency
|
||||||
|
persistence.eventType = domain.props.eventType.value
|
||||||
|
persistence.isNewCustomer = subscription.props.isFirstSubscriptionForUser
|
||||||
|
persistence.newMonthlyRevenue = domain.props.newMonthlyRevenue.value
|
||||||
|
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
||||||
|
persistence.subscriptionId = subscription.id.toValue() as number
|
||||||
|
persistence.subscriptionPlan = subscription.props.planName.value
|
||||||
|
persistence.username = user.props.username.value
|
||||||
|
persistence.userUuid = user.id.toString()
|
||||||
|
persistence.createdAt = domain.props.createdAt
|
||||||
|
|
||||||
|
return persistence
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
|
||||||
|
describe('MonthlyRevenue', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = MonthlyRevenue.create(123)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = MonthlyRevenue.create(-3)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { MonthlyRevenueProps } from './MonthlyRevenueProps'
|
||||||
|
|
||||||
|
export class MonthlyRevenue extends ValueObject<MonthlyRevenueProps> {
|
||||||
|
get value(): number {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: MonthlyRevenueProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(revenue: number): Result<MonthlyRevenue> {
|
||||||
|
if (isNaN(revenue) || revenue < 0) {
|
||||||
|
return Result.fail<MonthlyRevenue>(`Monthly revenue must be a non-negative number. Supplied: ${revenue}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<MonthlyRevenue>(new MonthlyRevenue({ value: revenue }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface MonthlyRevenueProps {
|
||||||
|
value: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
import { RevenueModification } from './RevenueModification'
|
||||||
|
|
||||||
|
describe('RevenueModification', () => {
|
||||||
|
let user: User
|
||||||
|
let subscription: Subscription
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
subscription = Subscription.create({
|
||||||
|
billingFrequency: 12,
|
||||||
|
isFirstSubscriptionForUser: true,
|
||||||
|
payedAmount: 123,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
}).getValue()
|
||||||
|
user = User.create({
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
}).getValue()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an aggregate for purchased subscription', () => {
|
||||||
|
const revenueModification = RevenueModification.create({
|
||||||
|
createdAt: 2,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
||||||
|
newMonthlyRevenue: MonthlyRevenue.create(45).getValue(),
|
||||||
|
subscription,
|
||||||
|
user,
|
||||||
|
}).getValue()
|
||||||
|
|
||||||
|
expect(revenueModification.id.toString()).toHaveLength(36)
|
||||||
|
expect(revenueModification.props.newMonthlyRevenue.value).toEqual(45)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Aggregate, UniqueEntityId, Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { RevenueModificationProps } from './RevenueModificationProps'
|
||||||
|
|
||||||
|
export class RevenueModification extends Aggregate<RevenueModificationProps> {
|
||||||
|
private constructor(props: RevenueModificationProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: RevenueModificationProps, id?: UniqueEntityId): Result<RevenueModification> {
|
||||||
|
return Result.ok<RevenueModification>(new RevenueModification(props, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
|
export interface RevenueModificationProps {
|
||||||
|
user: User
|
||||||
|
subscription: Subscription
|
||||||
|
eventType: SubscriptionEventType
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue
|
||||||
|
newMonthlyRevenue: MonthlyRevenue
|
||||||
|
createdAt: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { RevenueModification } from './RevenueModification'
|
||||||
|
|
||||||
|
export interface RevenueModificationRepositoryInterface {
|
||||||
|
findLastByUserUuid(userUuid: Uuid): Promise<RevenueModification | null>
|
||||||
|
sumMRRDiff(dto: { billingFrequencies: number[]; planNames?: string[] }): Promise<number>
|
||||||
|
save(revenueModification: RevenueModification): Promise<RevenueModification>
|
||||||
|
}
|
||||||
@@ -13,9 +13,12 @@ export enum StatisticsMeasure {
|
|||||||
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
||||||
RemainingSubscriptionTimePercentage = 'remaining-subscription-time-percentage',
|
RemainingSubscriptionTimePercentage = 'remaining-subscription-time-percentage',
|
||||||
Refunds = 'refunds',
|
Refunds = 'refunds',
|
||||||
NotesCountFreeUsers = 'notes-count-free-users',
|
|
||||||
NotesCountPaidUsers = 'notes-count-paid-users',
|
|
||||||
FilesCount = 'files-count',
|
|
||||||
NewCustomers = 'new-customers',
|
NewCustomers = 'new-customers',
|
||||||
TotalCustomers = 'total-customers',
|
TotalCustomers = 'total-customers',
|
||||||
|
MRR = 'mrr',
|
||||||
|
MonthlyPlansMRR = 'monthly-plans-mrr',
|
||||||
|
AnnualPlansMRR = 'annual-plans-mrr',
|
||||||
|
FiveYearPlansMRR = 'five-year-plans-mrr',
|
||||||
|
ProPlansMRR = 'pro-plans-mrr',
|
||||||
|
PlusPlansMRR = 'plus-plans-mrr',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,8 @@ export interface StatisticsStoreInterface {
|
|||||||
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
|
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
|
||||||
getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number>
|
getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
|
calculateTotalCountOverPeriod(
|
||||||
|
measure: StatisticsMeasure,
|
||||||
|
period: Period,
|
||||||
|
): Promise<Array<{ periodKey: string; totalCount: number }>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Subscription } from './Subscription'
|
||||||
|
import { SubscriptionPlanName } from './SubscriptionPlanName'
|
||||||
|
|
||||||
|
describe('Subscription', () => {
|
||||||
|
it('should create an entity', () => {
|
||||||
|
const subscription = Subscription.create({
|
||||||
|
billingFrequency: 1,
|
||||||
|
isFirstSubscriptionForUser: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
}).getValue()
|
||||||
|
|
||||||
|
expect(subscription.id.toString()).toHaveLength(36)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SubscriptionProps } from './SubscriptionProps'
|
||||||
|
|
||||||
|
export class Subscription extends Entity<SubscriptionProps> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: SubscriptionProps, id?: UniqueEntityId): Result<Subscription> {
|
||||||
|
return Result.ok<Subscription>(new Subscription(props, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { SubscriptionEventType } from './SubscriptionEventType'
|
||||||
|
|
||||||
|
describe('SubscriptionEventType', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = SubscriptionEventType.create('SUBSCRIPTION_PURCHASED')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('SUBSCRIPTION_PURCHASED')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = SubscriptionEventType.create('SUBSCRIPTION_REACTIVATED')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { ValueObject, Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SubscriptionEventTypeProps } from './SubscriptionEventTypeProps'
|
||||||
|
|
||||||
|
export class SubscriptionEventType extends ValueObject<SubscriptionEventTypeProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionEventTypeProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(subscriptionEventType: string): Result<SubscriptionEventType> {
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'SUBSCRIPTION_PURCHASED',
|
||||||
|
'SUBSCRIPTION_RENEWED',
|
||||||
|
'SUBSCRIPTION_EXPIRED',
|
||||||
|
'SUBSCRIPTION_REFUNDED',
|
||||||
|
'SUBSCRIPTION_CANCELLED',
|
||||||
|
'SUBSCRIPTION_DATA_MIGRATED',
|
||||||
|
].includes(subscriptionEventType)
|
||||||
|
) {
|
||||||
|
return Result.fail<SubscriptionEventType>(`Invalid subscription event type ${subscriptionEventType}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<SubscriptionEventType>(new SubscriptionEventType({ value: subscriptionEventType }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface SubscriptionEventTypeProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { SubscriptionPlanName } from './SubscriptionPlanName'
|
||||||
|
|
||||||
|
describe('SubscriptionPlanName', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = SubscriptionPlanName.create('PRO_PLAN')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('PRO_PLAN')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = SubscriptionPlanName.create('TEST')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { SubscriptionPlanNameProps } from './SubscriptionPlanNameProps'
|
||||||
|
|
||||||
|
export class SubscriptionPlanName extends ValueObject<SubscriptionPlanNameProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionPlanNameProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(subscriptionPlanName: string): Result<SubscriptionPlanName> {
|
||||||
|
if (!['PRO_PLAN', 'PLUS_PLAN'].includes(subscriptionPlanName)) {
|
||||||
|
return Result.fail<SubscriptionPlanName>(`Invalid subscription plan name ${subscriptionPlanName}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<SubscriptionPlanName>(new SubscriptionPlanName({ value: subscriptionPlanName }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user