mirror of
https://github.com/standardnotes/server
synced 2026-05-10 09:57:17 -04:00
Compare commits
296 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 |
@@ -187,7 +187,7 @@ jobs:
|
||||
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||
|
||||
- name: Run E2E test suite
|
||||
uses: convictional/trigger-workflow-and-wait@v1.6.3
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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)/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)
|
||||
|
||||
# "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",
|
||||
"force_dynamic_crt": 0,
|
||||
"host_arch": "x64",
|
||||
"icu_data_in": "../../deps/icu-tmp/icudt70l.dat",
|
||||
"icu_data_in": "../../deps/icu-tmp/icudt71l.dat",
|
||||
"icu_endianness": "l",
|
||||
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
||||
"icu_path": "deps/icu-small",
|
||||
"icu_small": "false",
|
||||
"icu_ver_major": "70",
|
||||
"icu_ver_major": "71",
|
||||
"is_debug": 0,
|
||||
"libdir": "lib",
|
||||
"llvm_version": "11.0",
|
||||
"napi_build_version": "8",
|
||||
"node_byteorder": "little",
|
||||
"node_debug_lib": "false",
|
||||
"node_enable_d8": "false",
|
||||
"node_fipsinstall": "false",
|
||||
"node_install_corepack": "true",
|
||||
"node_install_npm": "true",
|
||||
"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/timers.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_client.js",
|
||||
"lib/_http_common.js",
|
||||
"lib/string_decoder.js",
|
||||
"lib/cluster.js",
|
||||
"lib/v8.js",
|
||||
"lib/crypto.js",
|
||||
"lib/wasi.js",
|
||||
"lib/_http_incoming.js",
|
||||
"lib/_http_outgoing.js",
|
||||
"lib/_http_server.js",
|
||||
"lib/_stream_duplex.js",
|
||||
"lib/_stream_passthrough.js",
|
||||
"lib/_stream_readable.js",
|
||||
"lib/zlib.js",
|
||||
"lib/url.js",
|
||||
"lib/tls.js",
|
||||
"lib/_stream_transform.js",
|
||||
"lib/_stream_wrap.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/process.js",
|
||||
"lib/http.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/util/types.js",
|
||||
"lib/timers/promises.js",
|
||||
"lib/path/win32.js",
|
||||
"lib/path/posix.js",
|
||||
"lib/stream/consumers.js",
|
||||
"lib/stream/promises.js",
|
||||
"lib/stream/web.js",
|
||||
"lib/internal/constants.js",
|
||||
"lib/fs/promises.js",
|
||||
"lib/http.js",
|
||||
"lib/http2.js",
|
||||
"lib/https.js",
|
||||
"lib/inspector.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/fixed_queue.js",
|
||||
"lib/internal/blocklist.js",
|
||||
"lib/internal/v8_prof_polyfill.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/assert/assertion_error.js",
|
||||
"lib/internal/assert/calltracker.js",
|
||||
"lib/internal/assert/snapshot.js",
|
||||
"lib/internal/async_hooks.js",
|
||||
"lib/internal/http.js",
|
||||
"lib/internal/buffer.js",
|
||||
"lib/internal/trace_events_async_hooks.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/blob.js",
|
||||
"lib/internal/blocklist.js",
|
||||
"lib/internal/bootstrap/browser.js",
|
||||
"lib/internal/bootstrap/loaders.js",
|
||||
"lib/internal/bootstrap/pre_execution.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/is_not_main_thread.js",
|
||||
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
||||
"lib/internal/bootstrap/switches/is_main_thread.js",
|
||||
"lib/internal/test/binding.js",
|
||||
"lib/internal/test/transfer.js",
|
||||
"lib/internal/util/types.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/bootstrap/switches/is_not_main_thread.js",
|
||||
"lib/internal/buffer.js",
|
||||
"lib/internal/child_process.js",
|
||||
"lib/internal/child_process/serialization.js",
|
||||
"lib/internal/debugger/inspect_repl.js",
|
||||
"lib/internal/debugger/inspect_client.js",
|
||||
"lib/internal/cli_table.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/worker/io.js",
|
||||
"lib/internal/worker/js_transferable.js",
|
||||
"lib/internal/main/repl.js",
|
||||
"lib/internal/main/print_help.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/debugger/inspect_client.js",
|
||||
"lib/internal/debugger/inspect_repl.js",
|
||||
"lib/internal/dgram.js",
|
||||
"lib/internal/dns/callback_resolver.js",
|
||||
"lib/internal/dns/promises.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/read_file_context.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/sync_write_stream.js",
|
||||
"lib/internal/fs/utils.js",
|
||||
"lib/internal/fs/cp/cp.js",
|
||||
"lib/internal/fs/cp/cp-sync.js",
|
||||
"lib/internal/perf/nodetiming.js",
|
||||
"lib/internal/perf/usertiming.js",
|
||||
"lib/internal/perf/performance_entry.js",
|
||||
"lib/internal/perf/performance.js",
|
||||
"lib/internal/perf/timerify.js",
|
||||
"lib/internal/perf/utils.js",
|
||||
"lib/internal/perf/observe.js",
|
||||
"lib/internal/fs/watchers.js",
|
||||
"lib/internal/heap_utils.js",
|
||||
"lib/internal/histogram.js",
|
||||
"lib/internal/http.js",
|
||||
"lib/internal/http2/compat.js",
|
||||
"lib/internal/http2/core.js",
|
||||
"lib/internal/http2/util.js",
|
||||
"lib/internal/idna.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_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/sri.js",
|
||||
"lib/internal/process/task_queues.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/priority_queue.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/signal.js",
|
||||
"lib/internal/process/task_queues.js",
|
||||
"lib/internal/process/warning.js",
|
||||
"lib/internal/process/worker_thread_only.js",
|
||||
"lib/internal/console/constructor.js",
|
||||
"lib/internal/console/global.js",
|
||||
"lib/assert/strict.js",
|
||||
"lib/dns/promises.js",
|
||||
"lib/fs/promises.js"
|
||||
"lib/internal/promise_hooks.js",
|
||||
"lib/internal/querystring.js",
|
||||
"lib/internal/readline/callbacks.js",
|
||||
"lib/internal/readline/emitKeypressEvents.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_prefix": "/",
|
||||
"node_release_urlbase": "https://nodejs.org/download/release/",
|
||||
@@ -330,20 +353,22 @@
|
||||
"node_use_v8_platform": "true",
|
||||
"node_with_ltcg": "false",
|
||||
"node_without_node_options": "false",
|
||||
"openssl_fips": "",
|
||||
"openssl_is_fips": "false",
|
||||
"openssl_quic": "true",
|
||||
"ossfuzz": "false",
|
||||
"shlib_suffix": "93.dylib",
|
||||
"shlib_suffix": "108.dylib",
|
||||
"target_arch": "x64",
|
||||
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
||||
"v8_enable_gdbjit": 0,
|
||||
"v8_enable_hugepage": 0,
|
||||
"v8_enable_i18n_support": 1,
|
||||
"v8_enable_inspector": 1,
|
||||
"v8_enable_javascript_promise_hooks": 1,
|
||||
"v8_enable_lite_mode": 0,
|
||||
"v8_enable_object_print": 1,
|
||||
"v8_enable_pointer_compression": 0,
|
||||
"v8_enable_shared_ro_heap": 1,
|
||||
"v8_enable_short_builtin_calls": 1,
|
||||
"v8_enable_webassembly": 1,
|
||||
"v8_no_strict_aliasing": 1,
|
||||
"v8_optimized_debug": 1,
|
||||
@@ -353,8 +378,8 @@
|
||||
"v8_use_siphash": 1,
|
||||
"want_separate_host_toolset": 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,
|
||||
"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 := \
|
||||
-O0 \
|
||||
-gdwarf-2 \
|
||||
-mmacosx-version-min=10.13 \
|
||||
-mmacosx-version-min=10.15 \
|
||||
-arch x86_64 \
|
||||
-Wall \
|
||||
-Wendif-labels \
|
||||
@@ -38,7 +38,7 @@ CFLAGS_C_Debug := \
|
||||
|
||||
# Flags passed to only C++ files.
|
||||
CFLAGS_CC_Debug := \
|
||||
-std=gnu++14 \
|
||||
-std=gnu++17 \
|
||||
-stdlib=libc++ \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
@@ -51,13 +51,13 @@ CFLAGS_OBJC_Debug :=
|
||||
CFLAGS_OBJCC_Debug :=
|
||||
|
||||
INCS_Debug := \
|
||||
-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/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||
-I$(srcdir)/src \
|
||||
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
||||
|
||||
@@ -81,7 +81,7 @@ DEFS_Release := \
|
||||
CFLAGS_Release := \
|
||||
-O3 \
|
||||
-gdwarf-2 \
|
||||
-mmacosx-version-min=10.13 \
|
||||
-mmacosx-version-min=10.15 \
|
||||
-arch x86_64 \
|
||||
-Wall \
|
||||
-Wendif-labels \
|
||||
@@ -94,7 +94,7 @@ CFLAGS_C_Release := \
|
||||
|
||||
# Flags passed to only C++ files.
|
||||
CFLAGS_CC_Release := \
|
||||
-std=gnu++14 \
|
||||
-std=gnu++17 \
|
||||
-stdlib=libc++ \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
@@ -107,13 +107,13 @@ CFLAGS_OBJC_Release :=
|
||||
CFLAGS_OBJCC_Release :=
|
||||
|
||||
INCS_Release := \
|
||||
-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/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||
-I$(srcdir)/src \
|
||||
-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 := \
|
||||
-undefined dynamic_lookup \
|
||||
-Wl,-search_paths_first \
|
||||
-mmacosx-version-min=10.13 \
|
||||
-mmacosx-version-min=10.15 \
|
||||
-arch x86_64 \
|
||||
-L$(builddir) \
|
||||
-stdlib=libc++
|
||||
@@ -163,7 +163,7 @@ LIBTOOLFLAGS_Debug := \
|
||||
LDFLAGS_Release := \
|
||||
-undefined dynamic_lookup \
|
||||
-Wl,-search_paths_first \
|
||||
-mmacosx-version-min=10.13 \
|
||||
-mmacosx-version-min=10.15 \
|
||||
-arch x86_64 \
|
||||
-L$(builddir) \
|
||||
-stdlib=libc++
|
||||
|
||||
+7
-5
@@ -8,7 +8,7 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
|
||||
@@ -21,6 +21,7 @@
|
||||
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
|
||||
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
|
||||
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
|
||||
"lint:revisions": "yarn workspace @standardnotes/revisions-server lint",
|
||||
"clean": "yarn workspaces foreach -p --verbose run clean",
|
||||
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
|
||||
"start:auth": "yarn workspace @standardnotes/auth-server start",
|
||||
@@ -34,6 +35,7 @@
|
||||
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
|
||||
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
|
||||
"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\"",
|
||||
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||
@@ -46,8 +48,8 @@
|
||||
"@lerna-lite/list": "^1.5.1",
|
||||
"@lerna-lite/run": "^1.5.1",
|
||||
"@types/jest": "^29.1.1",
|
||||
"@types/newrelic": "^7.0.3",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/newrelic": "^7.0.4",
|
||||
"@types/node": "^18.11.9",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
@@ -59,7 +61,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.25",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.3.0",
|
||||
"newrelic": "^9.0.0"
|
||||
"@sentry/node": "^7.19.0",
|
||||
"newrelic": "^9.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,349 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.15.1-alpine
|
||||
FROM node:18.12.1-alpine
|
||||
|
||||
RUN apk add --update \
|
||||
curl \
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||
import { Period } from '../src/Domain/Time/Period'
|
||||
@@ -15,6 +16,9 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
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 (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
@@ -22,7 +26,12 @@ const requestReport = async (
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||
timer: TimerInterface,
|
||||
adminEmails: string[],
|
||||
): Promise<void> => {
|
||||
await calculateMonthlyRecurringRevenue.execute({})
|
||||
|
||||
const analyticsOverTime: Array<{
|
||||
name: string
|
||||
period: number
|
||||
@@ -96,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 = [
|
||||
StatisticsMeasure.Income,
|
||||
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||
@@ -137,6 +180,9 @@ const requestReport = async (
|
||||
const churnRates: Array<{
|
||||
rate: number
|
||||
periodKey: string
|
||||
averageCustomersCount: number
|
||||
existingCustomersChurn: number
|
||||
newCustomersChurn: number
|
||||
}> = []
|
||||
for (const monthPeriodKey of monthlyPeriodKeys) {
|
||||
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
|
||||
@@ -166,24 +212,35 @@ const requestReport = async (
|
||||
churnRates.push({
|
||||
periodKey: monthPeriodKey,
|
||||
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
|
||||
averageCustomersCount,
|
||||
existingCustomersChurn,
|
||||
newCustomersChurn,
|
||||
})
|
||||
}
|
||||
|
||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
||||
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
|
||||
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
|
||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticMeasures,
|
||||
retentionStatistics: [],
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
})
|
||||
|
||||
await domainEventPublisher.publish(event)
|
||||
for (const adminEmail of adminEmails) {
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailRequestedEvent({
|
||||
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||
subject: getSubject(),
|
||||
body: getBody(
|
||||
{
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticsOverTime,
|
||||
statisticMeasures,
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
},
|
||||
timer,
|
||||
),
|
||||
level: EmailLevel.LEVELS.System,
|
||||
userEmail: adminEmail,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
@@ -200,9 +257,25 @@ void container.load().then((container) => {
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||
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(
|
||||
requestReport(analyticsStore, statisticsStore, domainEventFactory, domainEventPublisher, periodKeyGenerator),
|
||||
requestReport(
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
periodKeyGenerator,
|
||||
calculateMonthlyRecurringRevenue,
|
||||
timer,
|
||||
adminEmails,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
logger.info('Usage report generation complete')
|
||||
|
||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
||||
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
echo "[Docker] Starting Worker..."
|
||||
yarn workspace @standardnotes/analytics worker
|
||||
;;
|
||||
|
||||
'report' )
|
||||
echo "Starting Usage Report Generation..."
|
||||
echo "[Docker] Starting Usage Report Generation..."
|
||||
yarn workspace @standardnotes/analytics report
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "Unknown command"
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@ module.exports = {
|
||||
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,8 +1,8 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.1.0",
|
||||
"version": "2.12.25",
|
||||
"engines": {
|
||||
"node": ">=14.0.0 <17.0.0"
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"description": "Analytics tools for Standard Notes projects",
|
||||
@@ -25,10 +25,10 @@
|
||||
"typeorm": "typeorm-ts-node-commonjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@types/jest": "^29.1.1",
|
||||
"@types/newrelic": "^7.0.3",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/newrelic": "^7.0.4",
|
||||
"@types/node": "^18.11.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
@@ -38,20 +38,21 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.3.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"aws-sdk": "^2.1158.0",
|
||||
"aws-sdk": "^2.1260.0",
|
||||
"dayjs": "^1.11.6",
|
||||
"dotenv": "^16.0.1",
|
||||
"inversify": "^6.0.1",
|
||||
"ioredis": "^5.2.3",
|
||||
"ioredis": "^5.2.4",
|
||||
"mysql2": "^2.3.3",
|
||||
"newrelic": "^9.0.0",
|
||||
"newrelic": "^9.6.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"typeorm": "^0.3.6",
|
||||
"typeorm": "^0.3.10",
|
||||
"winston": "^3.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
DomainEventMessageHandlerInterface,
|
||||
DomainEventSubscriberFactoryInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import { Env } from './Env'
|
||||
import TYPES from './Types'
|
||||
@@ -44,6 +45,13 @@ import { SubscriptionPurchasedEventHandler } from '../Domain/Handler/Subscriptio
|
||||
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
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -81,13 +89,24 @@ export class ContainerConfigLoader {
|
||||
})
|
||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||
|
||||
if (env.get('SNS_AWS_REGION', true)) {
|
||||
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
|
||||
new AWS.SNS({
|
||||
apiVersion: 'latest',
|
||||
region: env.get('SNS_AWS_REGION', true),
|
||||
}),
|
||||
)
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
const snsConfig: AWS.SNS.Types.ClientConfiguration = {
|
||||
apiVersion: 'latest',
|
||||
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)) {
|
||||
@@ -111,19 +130,30 @@ export class ContainerConfigLoader {
|
||||
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.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||
|
||||
// Repositories
|
||||
container
|
||||
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
|
||||
.to(MySQLAnalyticsEntityRepository)
|
||||
container
|
||||
.bind<RevenueModificationRepositoryInterface>(TYPES.RevenueModificationRepository)
|
||||
.to(MySQLRevenueModificationRepository)
|
||||
|
||||
// ORM
|
||||
container
|
||||
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
|
||||
container
|
||||
.bind<Repository<TypeORMRevenueModification>>(TYPES.ORMRevenueModificationRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(TypeORMRevenueModification))
|
||||
|
||||
// Use Case
|
||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||
container.bind<SaveRevenueModification>(TYPES.SaveRevenueModification).to(SaveRevenueModification)
|
||||
container
|
||||
.bind<CalculateMonthlyRecurringRevenue>(TYPES.CalculateMonthlyRecurringRevenue)
|
||||
.to(CalculateMonthlyRecurringRevenue)
|
||||
|
||||
// Hanlders
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
@@ -152,6 +182,11 @@ export class ContainerConfigLoader {
|
||||
.to(SubscriptionReactivatedEventHandler)
|
||||
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||
|
||||
// Maps
|
||||
container
|
||||
.bind<MapperInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
||||
.to(RevenueModificationMap)
|
||||
|
||||
// Services
|
||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DataSource, LoggerOptions } from 'typeorm'
|
||||
|
||||
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
|
||||
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||
|
||||
import { Env } from './Env'
|
||||
|
||||
@@ -36,7 +37,7 @@ export const AppDataSource = new DataSource({
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
},
|
||||
entities: [AnalyticsEntity],
|
||||
entities: [AnalyticsEntity, TypeORMRevenueModification],
|
||||
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||
|
||||
@@ -11,12 +11,17 @@ const TYPES = {
|
||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||
// Repositories
|
||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||
// ORM
|
||||
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
||||
ORMRevenueModificationRepository: Symbol.for('ORMRevenueModificationRepository'),
|
||||
// Use Case
|
||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
||||
CalculateMonthlyRecurringRevenue: Symbol.for('CalculateMonthlyRecurringRevenue'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
@@ -29,6 +34,8 @@ const TYPES = {
|
||||
SubscriptionExpiredEventHandler: Symbol.for('SubscriptionExpiredEventHandler'),
|
||||
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
|
||||
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||
// Maps
|
||||
RevenueModificationMap: Symbol.for('RevenueModificationMap'),
|
||||
// Services
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
@Index('email')
|
||||
declare userEmail: string
|
||||
declare username: string
|
||||
}
|
||||
|
||||
@@ -1,144 +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: [],
|
||||
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: [],
|
||||
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 { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -7,65 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
@injectable()
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
snjsStatistics: Array<{
|
||||
version: string
|
||||
count: number
|
||||
}>
|
||||
applicationStatistics: Array<{
|
||||
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 {
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent {
|
||||
return {
|
||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||
type: 'EMAIL_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: '',
|
||||
userIdentifierType: 'uuid',
|
||||
userIdentifier: dto.userEmail,
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Analytics,
|
||||
},
|
||||
|
||||
@@ -1,55 +1,11 @@
|
||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
||||
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
snjsStatistics: Array<{
|
||||
version: string
|
||||
count: number
|
||||
}>
|
||||
applicationStatistics: Array<{
|
||||
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
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||
@@ -9,16 +10,31 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionCancelledEventHandler', () => {
|
||||
let event: SubscriptionCancelledEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
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(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
@@ -30,6 +46,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionCancelledEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_CANCELLED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
@@ -41,7 +58,13 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
timestamp: 1,
|
||||
offline: 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 () => {
|
||||
@@ -55,6 +78,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
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 { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
|
||||
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 SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -15,10 +20,12 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
@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: 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, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
@@ -26,6 +33,23 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
])
|
||||
|
||||
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) {
|
||||
|
||||
@@ -2,23 +2,40 @@ 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)
|
||||
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',
|
||||
@@ -26,6 +43,9 @@ describe('SubscriptionExpiredEventHandler', () => {
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userExistingSubscriptionsCount: 2,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
@@ -36,6 +56,9 @@ describe('SubscriptionExpiredEventHandler', () => {
|
||||
|
||||
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 () => {
|
||||
@@ -43,5 +66,14 @@ describe('SubscriptionExpiredEventHandler', () => {
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
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 {
|
||||
@@ -15,10 +20,12 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
@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 } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
await this.analyticsStore.markActivity(
|
||||
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
|
||||
analyticsId,
|
||||
@@ -30,5 +37,22 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
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()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ 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
|
||||
@@ -15,16 +19,29 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () => new SubscriptionPurchasedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore)
|
||||
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',
|
||||
@@ -37,6 +54,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userRegisteredAt: 23,
|
||||
billingFrequency: 12,
|
||||
payAmount: 29.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
@@ -45,12 +64,16 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
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 () => {
|
||||
@@ -68,4 +91,12 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
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 {
|
||||
@@ -15,10 +20,12 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
@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 } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
@@ -54,5 +61,22 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
[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()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
@@ -10,18 +11,34 @@ import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHan
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { Period } from '../Time/Period'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionRefundedEventHandler', () => {
|
||||
let event: SubscriptionRefundedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
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(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_REFUNDED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
@@ -30,6 +47,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
offline: false,
|
||||
userExistingSubscriptionsCount: 3,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
@@ -41,6 +60,9 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
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 () => {
|
||||
@@ -56,6 +78,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.NewCustomersChurn], 3, [
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark churn for existing customer', async () => {
|
||||
@@ -75,4 +99,12 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
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 { 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 SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -15,10 +20,12 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
@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: 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, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
@@ -26,6 +33,23 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
])
|
||||
|
||||
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> {
|
||||
|
||||
@@ -2,21 +2,32 @@ import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionRenewedEventHandler', () => {
|
||||
let event: SubscriptionRenewedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () => new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore)
|
||||
const createHandler = () =>
|
||||
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_RENEWED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
@@ -24,6 +35,8 @@ describe('SubscriptionRenewedEventHandler', () => {
|
||||
subscriptionExpiresAt: 2,
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
@@ -32,6 +45,9 @@ describe('SubscriptionRenewedEventHandler', () => {
|
||||
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 track subscription renewed statistics', async () => {
|
||||
@@ -39,5 +55,14 @@ describe('SubscriptionRenewedEventHandler', () => {
|
||||
|
||||
expect(analyticsStore.markActivity).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 { inject, injectable } from 'inversify'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
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()
|
||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
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, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
@@ -26,5 +33,22 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
analyticsId,
|
||||
[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> {
|
||||
let analyticsEntity = new AnalyticsEntity()
|
||||
analyticsEntity.userUuid = event.payload.userUuid
|
||||
analyticsEntity.userEmail = event.payload.email
|
||||
analyticsEntity.username = event.payload.email
|
||||
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
||||
|
||||
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>
|
||||
}
|
||||
@@ -15,4 +15,10 @@ export enum StatisticsMeasure {
|
||||
Refunds = 'refunds',
|
||||
NewCustomers = 'new-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>
|
||||
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): 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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SubscriptionPlanNameProps {
|
||||
value: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
|
||||
export interface SubscriptionProps {
|
||||
planName: SubscriptionPlanName
|
||||
isFirstSubscriptionForUser: boolean
|
||||
payedAmount: number
|
||||
billingFrequency: number
|
||||
}
|
||||
@@ -26,4 +26,5 @@ export enum Period {
|
||||
OctoberThisYear,
|
||||
NovemberThisYear,
|
||||
DecemberThisYear,
|
||||
Last30DaysIncludingToday,
|
||||
}
|
||||
|
||||
@@ -62,6 +62,41 @@ describe('PeriodKeyGenerator', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for last 30 days including Today', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Last30DaysIncludingToday)).toEqual([
|
||||
'2022-4-25',
|
||||
'2022-4-26',
|
||||
'2022-4-27',
|
||||
'2022-4-28',
|
||||
'2022-4-29',
|
||||
'2022-4-30',
|
||||
'2022-5-1',
|
||||
'2022-5-2',
|
||||
'2022-5-3',
|
||||
'2022-5-4',
|
||||
'2022-5-5',
|
||||
'2022-5-6',
|
||||
'2022-5-7',
|
||||
'2022-5-8',
|
||||
'2022-5-9',
|
||||
'2022-5-10',
|
||||
'2022-5-11',
|
||||
'2022-5-12',
|
||||
'2022-5-13',
|
||||
'2022-5-14',
|
||||
'2022-5-15',
|
||||
'2022-5-16',
|
||||
'2022-5-17',
|
||||
'2022-5-18',
|
||||
'2022-5-19',
|
||||
'2022-5-20',
|
||||
'2022-5-21',
|
||||
'2022-5-22',
|
||||
'2022-5-23',
|
||||
'2022-5-24',
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for this year', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.ThisYear)).toEqual([
|
||||
'2022-1',
|
||||
|
||||
@@ -33,6 +33,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Last30DaysIncludingToday:
|
||||
for (let i = 0; i <= 29; i++) {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Last7Days:
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user