Skip to content

Commit

Permalink
fix: address feedback and fix noticed issued on move contact
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpascal committed Nov 12, 2024
1 parent 786acef commit 0173a6a
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 30 deletions.
36 changes: 36 additions & 0 deletions src/config/chis-tg/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,42 @@
"required": true
}
]
},
{
"name": "cj100_household",
"friendly": "Household",
"contact_type": "cj102_household",
"user_role": ["household_member"],
"username_from_place": false,
"deactivate_users_on_replace": true,
"hierarchy": [
{
"friendly_name": "CHW Site",
"property_name": "chw_site",
"contact_type": "ci90_chw_site",
"type": "name",
"required": true,
"level": 1
}
],
"replacement_property": {
"friendly_name": "Affected Household",
"property_name": "replacement",
"type": "string",
"parameter": ["\\(*Household*\\)"],
"required": false
},
"place_properties": [
{
"friendly_name": "Household Name",
"property_name": "name",
"type": "string",
"parameter": ["\\(*Household*\\)"],
"required": true
}
],
"contact_properties": [
]
}
],
"logoBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABJCAYAAADCOyPGAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAGSdJREFUeJztXQdYVNe2fnk3yc279d0LWKLRGDUae9SoUWNijVFUwHZjokk0sYsGUSxRo0axl9gV7BE7FrChAhYUEAu9KqD0OsDAADPzv7X2zBmBmVFQuYe87/5++xuHM2fP3v9ae6211977zH/9Vw2H5WRbK6spdsOtptgusZpqd4r+f5dek6gUUtHqi5LKY/pMIL0eptef6XWg5cQh/5S7/b9LEMl1qQwVZE61i6FSWmvaULw/eww6/jwJ/VbPxogtS/DNzhUYQ2X45sXovdIRHRZNxHuOX6PWVDvQPSoqUVR2UV1DLCfZ/K/c/arxsJxq8y4RtptKOhUw6X1WzYKL7zkEPIxEmiIbRSXFMAUtFWWxCkk5mbgdF4FfvU6ip7ODJAwuiVQ20IiykLufNQ5kMhowOVQKmKxuv9hjhYcb4tKTodVqTRJeGfC9wYlxWHbmINr89IMkiFQqKyyn2P5nRFhNsfkzkbGISh6TY7NxIa6E34Vao3lh0s2hRF2KM3f90GvFTEkQaVRmWUy2fVNuHmQB2eUmRMA5dqLtFozH9qtnkVekNCIuLy8PiYmJCA8Px507d3Dv3j08ePAAd+/eRVBQkCj8f/4bX+P3ERERePLkCfLz843qSyUztsnLHU1mj2YhlFBxoRH4rtx8/FtBnWYHm8ma+OW2ZcJum8KNGzfg5+eHtLQ0qFQqJCQkCHLXrl0LX19fHDx4EP7+/nB1dcW1a9ewevVqxMXF4fr164iPj8fNmzfFqynEpCVhFH23fjQkkUJ0kJuXaofFBOvXqLOOHDbWsR9GjtIdGhM2vrS0VBB94cIFQSKTGxgYCDc3N3h6emLevHmYM2eOuM5l6tSpQvPXrFmDvXv3YuvWrVAqlYZ7IyMjTfoSduhzj7mg7vTh0CvEN3JzVG2w+GEgk+/MGtdszre4HhVsREh2djYuX76MW7duGcwHazqbk23btuH48eM4ceIEnJycBMlbtmyBh4eHGAnp6ek4c+YMVq1ahdjYWHEvX5PMmI+PjygVzZKW/p0PDkC9GSNZCKVUJvxz/IDX5ObrlYJs7JvUsZ+4gxyN3IgOLUdCSkqKIJ01lYXAZiQ1NVVcu337ttDm0NBQYedjYmJw//59UdjmSz6B72UTxSPh0aNHUCgUOH/+PDTk0I8dOybqKSwsREhIiPh/Tk5OuTa4B91Aq/njdH5hit0MuTl7paBOTaOiaTzra0SmPDZ0mslhE8Fk8v+ZGCZv165d2L17t/gMO2C2+ZcuXRIjgK8XFBRArVbrNJhMC5ss1uyHDx/i9OnTwiwtWbIExcXFCAsLw9KlS8Wo2b59O5KSklBUVCT8C/uPsqbJO+I+3vnxX9JI6CM3b68EeoerrDdjRDnNZyKZfCaDtX/58uXYt28fxo8fj02bNgmyvLy8BIn8WRcXF2GGeISYA1/jz/Bnpc+xcFkwDg4Owmyxw2aTxcLkkcdmiYUs4ey9W6j/40jJJ/SWm7+XAg3lVpwSYPJP3Llu0Hp2jmxK2LQw0WzLHR0dhabv2bNHEMIEent7C4I4ktFUYW7An42KihL386jKyMgQ79l/nDt3DpMmTRLfxe8zMzPFKJFGIWPv9YtSdJRA5rOu3Dy+ECwn2/2JOuDHHVl4ci9FOxpB+NWrV4WTZLu8YMECYWrGjRsnIhwWCkc/kj/g0fGyYNMk1cm+hcnmEcAOe//+/di5c6cQihS28ohTa9QYvdNZSmO4W06x+6PcfFYZ1PB1TH73ZdORq9TZbO4oRyRsDpjg2bNnY8WKFTh69Kj4O4eYbKNfJv1gDlwnC56jLB5d7KzZXLFTljSfhcWC4bby3KTr0mnSSJgoN59VAjW4M6eL2aHFpD4RnWOCWfsZrGVs79m8MClsJjia+XeAHTbPnNnk8GjgyEgCt0tSEsaVsLt4e/oIFkAWmaJacvNaKfBwpQZ7SKanLDhWZyEwCQwOFdkUcYhpFholUBBQdabzfJ55mR28u7u7wczxTJvbxmbS8NU0ajjdrR8F+y2nDP2D3Pw+FzyRoVLcluL9zHyFUcezsrJECMiOll9zc3PFhKliXF5OABl7TV97FtK2mr3EEzs2RdwWjsR4/sDkc1sqIplMUXOaOHKfqPSSm99nwmqy7X9TI6NZY474e5slgMnmUJNjeyn2L2sKwLF9WgYKbt+F4oI3iu4erjL/Sl9Xca/yTjBKM7LKXWMTw6EpO2Y2ORwIlJSUmK3L2eOQNApOWU22q7mzZGrgEG7ox+S8ClSVj2DY5SZkpsHL8wwSxs9GeNu+iOg0ECENOyO4Xgc8/mEIjYQqRET02ZjPPhT3hrXujbDmn4r64qf9hAvXriApO6PydREy8nLR0OFL6JdBa27Sjhp3ngWw8dLJ55NOWp6YlY6jAT5iHYBXvzqMHSRIC2/bBylLNyBz92HEfzsDMb17QJtvnDsyWzd9NrRpRyRMmovcs15ImOCEsA8+hW+TTmgyYQhq2w/DWJfVOElzkzRFTqWirllHdkqjwFNunk2CGtaGp/Cc20/I1OVxWKujKQri1/iMVAQ/fogTgdewyH0fvlg7F40cv8J7DjawXdYXv+7riBCvRojq1BaRXQYh1/MKSlLSEf+dA8JbfgjVvcqbIVXQXiHI+LEzUZqeiexjZxHWqhei+7eC//kmWLenI/ot/hyNfrRBM6dvMGj9fKz0PCwWbEKfxIu2cpsjkxORnqfzTfHUpyazRktpioZy820EatRm1pA5R3cZUsw7fTxFboWdWNPZY8ApaP7M2/a26Ptzfzi7dkbAhfdRGlQb6qBauEXknB7WUZD3aMx0RH1ih+i+XyLknQ7IdVteaQHk7Jkm6ojp/xWi+/yLRtGP4r3HqA4IudxIfJcqsA78zjfDT9u6oueiL2gE6taO69oPF21lwdSfMRK+kQ90o4r6xIv/+lEwRW6+y4Ea9Dcq8dy4O4+iDEQkp4di874PsdzlI6xy7Yj9bm3gd64pFLffBu5aIdPvbZw43hIT1n2C92cOFp0bPbYP4od9gJQfmyJ7zbtQbG+AvB0NoLoytHLsU9RUdLYL8nY2EPdmr2iElOlN8WhICwz8ob/4jpazBmHm5q44fbIF8vzrirak+9WHr8f72HOorWirs0tH7DjUA3nKNEPVbrevSgK4Ljfn5UAN6sRDk7WG116fklGAgsAGKAqsDWVAHaTfrCc08LcjrWG/sRs6zRsgRkOHuQPEe0/35kjxrQ9tYC2hpTm36iLlRj2kUskLag2du342tKpE5AY0RvKN+uJexe260FBdWv9aSPB5B0eOtsTk9d1JCNaoP90GPRb0x4+busH9RAuEX3lXKAW3ldtcGPIpVfg0OuJNAnpnzFte/i437wboc/2YuHe9ESHfbXNARyK4/ZwBaOY4GHWm2Qpt/3xxXyzb9RGNiCbI969DJqE2Qkk4rr+1xfh1PfDZws/RarY1mjgMRlP6PBOF4qTnCiAp+SY+ou9r7DBE3NvGyRo9qS4W8MHDbRDj3RDF9F3ZJNxrHk0xd2sXMof9yB8MEcrwgeMg0VZu89ITG8rVzZsF2F/oR8EIuXk3QJr57rl+wYiQA15rMXrVpxi3tgfmb+uMA4dbI9hLZ4d56KeQpu491AZfruyJBjNsROfYOfdZ6UgC3QAn8ilOR3di/O61KMz1f64AYh56CEXgpUa+d5zrGvRcMVPYc66bhT9m9WfC9KXSiOQ2FN+pTc65MXZTO+Zs6SLaym2+EGjs+HnzgF4AB+XmXYAjAipPuFFRZRZbDMg+JToplVQi/NLp5liys5PQcs44crqaNetXCl/vJsSUN2NloaxEvsjMZwpLinErNhzLzx6iCGyOCHvrksZbL+0jRuJ1z6ZkfuqVaysUV4zq4ahOLwDus/x7i3gPJttE3hJoirh8cmIztw3At2s+FZ1tN2egsL0NyJbydsLFp/bDPy4C+apCo3tNk/usdQG6pnzw3Gp4+8vV8HtYcHKP2ATGkVpDGn3si2yW9RZtXbx3JLU93ehe7mODp36gmdz8swCcdNtLfjFNCYVvQzYswAdzv8PAdfNEgo7j7UcUa5fqlxUrDRZAqfkVMXGtOKFKVfLOiLi0ZBz29xYhNO875bDZ4dA2kzs2GKw4+lEwSG7+WQA7uTErPd2q1PEXQmEYqeAzHHFxIgnBWGtfNb7fvUYSgPyL99SIS89Lvr0yaFSkstHQhaNUMlzJx5zUvyfzU0jmR2vGf7xCLDl9QBLABrn5ZwEEc2N8Iu5Xe8ehIT8RO4pCHTvyhtbAPZpE3W+oex81EHg4tlzcXl3Y4W2IhA7LzT8LIIUbw3meakOeL5BFIWHsCCK9TvlIpWzha/FT6LNHyV9Un0IcD7wmCeCy3PyzAJScP+G9ltUGJt8c6eZKZULWF8SF4EBJAAFy888C0PA+Go5qqgVs0xOmC1I1QU+LRLQ2qPzfufDfkLoJlUldvAjY3OoFECI3/ywAEUdLKehXiuInZNfHCVufcdUCLd57He0/eB2dWr2B7u3egOuiv2KT01/QusnraNlYV/j/vy37G/mGd4BH4wG1maXOl8C1qGCtXgChcvOvGwEzRmppBFSPuunNT4qXBejrsOOnvyLe0wI/T/gz/v7X13Dv8D8QfuKf6N/tTXzc9g3x/ywfS90Iyb1YLU2iEaCtSSOAt5prY1KfVIMAqMrUDeUE0KX1G7Dr9Uc0eecP6PXRG8i/qSN7RL8/om/nN8v7gfRdr75JhPPBARq9AALl5v9pFJQYV8VpbSWQ7Q6EflhOAF8NeAv9u76JP/3Pa8IsSWSbFEBoB6Dg+Qm8quJogI+6JkVBIdwY7/B71ROAF4aTPW+E1MsW+DORfmb935F33RIfNn8d44a8hdJAHdljrN/CoB5lBBDSimbFpk/evCy2XjlTWpPmAV7cGLfbV02fH31ZcOrhQWOo71gJ26/005mcLG9LJJ6zEH8XK1pXLJDqZVFG+2nkVFMUtMh9b0lNmgnv4sYsO/NbJdKZ5ZGcnGzY58/QqorFfiBlwD2a9Oq3oRRFAk8WkEa3qfwcILQdVb4CUOkmh1xXwe0gCogU0Jb5Pt6ZJ21DrAq+3bWyuCblguZwY+x+XVRQ1Y7wfn3eOi7ILy4RC/ARHb9AeLt+iBs2HjknPKFW6I8UFQRWjnwaLSIpR+B7M13dEDvkO4R98BnC2/RBwsQ54rsYwcHBYmNWVfHJsumqmpQNteZtezQXUJWoSyuVCeNt4rwrmXdCM0qSU5F99CxC3vtY7F7gEtKgk3gfa/0NXU+jD6UASYuBlHVAeHc94bWp6FbWRC6IJ1/JzsR8PorCohDdczhC3u0i6pLqjfhooE6wNBp4Bx63gbelmzrSagrcx7enjyjVb1VsLjf/LAB+vAA/PAORKYnGB31NgPeFsuZx55VBwYj6bBieOC1DaNPuBqKkEtrsE2TtP16+gqIoEkI3miO4Uai5U2fvK6SpU5ZuLCdQqfCeo8RJcxHzxdfiu7kNfJassgKISE4sspKOttaEFTGGle7ANT/ToVJmiHcl8xEhtr8lj5PxcOQkMg1ORmRJJffMJeNKNGVcjtp4E3Dqmu0m62Ihsxl6OHyC+G7esc0nMU+dOlUpAay/eDxPL4BDcvNuADVmITdqrMvq5wqANe3kyZM4cOCA7g9kBngLYsqyXxHdZyRCG3c10tic456VIqcs0ja6ILKzdbm6wlr2FH4mdcVmXZ303awEFy9eFKdlnjcKSjVqTe+VjoV6AfxLbt4NoMZ0oaJ+z/GrohK1+pnzAX7cgJ+PL47v3a/7g0aDjB0HEGf3vdiUW1FjY23G6ux1FVGamU32fkD5+up3FD4gbuh45J6+yPtMUKhUwtfjHPbt3i0ObTwLNNtX1ZsxUq1bD7b9h9y8G8CblKx0j4NB4MOoZ8Z1fD4raNUmBHYbDMWlaxSwJIkthNJO6LIlpNHHeDhioghNqwpVbDxiBo4p54ANZohGGUdZ/N1pLodw71M7nHZeI3zBs7DvxkWlXvv95ObcCNSobdy4mW7bFFqt1uzWBWdnZwRt3CE2y7LzLUlKQeLU+abt9fufIMV5Mx7bL0T65sod0tAWqYQ5ix83k+7ZQ0LsYrLulOWbUBQaJULTiE7WuLRpuzgoaA4a6pP1uvmS+bGXm28jUKPa8/bEFnO/UyZkppmdlPF5YH7uQ+wvG4Qmxo9zFHv3TZHEoyLRfgGKwmPweOYSiufHIvvIGbMkMeE8mjiiUlz0QeLkeQh+5yOTdccO/k5sAA5+vzsSt+zB2bNncejQIbN1x6YlFTZ0GFWq3x3dSG6+TUJKS6w9f8z4rA90M92T39sj4uwFeHp4UBR5SkyQQpt0M0lSZNch4jNJc52RTa/hbfqK8BEmtovwDJfr4XtyyL4/HDkReVdvCsdrqu6wVr0R/uHniD91Dr7e3rixzRXeC53LzZTLYtK+DQq99l+Um2ezsNKdikf7RRMLlKoiI2esuORrmGQFfGJDk6wxwjFKZBtpKc1g1dm5eDxjkTBFyoD7Ysu6Ktp4/TnP+6YYSUVRcUiat0KEoZr8AiNHzKOOHbHkY8IHjkZg10GGEaeKeWRUd0puVlHd6cNZ83kO0EVuns3CcpLtH6iB4SwEV99zRqlITYESCXsO4/rwcQij8DK8dW/EDBgtTrCwZhtpacte4pyYhux66sqt4tRMaVqmOHRRESVJqUJYidN+QvoWnb8ouBVkFNYKwQ76RvgINlehnKLoboPzdt/g8eFT0Jo4Kzbr8I58vfafsJxSg8+IMaiRU6mUNJ/zbUFWQZ6RL+BjoAf3H0DItRsiClEr8qCKixcpB1OmIrrXCLqeAG1xMR5PXygcrCkTxOEsH0lKW7dTmBFl4AOjeYAo5BM4smJhqXMVSL77AGE3b4kT82WPqEogf6Zs5PgVZz+59JOb3+fCaordW2wnWWPmHnPJMmYKYtLDjrgcf4VFSN+0B2HNexiRFtXdFoX3w0RsHzvoW0FeRbCQeNKmURZCeYdIbfGZUT2c5GMnbsi0EvhpXPyELT6tbyxTjYaTjHrtP0J9e11ufisFamx3tpdvTx9eTNGDEVt8RHT9+vUmH8DBIyJ+rIPJkJQJ5pSFlM0sRxaZNx5F7EtM5YCSF60Vpqwi+LlEHJmVOyarh8f92wW1pw3j5cccKr+vh3ZQg7ew5nT8eZJCUag0OmPKTyox9zAOTb5SZEdNmY/Ce+ZP1Rf43zM4dYPgaEQpvK6ZjW74mRUcglZEfEZqYbsF41U1Nu5/HqjRf+FFa2GKjrpkaSpMzvjZEOaOh2pL1cg6eALh7fuXy5CyQ1XeDTEvAHK6ZWfUfC+bnVyPy2bvYbvPj7IpixK1WmOzcaE06/W0mmr7ltx8vhD0k7OSuvbD1UcDfFLMslAWZJbSNriIPD47y+h+X5aJ3XsJLTeHPG8/EtLTOQXfG/nxYFFX7mkTGVUz2OTlLsX8/DTF+nLz+FKgTnzF/qCO/bCSG9Ehz9jcD90CyfyVBjMS1qKnyJRGdh1cZnbcScyeC4MjDLflX/cXwipreji8ZYdrCEOpzrSNrs8l3+321Txqq1pv97+Qm79XAivdoyo1DRy+VEWlPDYrBHakYiWMCqcTpGiH/84z4aieww0azoSLa+RYWcslk8NRUo77eYPN55y/lGuK6mFnMs6X4PnAP58mXOx0WQAD5ebtlcFK9wibX7hjND9Q0kgwmapgcCQT2qyHmIBVhFalolB1tyCTF1V0wilEnO043cIN2XptifGqaIHfHXGdT92bgfag3+WCprPHlOhzPU6WE4bU7AlXVWE51Y6fG7qebWvjWV8X+UY+MBxl4UmW4qIvFOeuCi3mJJk6r8ziCJmmoogYFMc/FpOtmH6jaBKlz3qTz+D1Ag5RkxauFmkK/mxZcGgb1WMojZRBIkmX53OrTNVa7fFAXzI7Bs2fwW2Vm69qgcVkG36cDZ8pLqo1bah67fljGdT/0pwT58RuCM7bPBptLzS2LApDI3XxPZmm8PafiwX3ilBc8BGTL04/s0kyCEgPDmsffT1NpJ/5OueVlKqi0qn7fy2gtkix/niLif/PNN8UqKOj9B3mA3wZyTmZSp7lsqaatRGq4mdeN2g77/0xMeGSwM8OKs3KQXhSfOHAdfOkWS4/Sf1juXn5t8Jqim1zffpa22Lu2EIK/bLzipTVs7uuDJJyMlUrPNzyG8405Pb38ZPc5eZDFpAQ+CEfy6z0P9zw+WqnnEuhd7LVGs0r3+hbXFpScsTfO7/z4inSqhY/mHW+5WTb398jKV81SBDvWekedyPIab9wYv6SU/tzY9OSeHnzhYXBs++gR9H584+75lOEI6UV+OdR1lhNtv3PD/xUBJHCP+qwX6+dTJaWNDZz65XT2bdjw/NSFdmqwpLiYi2Z+opks6CUxariJ9kZRTeiQ/PJzOS0WzA+2+rpb8g8IVOzmb7j9/H4STlBZqEekTWCSDtKJU5vq7XvzhylavvTD6ruy6bnW6+bnztiyxLF8M2LFQPWzs3t9ou9svX873mLpLRxljWef4HJleqytZzyH41/IZDG1iYSRxCJS63E74jZ8u+IJVuZ/B0xkfzjvP1iKtYWv4NfTPo/eVWg03a67QYAAAAASUVORK5CYII="
Expand Down
6 changes: 6 additions & 0 deletions src/config/config-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const WorkerConfig = {
port: Number(environment.REDIS_PORT),
},
moveContactQueue: 'MOVE_CONTACT_QUEUE',
defaultJobOptions: {
attempts: 3, // Max retries for a failed job
backoff: {
type: 'custom',
},
}
};

const assertRedisConfig = () => {
Expand Down
9 changes: 5 additions & 4 deletions src/lib/queues.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { v4 } from 'uuid';
import { JobsOptions, Queue, ConnectionOptions } from 'bullmq';
import { JobsOptions, Queue, ConnectionOptions, DefaultJobOptions } from 'bullmq';
import { WorkerConfig } from '../config/config-worker';

export interface IQueue {
Expand All @@ -17,9 +17,9 @@ export class BullQueue implements IQueue {
public readonly name: string;
public readonly bullQueue: Queue;

constructor(queueName: string, connection: ConnectionOptions) {
constructor(queueName: string, connection: ConnectionOptions, defaultJobOptions?: DefaultJobOptions) {
this.name = queueName;
this.bullQueue = new Queue(queueName, { connection });
this.bullQueue = new Queue(queueName, { connection, defaultJobOptions });
}

public async add(jobParams: JobParams): Promise<string> {
Expand All @@ -37,5 +37,6 @@ export class BullQueue implements IQueue {

export const getMoveContactQueue = () => new BullQueue(
WorkerConfig.moveContactQueue,
WorkerConfig.redisConnection
WorkerConfig.redisConnection,
WorkerConfig.defaultJobOptions
);
77 changes: 53 additions & 24 deletions src/worker/move-contact-worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios';
import { spawn } from 'child_process';
import { Worker, Job, DelayedError, ConnectionOptions } from 'bullmq';
import { Worker, Job, DelayedError, ConnectionOptions, MinimalJob } from 'bullmq';
import { DateTime } from 'luxon';

import Auth from '../lib/authentication';
Expand All @@ -25,7 +25,13 @@ export class MoveContactWorker {
this.worker = new Worker(
queueName,
this.handleJob,
{ connection, concurrency: this.MAX_CONCURRENCY }
{
connection,
concurrency: this.MAX_CONCURRENCY,
settings: {
backoffStrategy: this.handleRetryBackoff,
}
}
);
}

Expand All @@ -40,40 +46,56 @@ export class MoveContactWorker {
const jobData: MoveContactData = job.data;

// Ensure server availability
if (await this.shouldPostpone(jobData)) {
await this.postpone(job, processingToken);
const { shouldPostpone, reason } = await this.shouldPostpone(jobData);
if (shouldPostpone) {
await this.postpone(job, reason, processingToken);
throw new DelayedError();
}

const result = await this.moveContact(jobData);
const result = await this.moveContact(job);
if (!result.success) {
job.log(`[${new Date().toISOString()}]: ${result.message}`);
const errorMessage = `Job ${job.id} failed with the following error: ${result.message}`;
console.error(errorMessage);
this.jobLogWithTimestamp(job, errorMessage);
throw new Error(errorMessage);
}

console.log(`Job completed successfully: ${job.id}`);
return true;
};

private static async shouldPostpone(jobData: MoveContactData): Promise<boolean> {
private static handleRetryBackoff = (
attemptsMade: number, type: string | undefined, err: Error | undefined, job: MinimalJob | undefined
): number => {
const {retryTimeFormatted} = this.computeRetryTime();
const fullMessage = `Job ${job?.id} will be retried ${attemptsMade + 1} time at ${retryTimeFormatted}. Due to failure: ${type}: ${err?.message}`;
this.jobLogWithTimestamp(job, fullMessage);
return this.DELAY_IN_MILLIS;
};

private static async shouldPostpone(jobData: MoveContactData): Promise<{ shouldPostpone: boolean; reason: string }> {
try {
const { instanceUrl } = jobData;
const response = await axios.get(`${instanceUrl}/api/v2/monitoring`);
const sentinelBacklog = response.data.sentinel?.backlog;
console.log(`Sentinel backlog at ${sentinelBacklog} of ${this.MAX_SENTINEL_BACKLOG}`);
return sentinelBacklog > this.MAX_SENTINEL_BACKLOG;
return { shouldPostpone: sentinelBacklog > this.MAX_SENTINEL_BACKLOG, reason: `Sentinel backlog too high at ${sentinelBacklog}` };
} catch (err: any) {
const errorMessage = err.response?.data?.error?.message || err.response?.error || err?.message;
console.error('Error fetching monitoring data:', errorMessage);
return true;

// Handle server unavailability (HTTP 500 errors)
if (err.response?.status === 500) {
console.log('Server error encountered, postponing job...');
return { shouldPostpone: true, reason: `Server error encountered: ${errorMessage}` };
}
return { shouldPostpone: false, reason: '' };
}
}

private static async moveContact(jobData: MoveContactData): Promise<JobResult> {
private static async moveContact(job: Job): Promise<JobResult> {
try {
const { contactId, parentId, instanceUrl, sessionToken } = jobData;
const { contactId, parentId, instanceUrl, sessionToken } = job.data as MoveContactData;

if (!sessionToken) {
return { success: false, message: 'Missing session token' };
Expand All @@ -86,7 +108,7 @@ export class MoveContactWorker {
const args = this.buildCommandArgs(instanceUrl, token, contactId, parentId);

this.logCommand(command, args);
await this.executeCommand(command, args);
await this.executeCommand(command, args, job);

return { success: true, message: `Job processing completed.` };
} catch (error) {
Expand All @@ -112,7 +134,7 @@ export class MoveContactWorker {
console.log('Executing command:', `${command} ${maskedArgs.join(' ')}`);
}

private static async executeCommand(command: string, args: string[]): Promise<void> {
private static async executeCommand(command: string, args: string[], job: Job): Promise<void> {
return new Promise((resolve, reject) => {
const chtProcess = spawn(command, args);
let lastOutput = '';
Expand All @@ -123,12 +145,13 @@ export class MoveContactWorker {
}, this.MAX_TIMEOUT_IN_MILLIS);

chtProcess.stdout.on('data', data => {
console.log(`cht-conf: ${data}`);
lastOutput = data.toString();
this.jobLogWithTimestamp(job, `cht-conf output: ${data.toString()}`);
});

chtProcess.stderr.on('data', error => {
console.error(`cht-conf error: ${error}`);
lastOutput = error.toString();
this.jobLogWithTimestamp(job, `cht-conf error: ${error.toString()}`);
});

chtProcess.on('close', code => {
Expand All @@ -141,22 +164,28 @@ export class MoveContactWorker {

chtProcess.on('error', error => {
clearTimeout(timeout);
console.log(error);
this.jobLogWithTimestamp(job, `cht-conf process error: ${error.toString()}`);
reject(error);
});
});
}

private static async postpone(job: Job, processingToken?: string): Promise<void> {
// Calculate the retry time using luxon
private static async postpone(job: Job, retryMessage: string, processingToken?: string): Promise<void> {
const { retryTimeFormatted, retryTime } = this.computeRetryTime();
this.jobLogWithTimestamp(job, `Job ${job.id} postponed until ${retryTimeFormatted}. Reason: ${retryMessage}.`);
await job.moveToDelayed(retryTime.toMillis(), processingToken);
}

private static computeRetryTime(): { retryTime: DateTime; retryTimeFormatted: string } {
const retryTime = DateTime.now().plus({ milliseconds: this.DELAY_IN_MILLIS });
const retryTimeFormatted = retryTime.toLocaleString(DateTime.TIME_SIMPLE);

// Delayed this job by DELAY_IN_MILLIS, using the current worker processing token
await job.moveToDelayed(retryTime.toMillis(), processingToken);
return { retryTime, retryTimeFormatted };
}

const retryMessage = `Job ${job.id} postponed until ${retryTimeFormatted}. Reason was sentinel backlog.`;
job.log(`[${new Date().toISOString()}]: ${retryMessage}`);
console.log(retryMessage);
private static jobLogWithTimestamp(job: Job|MinimalJob|undefined, message: string): void {
const timestamp = new Date().toISOString();
const fullMessage = `[${timestamp}] ${message}`;
job?.log(fullMessage);
console.log(fullMessage);
}
}
5 changes: 3 additions & 2 deletions test/integration/move-contact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('integration/move-contact', function () {

const queueName = 'move_contact_queue';
const connection = { host: '127.0.0.1', port: 6363 };
const defaultJobOptions = { attempts: 3, backoff: { type: 'custom' } };

let sandbox: sinon.SinonSandbox;
let addStub: sinon.SinonStub;
Expand All @@ -32,7 +33,7 @@ describe('integration/move-contact', function () {

beforeEach(async () => {
sandbox = sinon.createSandbox();
moveContactQueue = new BullQueue(queueName, connection);
moveContactQueue = new BullQueue(queueName, connection, defaultJobOptions);
addStub = sandbox.stub(moveContactQueue, 'add');

handleJobStub = sandbox.stub(MoveContactWorker as any, 'handleJob');
Expand Down Expand Up @@ -116,7 +117,7 @@ describe('integration/move-contact', function () {
await new Promise(resolve => setTimeout(resolve, 1000));

// Check if the job has failed
const job = await moveContactQueue['bullQueue'].getJob(jobId) as Job;
const job = await moveContactQueue['bullQueue'].getJob(jobId) as unknown as Job;
expect(await job.getState()).to.equal('failed');
});
});

0 comments on commit 0173a6a

Please sign in to comment.