diff --git a/404.html b/404.html index b44cb75..964e025 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | Schulcloud-Verbund-Software Documentation - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/images/server_area_dependency_v3-c5729a31749381e23c81242a00541ec7.svg b/assets/images/server_area_dependency_v3-c5729a31749381e23c81242a00541ec7.svg deleted file mode 100644 index b73685f..0000000 --- a/assets/images/server_area_dependency_v3-c5729a31749381e23c81242a00541ec7.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - @shared@infra@core@modulessubmodules/apps@infra@modulessubmodules/migrations \ No newline at end of file diff --git a/assets/images/server_area_dependency_v3-d2fa547887f0c01e8bbbadc65351db31.svg b/assets/images/server_area_dependency_v3-d2fa547887f0c01e8bbbadc65351db31.svg new file mode 100644 index 0000000..326a8e3 --- /dev/null +++ b/assets/images/server_area_dependency_v3-d2fa547887f0c01e8bbbadc65351db31.svg @@ -0,0 +1,2 @@ +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1daXfayrL9nl+R5ff1WKfnrjqfrlx1MDAxZI9xPMSz/d5dWVx1MDAxOIQhjFx1MDAwNnm86/z3V41cdTAwMWQkoSHCXGKbnFx1MDAxYtZcdTAwMTlcdTAwMTKEUKtVe9eurqrmP1x1MDAxZj5+XFxcblx1MDAxZfv+0l9cdTAwMWaX/Idqpd2sXHIq90t/uPfv/MGw2evSITH6+7B3O6iOPtlcYoL+8K8//1xmz/Cqvc7zWX7b7/jdYEif+1/6+8eP/1x1MDAxOf03cp2BX1xyKt3rtj86YXQovJRcdTAwMTJ68t29Xnd0WVx1MDAwM1x1MDAwNq1cdTAwMTaA41x1MDAwZjS7Nf/BfeVcdTAwMTXbXHUwMDBiv6w5XFyjQVx1MDAwNH6NjtQr7aFcdTAwMWZcdTAwMWVxby3B15bmkl+3zjrnrYPeyu7+0XBcdTAwMTieXm+220fBY/v5TivVxu0gMtJhMOi1/LNmLWjQcT7x/vi8YY/mJTxr0Lu9bnT94TB2Tq9fqTaDR/dcdTAwMWVj43efp+avj+E77lx1MDAxNrll1jM0bqtccmNKcTk+7L5AafQ4U4Zbzq1cdTAwMDWt1MTIPvXavYFcdTAwMWLZ/7DRK1x1MDAxY9tVpdq6plx1MDAwMXZr4Wdq7Kper4efuX+5X8GYJ9BcdTAwMTi6iGZSMmnGXHUwMDFmafjN60bgRirBY0KhVWhcdTAwMDRKQFxih+KPXHUwMDFlXG5ndFx1MDAxYpKDXHRcdTAwMWa1XHUwMDFiQH+7NrKaf4ePYlDp+NvulO5tu1x1MDAxZJ3Nbu1lNn9YV2hf8uWdv8M7dJ9fn7TLqG1GrKPO9u9uXHUwMDFlz0/3O3Jl90ZUTjfuv5vxTMRcZrkyXHUwMDE49O6Xxkf+/iPveyXb+HRk14PG3d7RcKePy6f1088lfO/a0KI8PLlmonFcdTAwMWPsLne2j7budbHvfflTON+3/VrlXHUwMDE5N2RHSllcdTAwMDXCMFx1MDAxOZpSu9ltTT6Mdq/aXG6h9iEy4Fx1MDAwNPBcdTAwMDP/IUjDvJCYhXnBXHUwMDA1XG5rhOJpoP9aXHUwMDFj9Fx1MDAwZo314GhcdTAwMGL04Z3+PuycXFx/e4TDi8VcdTAwMDe90Z4hrCiFoLXSNlx1MDAwNnrNhGe4XHUwMDA2iVZcdTAwMWHB9EyQR2t1vZKEPHBPac0s8FxiM4/BrswkuNFy5JqhnVx1MDAxZNuxXHUwMDAzXHRcdTAwMTCnmy1NltHGSqFgXG6zXHJH1etcdTAwMDZHzaeR6bHYu1x1MDAxYpVOs/34w1x1MDAxOT7btbNnmrt/XHJcdTAwMWKVgV/7v+6/XHUwMDAyf1x1MDAxODS710uxj6y0m9fOzJfafj1u/0GT/Of4cNDrh0erdMlKs+tcdTAwMGaSU9RcdTAwMWI0r5vdSvv455enO/e3xsTs8fBcdFZug96hP3y+1WBwm4/cXFyXLU2my1ZcdTAwMTLIbkXEbiPoPSyOXna3t1W7qFxm7ne7qztcdTAwMGZcdTAwMDdbx8v1ulh89DLjca604Fx1MDAwNFx0xiC0XHUwMDFj91x1MDAwNVx1MDAwNrXHlVx1MDAxNMJcdTAwMTDTastgYmDTwbeiazBHj8015yBEXHUwMDE5qC7dY1x1MDAwZrv17+tf177q2nVNfunub9d2XHUwMDFilVx1MDAwNfbY+KSrp90vZ5tBp1NtN/fv2Oe7m3I8NlxirqVUNkpcdTAwMDDz8NjIs1x1MDAxME/CWoCwqVx1MDAxYf2oOOC/n6+c14f11u1X9ri8Y1bW+cW308VcdTAwMDe8taTCXHTSllx0kuBxiU74c1x1MDAwMt5yYbgxXHUwMDFhcSa8Z7lrilx1MDAwM8TodOugjCrFZ1x1MDAwYpmEt9FAhmNwdni/xmmPtCZxoIzYzdyddrNLd/PGnnrimm/inlx1MDAxNbDJd8dgJXmPXHUwMDE2jVxmXHQ/XHUwMDAy1+PicD1rrVeb+lx1MDAwNnauWntcdTAwMTV7OFhcdTAwMGJcdTAwMWXvXHUwMDBlXHUwMDE3XHUwMDFkrsSVxiPxoiTJbMtwMqJcdTAwMDZPc5DuXHUwMDFmy42UcmJgU+JcdTAwMTV9jEqWsiNqyVx1MDAxMVx1MDAxNS9cdTAwMDPAv0xEvXvYW1s7uD3oVTqqfTeAs7PDz90y/PPG3vmmwVx1MDAwNm53vpzh6caywe3HXHUwMDEyvje4qlx1MDAxZu6vXGb6d1x1MDAxYttcdTAwMDfnu4PB3srNt/tcdTAwMTL8vpTk9Fx1MDAxNTmfiMOZV6TOMiN1XCJwYYXkqYH6SXEq6XypfD6p1lx1MDAwZTeXWzvbrdWzytPN1vLCU1x0uXxPakV6XlH0K0KAjuJ0rlxiv1x1MDAwNlx1MDAxOFx0NCO0sFx1MDAxM+N6R88vjWFcdTAwMDJBvavjXHUwMDA3xmGaRaZcdTAwMTlcdTAwMWR/tVx1MDAxNzWZN/H78Uu+jduX2Vx1MDAwYulcdTAwMWGVsdymev3T4lBtnFxc2Fx1MDAxNdhum71cdTAwMDe/+/SIrZVcdTAwMDPVX3iocsM8XHUwMDE0XHUwMDFjmTZaWinia2qWcU9JaZV2q0g24o5fhVVTXHUwMDE3NTs3r1x1MDAwZtqxPspFjMpcdTAwMDefez3/snV5rzbO749X/bpq67NcdTAwMTK86Lyi/XmpiVx1MDAxOdbnc7/3oG3268ePa0edk2btbKVyedrcr5bwvec+PPK7Lzudi5XD097BTe90/+SohO99XFxcdTAwMWa2V1x1MDAxZuHm/vBcdTAwMTR2v+9eXHUwMDA19e5FwfktoH6YklO5kFepXHUwMDFmulhmJGWBnFx1MDAxOfKIXHUwMDA0i3DqWXFOrWxcdTAwMGWWe1uX21x1MDAwZjfrVbV+cP7pXHUwMDE49dXCc6rg4KFcIopiRmuMpHOeXHUwMDE3PlxmXHUwMDFkXHUwMDE0pFxyiU6VmTGOylA/qD1cdTAwMDBcbtI4qVx1MDAxOVx1MDAxMMBDv5+jfkiykvpAVkJcdTAwMWXy1eqHPLGO0P/c1U+nV7tt+8M3XHUwMDE2QImrvolcdTAwMDYyKCbfXHJcdTAwMDFLPp5cdTAwMTCrQ9aIXHUwMDAw9rw4YG+2d87wS6umT9ZXrk9a16ubXHUwMDFie4svgoSRXHUwMDFlUlxcwqV22lwinFx1MDAwNHc+WO1JRO6qLSxwPVtm4krUxdXVvDRcdTAwMTBFpFx1MDAxYYS2XHUwMDEwgntxNNC8fPRcdTAwMDDFttp+OPgmN9e3h4+b1Z31nYKZiVxcX+oyXGIkKlxyMlx1MDAxMVx1MDAwNeo8fKmK5GcnoEkhNCGT/GlcdTAwMWEyL4oj86vqn53ri+HJ56uLh83rXHUwMDFkvtfoPS08MiUnb2lBXHUwMDAxaFI2yOKrkii5Z9GANUZqy3HG8CTDmXJmPeDGXHUwMDE4bUbPgqVAM+lNabxcdTAwMTSNXHUwMDE4/p7e1CBxxDTLYLN50+Ht1bv407Trvo1HzS7P48CY1JL0X1x1MDAxYW4vi+N2o9W62pOBudzrN+6eztXZ7srm8cLj1lrmXHUwMDExWIg/3WqWXHUwMDBlp+k51y9dJYBRmllcdTAwMDaKz7ZcdTAwMDJYr1exivPyqNa41UMlXHUwMDE20aHOa1FhhiX/nzpUwUmGyrmn5HOqcEjmMiRcdTAwMWSnU1x1MDAxN/yuiiPz+knWIbhcdTAwMTTLvZ0zU+neXHUwMDFjrpxvLH6aXHUwMDBmlPUo8nRLelx1MDAxNKeqyVx1MDAwNb/Rurkw5HNRXG6tZyuczfKoiuJjdzbXQoNcdTAwMGI5XHUwMDBieVRcdTAwMTK3THBWylx1MDAwMt+riunc2oqk/+lp9OBsLvXPSr//xt504pJvszzPMzNp0q08XHUwMDAzs6lLSbXiaP1+clx1MDAxM7RcdTAwMGY+XHK+XHUwMDA1XHUwMDE319Uv68OLXHUwMDAzWakuOlo5+UfSv5pLYbVkqOJFc2C0R0BWymhpXGZcYjExsIUqmlx1MDAwM6M4XHQmvYie9Fx1MDAxZlCExiOXmIBcdTAwMTAqRjKMqVQp6lx1MDAxN4eQVJufsFx1MDAwMZf3ne3vUm988S8vu79cdTAwMDCELHquqEdcdTAwMDL5XHUwMDFhLeKrsSi4J7QkX2eZtnZyWO9bhSZcdTAwMTTj0Vr/d0hGXHUwMDFibsXbebv/mio0YzPXezhIgYKl1ojXi2P1cmX1fDA4P1x1MDAxYnzbqu+pb/dB/atd/MJcdTAwMTEmXVUoXHUwMDEzgKRcZpmWcXFcblZ43IKxLqQ0nC10NprCW3Q14ovY1TVDVrOAu3N1PSyKp7lcdTAwMDR4kcW+SVx1MDAwNJFMYlxmIL3P4ro4hlY261x1MDAxZHHzsN26/9bfuDj9ttlcdTAwMTjK5uJjXGIowFx1MDAwM6CnoVx1MDAwMdTk2lx1MDAwYlx1MDAxMsKkVlx1MDAxNFx1MDAwMYLkrkBrLlx1MDAxZe9V+UdyeEJJXHTqneK7lyVTbafq8ZvR4/03JSCtzJSooFxcOzOLZFpcIpBtXHUwMDE0h+zmcqu2bDbqnVx1MDAwMI6G+snet87PXHUwMDE2vrFRXGJjPOHiI8IkizqT5/OV9CxcbmRIvlxiOJstyptvXHUwMDAykihcdTAwMDWlXHUwMDBi2Fx1MDAxNzHK+yckXG4xXHUwMDEzQopGwLSBVK/XLFx1MDAwZSH9sNboXHUwMDFjfd5a/7w+vHy8PHnaMMeXXHUwMDBiXHUwMDBmIcnRk1xckvXRf13v/1x1MDAwNIaQXHUwMDBlMy5JVzJccrhQmULBjDKMR2uBfqdcbn/BVOEzZ6SAVsjMwlx1MDAxYqJ1yyzHcOJcIqDtXHUwMDE3XHUwMDA3bX4qaO6gXHKHP12WkPyeJp2qXGIxQsNEoZzlXHUwMDA0WWuUXHUwMDE1JCWjZXRlZlx0XHUwMDE1U6SHJaOYm1xiVJjw7seIlVx1MDAxNJKSS7Q0XHUwMDA20Fx1MDAwNJaIZFx1MDAxZcd6yFGQXHUwMDBimIfTXHUwMDEzP3d6ebWgUlx1MDAwMLyqj3BcdTAwMThUXHUwMDA2wSqZo+uhj1xy7GVTm+1cdTAwMDL56Vx1MDAxMVx1MDAxM1Rv3SiXmUfaXHUwMDAylNueXHUwMDAxLNOI4VwiiJuxSn8010hosVxcXHUwMDFhXHKkRcJcdTAwMTlcdTAwMWRcdTAwMTnxg1876DW7wctcdTAwMWQkJsbv1n4+4Pw6/ciAmcesJS2jjNBcdTAwMTaVRW5cdTAwMTLj5VONr11cdTAwMTlcdTAwMDafep1OM1xi4p+MT/qKI5KGX0lcdTAwMThcbt1f9Ngk4/TdN8ZVUPinjyH4Rn9cdTAwMTn/+d9/pH56OVx1MDAxM1x1MDAxOe6VwET4dVx1MDAxZqL/n5orIbuXXHUwMDFhtETQLL2b+qY4V+bXzC8qV3KNeY1cdTAwMWFEllxutVx1MDAxNpaI0lx1MDAwMJutrDizpEKBZ+kpXHUwMDEwMknbIKmclFx1MDAxOIFcIlx1MDAwNFx1MDAxYVx1MDAwNyktKdSoXG46oXeAboNE2lxcujNfzZZh1/WrtkopyJaFyYfY0nXN04yTqERhhFwi1kzSz+soMH9cdTAwMDOROFx1MDAwNVx1MDAwMvlfLjQ5aVx1MDAxN1uo5Fx1MDAxOFx1MDAxNJmEcaXulpOIlFxufm3ay7bx0eGkeZfFfDo7JcCNkopjKvFccopcdTAwMTNffuvx4lx1MDAxMp/1XGKaXHUwMDEyJD1cdTAwMGKj48XZ5Jc8XHUwMDEwllx1MDAxZVx1MDAwNbjYbralkUza09xzyWvGXHIo8nqYSnvWPqdcdTAwMThdko/JXHUwMDE0lYjKLUxbLCGhVzrvoWKRWsbyeS9/S4Q449CLonRw231cdTAwMTm3VZBcdTAwMDFcdTAwMWSVgTMxX/5uh1x1MDAxM/yrXeVcdTAwMWZKTqanXHUwMDE4XHUwMDBim5lyXHUwMDA28WtRXaZdj1x1MDAwZSdNuiyqk5lcIk9b8jQ6unxcdTAwMTihuqA41eXvVrSgVMeBWY9LMdqzjVtcdTAwMTLgMa4zKDzBXWykNDKOs+1wl0V2zKN4kaJZXHUwMDEy9kDcwGRKtVx1MDAwMrplLi4oUiNcdTAwMDeJOtlJXHUwMDA2YtTZv4BMR1NcdTAwMWPdtqF8pptGWzFB3EJcdTAwMDEvurlkxkAywFx1MDAxNCT6XHJcdTAwMDNEt3VcdTAwMWN9WiRvv3ziI/JcdTAwMDWu3Vx1MDAxMj95XFy35+Q/jPmybdy9lifNuzSFpybfXGZrkqV0XHUwMDBlL30339vivJfft72gvCdcdTAwMDRJPJpnl0PRpLkndlxuc4kpjmDJXHUwMDA3gZxcdTAwMGbpxVxcXHUwMDFkQKT6Jlx1MDAxNHhMeWQoXHUwMDE0IIJcIkiYyELFXHUwMDBmgSc0XG5X07yAga3LSuA0NYhzXHJsOaGPwklDc0pzrWRKYOs5arTCkKdcdTAwMTPSMvs61ptGdnJFTEvRLlx1MDAxOaJrQ7ZFNOevRHrLmUb+fDRh32XRXnZhKiexR0JG6lS5d1ec9vK3lVhU2iOJ50mDTLnoi0+ynvWkpKjfVVx1MDAwNYDR88l+UOTsuSo1KdxGMFqkZD+4kJ5GS4pAUYBNwXhC7VlypkqAnUvKf0bam3bH7rnRXHUwMDFl+TBcdTAwMDZGuK3zKNBiyGxKMmHO0s4lYDRcYqI3RY6UkMdVgTH8Ulx1MDAxY5dp0KOjSVsuyHFFXHUwMDFh2LOI8KU8wXokXGZIVZKKXHUwMDE3Jl6TR6TsoZToQjtCfKRCYJylXHUwMDE0XHUwMDFlXHUwMDExsnA1r5KRJk2pp5PocUFcdTAwMGZcdTAwMTYlcyxcdTAwMWaxr1x1MDAxNPopg0Pem1SnXCKZiFu5j11uKur5QXjcUlx1MDAxMKlEolx1MDAwMnX0pDKdndsznlx1MDAxZbCJ1LhlurXiJOeM2UjxKpIrXHUwMDE1e5k26l5J60xcIm9aximVnUn+MK2BM41cXEqNRZixXHUwMDEwO+fviFx1MDAxMlx1MDAxYlx1MDAwM/lcdTAwMDVFl+bcUZRcdTAwMTVQZFxmr+HeXHUwMDFjissvkcunOINcdTAwMWWi20RcdTAwMDZcdTAwMTWXYmLxipO38ZS0yulsqaKb4E9cdTAwMTGESUE6naHSXHUwMDE2LVF8ZKui31x1MDAxY1x1MDAxN+W4h5k5joNBZJJCtVx1MDAxNJJcdTAwMTPZXHUwMDBiXHUwMDE5QOFcdTAwMWH9a0JuLInkpi2vnFx1MDAwZsnlxlBcdOt8XHUwMDBijivML6OMMlx1MDAwM+3KiC1cdTAwMWFmXHUwMDA0Y6XlVfLrruOBNoA0ilxchJScXHQh3oHl8vtfcllOMOlZ15XnOlxuXHUwMDE5g6mVXHUwMDFj88iOwWqyaLfZmUlZXpfa08aSynA/XHUwMDE2XHUwMDAyjIXM8pvloiz3ODvLkZtCY6K+JtJRm7OFXHUwMDA0IclKXHUwMDE1yWOVwnLT907NKYzKtlL3StjnQmm5UZ0hXG5cdTAwMDFOZtBdMC5LXHUwMDEzc/ldlfFBXGLlNrZAeqZaIdp3oLn8rvY8miM3rl3xXHUwMDFmkicjNVx1MDAxN1koeKE57oGLgCy4XyA0SZorkkWUxKTgvICxJFx1MDAxYTmD31x1MDAxMWs6zz3NznOM0Cq5ZVx0SluKdSQkfm9OW22ju1xclURz0+6IMCeay89cdTAwMDMm7fMteK5w/vg5Uas1o2fr7sBqLJSoLUR0+bulTIzCuI3OJFx1MDAwNVx1MDAwYlJcblx1MDAxOWl6eUOmy92xKlfQWSs8i1x1MDAxYdCSXHUwMDE0dY3nMaZzXHUwMDFioCPCaJtcIrdyXHUwMDE4LsOPXHUwMDA1XHUwMDFkXHUwMDE3nnY/6yZccqNcdTAwMDdcdTAwMDBpqcOJZUf4XHUwMDFktqZcdTAwMTJcdTAwMWRns1x1MDAxM1x1MDAxZFx0XHUwMDE2t/pcdTAwMGJpgs5kb1jNNbcqXlteXHUwMDBl002729m8XHUwMDA0XZaVjo4mXHL0LZiucOdcYvMkpyGS4rBcdTAwMWNdk2NpYetcdTAwMTT5YbeDi3BccpbcKVx1MDAxZVx1MDAwMckx0Fx1MDAxYyqQRFwiroTBXHUwMDFhnbTRskivYLd1ZpWdXHUwMDExhvyZiTRJRkDIi6ddlS9WW1fsu908fnowwcNNb+1ybeFbRa2RnmTKXHUwMDE4NEy5X16OMb5S2vW2XHUwMDE4iuCtQTCLvDklXHUwMDE4l0xikTbTRWi2Tu1hjljN6upB/+j65NNqIFvdrfrjxaDfa73BXHUwMDBmJ+R+73x/jopCKDVzb3je7CWkTryB/HnnR6488lx1MDAwMGglasUmzJ6s0ONud1x1MDAwMZdcIlbaRJZ4x5t6WFx1MDAwZpzFS0bOUltIKVx1MDAxNoiEXHUwMDEyv5VNlFRFurKJu82XXHUwMDFmobKIXHUwMDE0cyXyiEt5yUXrVmvijD6rfnmd5YaG+GM7yU7zelBcdGiskZbrqXvGx43fVVx1MDAxYbQ/iPm7eGd4p1mrRVx1MDAxZGK8OfxnLiuxXHUwMDE5ZtroXHUwMDEzbeHR+Ym3kudcdTAwMDA5n8nyY1x1MDAxNlxynnJI1spcImC8XHUwMDE1kDSeJ1x0w0KjsaSHk4szXHUwMDEy03ag467dmk7Rrjkv8lx1MDAxYpi/gVx1MDAxY1x1MDAwNbIsYS2GomWjgaUhXFyozEVn4X4pV0S3f3lviJdcdTAwMWKiRKzSvSbtMTw/4W9Li0jyySFcdTAwMWWRgFOO5DBcclxiZbVJNlx1MDAwNylcdTAwMGY0hX9cdTAwMTR5UVSgUEzXKl5+K7txZYRWoHZcdTAwMDX9LDle4bnKMLJLYlx1MDAxN6mYsFONd6ZcdTAwMWEwv33Vuy+kfvJl2k9I0+SRpvJcdTAwMTjFlqOcXHUwMDFlXHUwMDAznkaaaVx1MDAxMt2jXHUwMDE5Va5ZgknrfuvyN2umsqaamTWVs1xcl1x1MDAxZEsjzchcYicjXeTo1lxyXCL7q/zDSNNET0jY40KxpsvMuf1uiNaJgCj8jmyFO6YhM3/aLLzq41x1MDAwNixH4bfbO8pcdTAwMDDTSdp0e7Mx16Plfj1NXHUwMDFh8960+eHlXHUwMDE5L1X6/aOArHY8XHUwMDExS3dN/341lWXcy0W0o4E5lvFH8Pz7w9//XHUwMDBmUU3fxiJ9@shared@testing@infra@core@modulessubmodules/apps@infra@modulessubmodules/migrations \ No newline at end of file diff --git a/assets/js/ff57262e.4a916d92.js b/assets/js/ff57262e.b83dfab1.js similarity index 74% rename from assets/js/ff57262e.4a916d92.js rename to assets/js/ff57262e.b83dfab1.js index d6c28dc..f43bed1 100644 --- a/assets/js/ff57262e.4a916d92.js +++ b/assets/js/ff57262e.b83dfab1.js @@ -1 +1 @@ -"use strict";(self.webpackChunkdataport_docusaurus=self.webpackChunkdataport_docusaurus||[]).push([[8247],{3905:(e,t,r)=>{r.d(t,{Zo:()=>d,kt:()=>f});var o=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,o)}return r}function a(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var i=o.createContext({}),u=function(e){var t=o.useContext(i),r=t;return e&&(r="function"==typeof e?e(t):a(a({},t),e)),r},d=function(e){var t=u(e.components);return o.createElement(i.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},p=o.forwardRef((function(e,t){var r=e.components,n=e.mdxType,l=e.originalType,i=e.parentName,d=s(e,["components","mdxType","originalType","parentName"]),c=u(r),p=n,f=c["".concat(i,".").concat(p)]||c[p]||m[p]||l;return r?o.createElement(f,a(a({ref:t},d),{},{components:r})):o.createElement(f,a({ref:t},d))}));function f(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var l=r.length,a=new Array(l);a[0]=p;var s={};for(var i in t)hasOwnProperty.call(t,i)&&(s[i]=t[i]);s.originalType=e,s[c]="string"==typeof e?e:n,a[1]=s;for(var u=2;u{r.r(t),r.d(t,{assets:()=>i,contentTitle:()=>a,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>u});var o=r(7462),n=(r(7294),r(3905));const l={},a="Implementation and usage of modules, submodule and barrel files in our project",s={unversionedId:"schulcloud-server/Coding-Guidelines/modules-submodules",id:"schulcloud-server/Coding-Guidelines/modules-submodules",title:"Implementation and usage of modules, submodule and barrel files in our project",description:"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.",source:"@site/docs/schulcloud-server/Coding-Guidelines/modules-submodules.md",sourceDirName:"schulcloud-server/Coding-Guidelines",slug:"/schulcloud-server/Coding-Guidelines/modules-submodules",permalink:"/docs/schulcloud-server/Coding-Guidelines/modules-submodules",draft:!1,editUrl:"https://github.com/hpi-schul-cloud/hpi-schul-cloud.github.io/blob/main/docs/schulcloud-server/Coding-Guidelines/modules-submodules.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Defining Entities",permalink:"/docs/schulcloud-server/Coding-Guidelines/micro-orm"},next:{title:"Repositories",permalink:"/docs/schulcloud-server/Coding-Guidelines/repositories"}},i={},u=[{value:"Modules and Submodules",id:"modules-and-submodules",level:2},{value:"Barrel Files",id:"barrel-files",level:2},{value:"Handling Circular Dependencies",id:"handling-circular-dependencies",level:2}],d={toc:u},c="wrapper";function m(e){let{components:t,...l}=e;return(0,n.kt)(c,(0,o.Z)({},d,l,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"implementation-and-usage-of-modules-submodule-and-barrel-files-in-our-project"},"Implementation and usage of modules, submodule and barrel files in our project"),(0,n.kt)("p",null,"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain."),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"Module Structure",src:r(3374).Z,width:"1467",height:"2429"})),(0,n.kt)("h2",{id:"modules-and-submodules"},"Modules and Submodules"),(0,n.kt)("p",null,"In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the ",(0,n.kt)("inlineCode",{parentName:"p"},"export")," keyword. To import a module, you use the ",(0,n.kt)("inlineCode",{parentName:"p"},"import")," keyword followed by the module name. Here's an example:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"import { ModuleName } from '@modules/module-name';\n")),(0,n.kt)("p",null,"Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts):"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/module-name/index.ts\nexport { SubmoduleServiceName } from './submodule-name/service.ts';\n")),(0,n.kt)("h2",{id:"barrel-files"},"Barrel Files"),(0,n.kt)("p",null,"Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules."),(0,n.kt)("p",null,"If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again."),(0,n.kt)("p",null,"Here's an example of a barrel file:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/module-name/index.ts\nexport { PublicService } from './services/public-service.ts';\nexport { ServiceInterfaceA, InterfaceB } from './interfaces';\nexport { InterfaceC } from './submodule-name/interfaces';\n")),(0,n.kt)("p",null,"!!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like ",(0,n.kt)("inlineCode",{parentName:"p"},"export * from './services'")," in the barrel file."),(0,n.kt)("p",null,"And here's how you can import from the barrel:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/other-module-name/service.ts\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';\n")),(0,n.kt)("h2",{id:"handling-circular-dependencies"},"Handling Circular Dependencies"),(0,n.kt)("p",null,"Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs."),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"Module Structure",src:r(9331).Z,width:"1285",height:"756"})),(0,n.kt)("p",null,"Here are some strategies to handle circular dependencies:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Refactor Your Code"),": The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/moduleC/service.ts\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA';\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB';\n")),(0,n.kt)("ol",{start:2},(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Use Interfaces"),": If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/moduleC/service.ts\nimport { type PublicService } from '@modules/moduleA';\nimport { type PublicService } from '@modules/moduleB';\n")),(0,n.kt)("ol",{start:3},(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Use Events"),": If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it.")),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("p",{parentName:"li"},(0,n.kt)("a",{parentName:"p",href:"https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling"},"https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling"))),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("p",{parentName:"li"},(0,n.kt)("a",{parentName:"p",href:"https://docs.nestjs.com/recipes/cqrs"},"https://docs.nestjs.com/recipes/cqrs")))),(0,n.kt)("p",null,"Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible."))}m.isMDXComponent=!0},3374:(e,t,r)=>{r.d(t,{Z:()=>o});const o=r.p+"assets/images/Modules-SubModules_background-87819f3982b86ea657a437c4ce27a1b4.svg"},9331:(e,t,r)=>{r.d(t,{Z:()=>o});const o=r.p+"assets/images/server_area_dependency_v3-c5729a31749381e23c81242a00541ec7.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunkdataport_docusaurus=self.webpackChunkdataport_docusaurus||[]).push([[8247],{3905:(e,t,r)=>{r.d(t,{Zo:()=>d,kt:()=>f});var o=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,o)}return r}function a(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var i=o.createContext({}),u=function(e){var t=o.useContext(i),r=t;return e&&(r="function"==typeof e?e(t):a(a({},t),e)),r},d=function(e){var t=u(e.components);return o.createElement(i.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},p=o.forwardRef((function(e,t){var r=e.components,n=e.mdxType,l=e.originalType,i=e.parentName,d=s(e,["components","mdxType","originalType","parentName"]),c=u(r),p=n,f=c["".concat(i,".").concat(p)]||c[p]||m[p]||l;return r?o.createElement(f,a(a({ref:t},d),{},{components:r})):o.createElement(f,a({ref:t},d))}));function f(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var l=r.length,a=new Array(l);a[0]=p;var s={};for(var i in t)hasOwnProperty.call(t,i)&&(s[i]=t[i]);s.originalType=e,s[c]="string"==typeof e?e:n,a[1]=s;for(var u=2;u{r.r(t),r.d(t,{assets:()=>i,contentTitle:()=>a,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>u});var o=r(7462),n=(r(7294),r(3905));const l={},a="Implementation and usage of modules, submodule and barrel files in our project",s={unversionedId:"schulcloud-server/Coding-Guidelines/modules-submodules",id:"schulcloud-server/Coding-Guidelines/modules-submodules",title:"Implementation and usage of modules, submodule and barrel files in our project",description:"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.",source:"@site/docs/schulcloud-server/Coding-Guidelines/modules-submodules.md",sourceDirName:"schulcloud-server/Coding-Guidelines",slug:"/schulcloud-server/Coding-Guidelines/modules-submodules",permalink:"/docs/schulcloud-server/Coding-Guidelines/modules-submodules",draft:!1,editUrl:"https://github.com/hpi-schul-cloud/hpi-schul-cloud.github.io/blob/main/docs/schulcloud-server/Coding-Guidelines/modules-submodules.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Defining Entities",permalink:"/docs/schulcloud-server/Coding-Guidelines/micro-orm"},next:{title:"Repositories",permalink:"/docs/schulcloud-server/Coding-Guidelines/repositories"}},i={},u=[{value:"Modules and Submodules",id:"modules-and-submodules",level:2},{value:"Barrel Files",id:"barrel-files",level:2},{value:"Handling Circular Dependencies",id:"handling-circular-dependencies",level:2}],d={toc:u},c="wrapper";function m(e){let{components:t,...l}=e;return(0,n.kt)(c,(0,o.Z)({},d,l,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"implementation-and-usage-of-modules-submodule-and-barrel-files-in-our-project"},"Implementation and usage of modules, submodule and barrel files in our project"),(0,n.kt)("p",null,"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain."),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"Module Structure",src:r(3374).Z,width:"1467",height:"2429"})),(0,n.kt)("h2",{id:"modules-and-submodules"},"Modules and Submodules"),(0,n.kt)("p",null,"In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the ",(0,n.kt)("inlineCode",{parentName:"p"},"export")," keyword. To import a module, you use the ",(0,n.kt)("inlineCode",{parentName:"p"},"import")," keyword followed by the module name. Here's an example:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"import { ModuleName } from '@modules/module-name';\n")),(0,n.kt)("p",null,"Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts):"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/module-name/index.ts\nexport { SubmoduleServiceName } from './submodule-name/service.ts';\n")),(0,n.kt)("h2",{id:"barrel-files"},"Barrel Files"),(0,n.kt)("p",null,"Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules."),(0,n.kt)("p",null,"If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again."),(0,n.kt)("p",null,"Here's an example of a barrel file:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/module-name/index.ts\nexport { PublicService } from './services/public-service.ts';\nexport { ServiceInterfaceA, InterfaceB } from './interfaces';\nexport { InterfaceC } from './submodule-name/interfaces';\n")),(0,n.kt)("p",null,"!!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like ",(0,n.kt)("inlineCode",{parentName:"p"},"export * from './services'")," in the barrel file."),(0,n.kt)("p",null,"And here's how you can import from the barrel:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/other-module-name/service.ts\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';\n")),(0,n.kt)("h2",{id:"handling-circular-dependencies"},"Handling Circular Dependencies"),(0,n.kt)("p",null,"Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs."),(0,n.kt)("p",null,(0,n.kt)("img",{alt:"Module Structure",src:r(9331).Z,width:"2569",height:"1512"})),(0,n.kt)("p",null,"Here are some strategies to handle circular dependencies:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Refactor Your Code"),": The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/moduleC/service.ts\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA';\nimport { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB';\n")),(0,n.kt)("ol",{start:2},(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Use Interfaces"),": If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"// @modules/moduleC/service.ts\nimport { type PublicService } from '@modules/moduleA';\nimport { type PublicService } from '@modules/moduleB';\n")),(0,n.kt)("ol",{start:3},(0,n.kt)("li",{parentName:"ol"},(0,n.kt)("strong",{parentName:"li"},"Use Events"),": If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it.")),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("p",{parentName:"li"},(0,n.kt)("a",{parentName:"p",href:"https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling"},"https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling"))),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("p",{parentName:"li"},(0,n.kt)("a",{parentName:"p",href:"https://docs.nestjs.com/recipes/cqrs"},"https://docs.nestjs.com/recipes/cqrs")))),(0,n.kt)("p",null,"Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible."))}m.isMDXComponent=!0},3374:(e,t,r)=>{r.d(t,{Z:()=>o});const o=r.p+"assets/images/Modules-SubModules_background-87819f3982b86ea657a437c4ce27a1b4.svg"},9331:(e,t,r)=>{r.d(t,{Z:()=>o});const o=r.p+"assets/images/server_area_dependency_v3-d2fa547887f0c01e8bbbadc65351db31.svg"}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.f78588b2.js b/assets/js/runtime~main.c1411b90.js similarity index 99% rename from assets/js/runtime~main.f78588b2.js rename to assets/js/runtime~main.c1411b90.js index d92b961..74a673e 100644 --- a/assets/js/runtime~main.f78588b2.js +++ b/assets/js/runtime~main.c1411b90.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,d,c,f,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var d=t[e]={id:e,loaded:!1,exports:{}};return b[e].call(d.exports,d,d.exports,r),d.loaded=!0,d.exports}r.m=b,r.c=t,e=[],r.O=(a,d,c,f)=>{if(!d){var b=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](d[o])))?d.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[d,c,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},d=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var b={};a=a||[null,d({}),d([]),d(d)];for(var t=2&c&&e;"object"==typeof t&&!~a.indexOf(t);t=d(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(f,b),f},r.d=(e,a)=>{for(var d in a)r.o(a,d)&&!r.o(e,d)&&Object.defineProperty(e,d,{enumerable:!0,get:a[d]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,d)=>(r.f[d](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",604:"e68d14cf",770:"9bc4dd96",1142:"b7891a86",1292:"cc565af5",1594:"46dbb8ab",1670:"09271db2",1779:"f70b05ae",1796:"679fb4ac",1932:"09e11c27",2024:"14307e05",2341:"9a9bfca7",2364:"9e1b0827",2395:"dc9c4dc5",2462:"85a91618",2732:"88fdfd2d",2996:"81589697",3027:"e8e7469b",3085:"1f391b9e",3093:"579213b9",3195:"25d29d9b",3237:"1df93b7f",3589:"dcaf9ebd",3879:"4648a6e8",3941:"aa3d8eef",3958:"dd56d1b0",4038:"76ac0d9b",4211:"5bde3a12",4233:"8e3ce21d",4293:"e4a1fa55",4392:"b6ce3ed6",4615:"287ec6bc",4832:"1e32a951",4968:"8e9e9291",5014:"0579a3a5",5125:"2276cd77",5556:"18a636a5",5773:"4ad69fc2",5896:"7fbd2616",6012:"8b2430e2",6074:"a6e79240",6190:"74c4112b",6207:"d8db4531",6333:"ce3b758c",6584:"d5e448b9",6616:"306c3845",6947:"67d0f22c",7022:"a5f57a66",7414:"393be207",7464:"1c700299",7816:"64027feb",7858:"48751006",7918:"17896441",7920:"1a4e3797",7975:"78e80160",8171:"e849eca6",8247:"ff57262e",8404:"4f93518d",8639:"6af581cf",8844:"234d9ac0",8896:"8fc771c1",8981:"785f7b5a",9514:"1be78505",9539:"7fbcedce",9671:"0e384e19",9739:"95fe1a58",9817:"14eb3368",9826:"d757ab52",9903:"76533127"}[e]||e)+"."+{53:"2ed88293",604:"4dcf5e70",770:"a1510a0d",1142:"a9792265",1292:"24ec029a",1594:"9d6df704",1670:"5af3c195",1779:"b8694906",1796:"c4404ae2",1932:"49f1586c",2024:"c00f4a4f",2341:"1ab88c43",2364:"7249293b",2395:"469d997c",2462:"c2922e7b",2732:"3c63a2d8",2784:"8b1ae48a",2996:"98f0a470",3027:"15b26647",3085:"df0b103d",3093:"abb9c22f",3195:"152e091f",3237:"ea1540bd",3589:"b0778315",3879:"55307307",3941:"f5cb9117",3958:"404f0b0a",4038:"e63764d6",4211:"bbb6e7d2",4233:"d65de873",4293:"ca3f9d35",4392:"fc46af79",4615:"5f1036cb",4832:"e41bf422",4968:"988f1507",4972:"cdbd8342",5014:"209fe466",5125:"5fdd5591",5525:"11b572a0",5556:"798fa689",5773:"55e9d5f3",5896:"8fa9a60d",6012:"633b13aa",6074:"b9af744f",6190:"b7c648c5",6207:"a87b1861",6316:"5470f13c",6333:"592af224",6584:"364c7c7b",6616:"0e4f26d3",6947:"31dcd5d3",7022:"39998d3f",7414:"5251776a",7464:"ae3e3d31",7724:"4dc12d50",7816:"1ddc0b72",7858:"7a9fc9bd",7918:"1560033c",7920:"7f2aabb6",7975:"17d12cc4",8171:"61599aca",8247:"4a916d92",8404:"fbdfe8f9",8443:"b1b63fda",8639:"851bf062",8844:"1156910e",8896:"7b1c7644",8981:"22d20bab",9487:"99f6d0bd",9514:"58c27077",9539:"798def7b",9671:"05580918",9739:"7617c1b3",9817:"6f08294e",9826:"47741084",9903:"d1b788e1"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},f="dataport-docusaurus:",r.l=(e,a,d,b)=>{if(c[e])c[e].push(a);else{var t,o;if(void 0!==d)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=c[e];if(delete c[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(d))),a)return a(d)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={17896441:"7918",48751006:"7858",76533127:"9903",81589697:"2996","935f2afb":"53",e68d14cf:"604","9bc4dd96":"770",b7891a86:"1142",cc565af5:"1292","46dbb8ab":"1594","09271db2":"1670",f70b05ae:"1779","679fb4ac":"1796","09e11c27":"1932","14307e05":"2024","9a9bfca7":"2341","9e1b0827":"2364",dc9c4dc5:"2395","85a91618":"2462","88fdfd2d":"2732",e8e7469b:"3027","1f391b9e":"3085","579213b9":"3093","25d29d9b":"3195","1df93b7f":"3237",dcaf9ebd:"3589","4648a6e8":"3879",aa3d8eef:"3941",dd56d1b0:"3958","76ac0d9b":"4038","5bde3a12":"4211","8e3ce21d":"4233",e4a1fa55:"4293",b6ce3ed6:"4392","287ec6bc":"4615","1e32a951":"4832","8e9e9291":"4968","0579a3a5":"5014","2276cd77":"5125","18a636a5":"5556","4ad69fc2":"5773","7fbd2616":"5896","8b2430e2":"6012",a6e79240:"6074","74c4112b":"6190",d8db4531:"6207",ce3b758c:"6333",d5e448b9:"6584","306c3845":"6616","67d0f22c":"6947",a5f57a66:"7022","393be207":"7414","1c700299":"7464","64027feb":"7816","1a4e3797":"7920","78e80160":"7975",e849eca6:"8171",ff57262e:"8247","4f93518d":"8404","6af581cf":"8639","234d9ac0":"8844","8fc771c1":"8896","785f7b5a":"8981","1be78505":"9514","7fbcedce":"9539","0e384e19":"9671","95fe1a58":"9739","14eb3368":"9817",d757ab52:"9826"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,d)=>{var c=r.o(e,a)?e[a]:void 0;if(0!==c)if(c)d.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((d,f)=>c=e[a]=[d,f]));d.push(c[2]=f);var b=r.p+r.u(a),t=new Error;r.l(b,(d=>{if(r.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var f=d&&("load"===d.type?"missing":d.type),b=d&&d.target&&d.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+b+")",t.name="ChunkLoadError",t.type=f,t.request=b,c[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,d)=>{var c,f,b=d[0],t=d[1],o=d[2],n=0;if(b.some((a=>0!==e[a]))){for(c in t)r.o(t,c)&&(r.m[c]=t[c]);if(o)var i=o(r)}for(a&&a(d);n{"use strict";var e,a,d,c,f,b={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var d=t[e]={id:e,loaded:!1,exports:{}};return b[e].call(d.exports,d,d.exports,r),d.loaded=!0,d.exports}r.m=b,r.c=t,e=[],r.O=(a,d,c,f)=>{if(!d){var b=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](d[o])))?d.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[d,c,f]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},d=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var b={};a=a||[null,d({}),d([]),d(d)];for(var t=2&c&&e;"object"==typeof t&&!~a.indexOf(t);t=d(t))Object.getOwnPropertyNames(t).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,r.d(f,b),f},r.d=(e,a)=>{for(var d in a)r.o(a,d)&&!r.o(e,d)&&Object.defineProperty(e,d,{enumerable:!0,get:a[d]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,d)=>(r.f[d](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",604:"e68d14cf",770:"9bc4dd96",1142:"b7891a86",1292:"cc565af5",1594:"46dbb8ab",1670:"09271db2",1779:"f70b05ae",1796:"679fb4ac",1932:"09e11c27",2024:"14307e05",2341:"9a9bfca7",2364:"9e1b0827",2395:"dc9c4dc5",2462:"85a91618",2732:"88fdfd2d",2996:"81589697",3027:"e8e7469b",3085:"1f391b9e",3093:"579213b9",3195:"25d29d9b",3237:"1df93b7f",3589:"dcaf9ebd",3879:"4648a6e8",3941:"aa3d8eef",3958:"dd56d1b0",4038:"76ac0d9b",4211:"5bde3a12",4233:"8e3ce21d",4293:"e4a1fa55",4392:"b6ce3ed6",4615:"287ec6bc",4832:"1e32a951",4968:"8e9e9291",5014:"0579a3a5",5125:"2276cd77",5556:"18a636a5",5773:"4ad69fc2",5896:"7fbd2616",6012:"8b2430e2",6074:"a6e79240",6190:"74c4112b",6207:"d8db4531",6333:"ce3b758c",6584:"d5e448b9",6616:"306c3845",6947:"67d0f22c",7022:"a5f57a66",7414:"393be207",7464:"1c700299",7816:"64027feb",7858:"48751006",7918:"17896441",7920:"1a4e3797",7975:"78e80160",8171:"e849eca6",8247:"ff57262e",8404:"4f93518d",8639:"6af581cf",8844:"234d9ac0",8896:"8fc771c1",8981:"785f7b5a",9514:"1be78505",9539:"7fbcedce",9671:"0e384e19",9739:"95fe1a58",9817:"14eb3368",9826:"d757ab52",9903:"76533127"}[e]||e)+"."+{53:"2ed88293",604:"4dcf5e70",770:"a1510a0d",1142:"a9792265",1292:"24ec029a",1594:"9d6df704",1670:"5af3c195",1779:"b8694906",1796:"c4404ae2",1932:"49f1586c",2024:"c00f4a4f",2341:"1ab88c43",2364:"7249293b",2395:"469d997c",2462:"c2922e7b",2732:"3c63a2d8",2784:"8b1ae48a",2996:"98f0a470",3027:"15b26647",3085:"df0b103d",3093:"abb9c22f",3195:"152e091f",3237:"ea1540bd",3589:"b0778315",3879:"55307307",3941:"f5cb9117",3958:"404f0b0a",4038:"e63764d6",4211:"bbb6e7d2",4233:"d65de873",4293:"ca3f9d35",4392:"fc46af79",4615:"5f1036cb",4832:"e41bf422",4968:"988f1507",4972:"cdbd8342",5014:"209fe466",5125:"5fdd5591",5525:"11b572a0",5556:"798fa689",5773:"55e9d5f3",5896:"8fa9a60d",6012:"633b13aa",6074:"b9af744f",6190:"b7c648c5",6207:"a87b1861",6316:"5470f13c",6333:"592af224",6584:"364c7c7b",6616:"0e4f26d3",6947:"31dcd5d3",7022:"39998d3f",7414:"5251776a",7464:"ae3e3d31",7724:"4dc12d50",7816:"1ddc0b72",7858:"7a9fc9bd",7918:"1560033c",7920:"7f2aabb6",7975:"17d12cc4",8171:"61599aca",8247:"b83dfab1",8404:"fbdfe8f9",8443:"b1b63fda",8639:"851bf062",8844:"1156910e",8896:"7b1c7644",8981:"22d20bab",9487:"99f6d0bd",9514:"58c27077",9539:"798def7b",9671:"05580918",9739:"7617c1b3",9817:"6f08294e",9826:"47741084",9903:"d1b788e1"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},f="dataport-docusaurus:",r.l=(e,a,d,b)=>{if(c[e])c[e].push(a);else{var t,o;if(void 0!==d)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=c[e];if(delete c[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(d))),a)return a(d)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/",r.gca=function(e){return e={17896441:"7918",48751006:"7858",76533127:"9903",81589697:"2996","935f2afb":"53",e68d14cf:"604","9bc4dd96":"770",b7891a86:"1142",cc565af5:"1292","46dbb8ab":"1594","09271db2":"1670",f70b05ae:"1779","679fb4ac":"1796","09e11c27":"1932","14307e05":"2024","9a9bfca7":"2341","9e1b0827":"2364",dc9c4dc5:"2395","85a91618":"2462","88fdfd2d":"2732",e8e7469b:"3027","1f391b9e":"3085","579213b9":"3093","25d29d9b":"3195","1df93b7f":"3237",dcaf9ebd:"3589","4648a6e8":"3879",aa3d8eef:"3941",dd56d1b0:"3958","76ac0d9b":"4038","5bde3a12":"4211","8e3ce21d":"4233",e4a1fa55:"4293",b6ce3ed6:"4392","287ec6bc":"4615","1e32a951":"4832","8e9e9291":"4968","0579a3a5":"5014","2276cd77":"5125","18a636a5":"5556","4ad69fc2":"5773","7fbd2616":"5896","8b2430e2":"6012",a6e79240:"6074","74c4112b":"6190",d8db4531:"6207",ce3b758c:"6333",d5e448b9:"6584","306c3845":"6616","67d0f22c":"6947",a5f57a66:"7022","393be207":"7414","1c700299":"7464","64027feb":"7816","1a4e3797":"7920","78e80160":"7975",e849eca6:"8171",ff57262e:"8247","4f93518d":"8404","6af581cf":"8639","234d9ac0":"8844","8fc771c1":"8896","785f7b5a":"8981","1be78505":"9514","7fbcedce":"9539","0e384e19":"9671","95fe1a58":"9739","14eb3368":"9817",d757ab52:"9826"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,d)=>{var c=r.o(e,a)?e[a]:void 0;if(0!==c)if(c)d.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var f=new Promise(((d,f)=>c=e[a]=[d,f]));d.push(c[2]=f);var b=r.p+r.u(a),t=new Error;r.l(b,(d=>{if(r.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var f=d&&("load"===d.type?"missing":d.type),b=d&&d.target&&d.target.src;t.message="Loading chunk "+a+" failed.\n("+f+": "+b+")",t.name="ChunkLoadError",t.type=f,t.request=b,c[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,d)=>{var c,f,b=d[0],t=d[1],o=d[2],n=0;if(b.some((a=>0!==e[a]))){for(c in t)r.o(t,c)&&(r.m[c]=t[c]);if(o)var i=o(r)}for(a&&a(d);n Congratulations! | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/How to update the docs/create-a-blog-post.html b/docs/How to update the docs/create-a-blog-post.html index 014e6db..c002c4e 100644 --- a/docs/How to update the docs/create-a-blog-post.html +++ b/docs/How to update the docs/create-a-blog-post.html @@ -4,13 +4,13 @@ Create a Blog Post | Schulcloud-Verbund-Software Documentation - +

Create a Blog Post

Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...

Create your first Post

Create a file at blog/2021-02-28-greetings.md:

blog/2021-02-28-greetings.md
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---

Congratulations, you have made your first post!

Feel free to play around and edit this post as much you like.

A new blog post is now available at http://localhost:3000/blog/greetings.

- + \ No newline at end of file diff --git a/docs/How to update the docs/create-a-document.html b/docs/How to update the docs/create-a-document.html index dd9e66a..5fde69d 100644 --- a/docs/How to update the docs/create-a-document.html +++ b/docs/How to update the docs/create-a-document.html @@ -4,13 +4,13 @@ Create a Document | Schulcloud-Verbund-Software Documentation - +

Create a Document

Documents are groups of pages connected through:

  • a sidebar
  • previous/next navigation
  • versioning

Create your first Doc

Create a Markdown file at docs/hello.md:

docs/hello.md
# Hello

This is my **first Docusaurus document**!

A new document is now available at http://localhost:3000/docs/hello.

Configure the Sidebar

Docusaurus automatically creates a sidebar from the docs folder.

Add metadata to customize the sidebar label and position:

docs/hello.md
---
sidebar_label: 'Hi!'
sidebar_position: 3
---

# Hello

This is my **first Docusaurus document**!

It is also possible to create your sidebar explicitly in sidebars.js:

sidebars.js
module.exports = {
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
};
- + \ No newline at end of file diff --git a/docs/How to update the docs/create-a-page.html b/docs/How to update the docs/create-a-page.html index 8b77a1b..b70c4b2 100644 --- a/docs/How to update the docs/create-a-page.html +++ b/docs/How to update the docs/create-a-page.html @@ -4,13 +4,13 @@ Create a Page | Schulcloud-Verbund-Software Documentation - +

Create a Page

Add Markdown or React files to src/pages to create a standalone page:

  • src/pages/index.jslocalhost:3000/
  • src/pages/foo.mdlocalhost:3000/foo
  • src/pages/foo/bar.jslocalhost:3000/foo/bar

Create your first React Page

Create a file at src/pages/my-react-page.js:

src/pages/my-react-page.js
import React from 'react';
import Layout from '@theme/Layout';

export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}

A new page is now available at http://localhost:3000/my-react-page.

Create your first Markdown Page

Create a file at src/pages/my-markdown-page.md:

src/pages/my-markdown-page.md
# My Markdown page

This is a Markdown page

A new page is now available at http://localhost:3000/my-markdown-page.

- + \ No newline at end of file diff --git a/docs/How to update the docs/deploy-your-site.html b/docs/How to update the docs/deploy-your-site.html index 8836927..5605dd3 100644 --- a/docs/How to update the docs/deploy-your-site.html +++ b/docs/How to update the docs/deploy-your-site.html @@ -4,13 +4,13 @@ Deploy your site | Schulcloud-Verbund-Software Documentation - +

Deploy your site

Docusaurus is a static-site-generator (also called Jamstack).

It builds your site as simple static HTML, JavaScript and CSS files.

Build your site

Build your site for production:

npm run build

The static files are generated in the build folder.

Deploy your site

Test your production build locally:

npm run serve

The build folder is now served at http://localhost:3000/.

You can now deploy the build folder almost anywhere easily, for free or very small cost (read the Deployment Guide).

- + \ No newline at end of file diff --git a/docs/How to update the docs/markdown-features.html b/docs/How to update the docs/markdown-features.html index 9671f17..4f72908 100644 --- a/docs/How to update the docs/markdown-features.html +++ b/docs/How to update the docs/markdown-features.html @@ -4,13 +4,13 @@ Markdown Features | Schulcloud-Verbund-Software Documentation - +

Markdown Features

Docusaurus supports Markdown and a few additional features.

Front Matter

Markdown documents have metadata at the top called Front Matter:

my-doc.md
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---

## Markdown heading

Markdown text with [links](./hello.md)

Regular Markdown links are supported, using url paths or relative file paths.

Let's see how to [Create a page](/create-a-page).
Let's see how to [Create a page](./create-a-page.md).

Result: Let's see how to Create a page.

Images

Regular Markdown images are supported.

You can use absolute paths to reference images in the static directory (static/img/docusaurus.png):

![Docusaurus logo](/img/docusaurus.png)

Docusaurus logo

You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:

![Docusaurus logo](./img/docusaurus.png)

Code Blocks

Markdown code blocks are supported with Syntax highlighting.

```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
src/components/HelloDocusaurus.js
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}

Admonitions

Docusaurus has a special syntax to create admonitions and callouts:

:::tip My tip

Use this awesome feature option

:::

:::danger Take care

This action is dangerous

:::
My tip

Use this awesome feature option

Take care

This action is dangerous

MDX and React Components

MDX can make your documentation more interactive and allows using any React components inside Markdown:

export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);

This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !

This is <Highlight color="#1877F2">Facebook blue</Highlight> !

This is Docusaurus green !

This is Facebook blue !

- + \ No newline at end of file diff --git a/docs/Informations/detect-dependency-cycles.html b/docs/Informations/detect-dependency-cycles.html index c077e25..f8ae870 100644 --- a/docs/Informations/detect-dependency-cycles.html +++ b/docs/Informations/detect-dependency-cycles.html @@ -4,7 +4,7 @@ Detect Dependency Cycles | Schulcloud-Verbund-Software Documentation - + @@ -12,7 +12,7 @@

Detect Dependency Cycles

You can use following package and command to detect dependency cycles in code.

https://www.npmjs.com/package/madge

text export

  • npx madge --extensions js,ts --circular .

image export (Ubuntu/Wsl)

  • apt-get install graphviz
  • npx madge --extensions js,ts --circular --image graph.svg .

examples

as graphic

npx madge --image graph_server.svg dist/apps/server/apps/server.app.js npx madge --circular --image graph_server_circular.svg dist/apps/server/apps/server.app.js

npx madge --exclude '^(?!.entity).$' --image graph_server_entities.svg dist/apps/server/apps/server.app.js npx madge --circular --exclude '^(?!.entity).$' --image graph_server_circular_entities.svg dist/apps/server/apps/server.app.js

as text

npx madge --json dist/apps/server/apps/server.app.js >> output.json

more solutions

https://github.com/jmcdo29/nestjs-spelunker

https://sanyamaggarwal.medium.com/automate-circular-dependency-detection-in-your-node-js-project-394ed08f64bf

- + \ No newline at end of file diff --git a/docs/category/e2e-system-tests.html b/docs/category/e2e-system-tests.html index 22659ae..ebd101f 100644 --- a/docs/category/e2e-system-tests.html +++ b/docs/category/e2e-system-tests.html @@ -4,13 +4,13 @@ e2e-system-tests | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/category/how-to-update-the-docs.html b/docs/category/how-to-update-the-docs.html index 35062bd..07d80f4 100644 --- a/docs/category/how-to-update-the-docs.html +++ b/docs/category/how-to-update-the-docs.html @@ -4,13 +4,13 @@ How to update the docs | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/category/nuxt-client.html b/docs/category/nuxt-client.html index a6c2908..6843d38 100644 --- a/docs/category/nuxt-client.html +++ b/docs/category/nuxt-client.html @@ -4,13 +4,13 @@ nuxt-client | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/category/schulcloud-client.html b/docs/category/schulcloud-client.html index 16e5723..b27803e 100644 --- a/docs/category/schulcloud-client.html +++ b/docs/category/schulcloud-client.html @@ -4,13 +4,13 @@ schulcloud-client | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/category/schulcloud-server.html b/docs/category/schulcloud-server.html index 6013a92..00e3345 100644 --- a/docs/category/schulcloud-server.html +++ b/docs/category/schulcloud-server.html @@ -4,13 +4,13 @@ schulcloud-server | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/e2e-system-tests/CodeConventions.html b/docs/e2e-system-tests/CodeConventions.html index 90e98f7..d8bec8d 100644 --- a/docs/e2e-system-tests/CodeConventions.html +++ b/docs/e2e-system-tests/CodeConventions.html @@ -4,13 +4,13 @@ Code Conventions | Schulcloud-Verbund-Software Documentation - +

Code Conventions

This guide provides coding conventions and best practices for writing feature files, naming folders, files, methods, and step definitions. Following these conventions will ensure consistency and maintainability across the test framework.


1. Writing Feature Files

Template Structure

Use the following template when creating a feature file:

Feature: <Description of the user story or functionality>

<As a --user--, I want to ---perform an action--- so that ---I can achieve a certain goal--->

Scenario: <Short description of the scenario>

Given <precondition>
When <action or interaction with the application>
Then <expected outcome>

Guidelines

  • "When" statements describe actions or interactions with the application.

  • "Then" statements describe the expected results of those actions.

  • Multiple "When" statements can precede a single "Then" statement:

    When <action 1>
    When <action 2>
    Then <result>
  • It is acceptable to have multiple "Then" statements:

    When <action>
    Then <result 1>
    Then <result 2>
  • Avoid using "And" statements. Use only "When" and "Then" for clarity.


2. Using Parameters in Feature Files

Leveraging Example Tables with Scenario Outline

When you need to run the same scenario with different sets of test data, use Scenario Outline along with Example Tables. This approach makes your test cases more efficient and maintainable.

Scenario Outline allows you to define steps with placeholders (<parameter>) that are substituted with values from the Examples table.

  • Example Usage

    Feature: Create and delete a class

    As a teacher, I want to create and delete classes
    so that I can manage my courses effectively.

    Scenario Outline: Teacher creates and deletes a class
    Given a user with the role "<role>"
    When the user creates a class named "<className>"
    Then the class "<className>" should be visible in the list
    When the user deletes the class "<className>"
    Then the class "<className>" should no longer exist

    Examples:
    | role | className |
    | teacher | Math101 |
    | teacher | Science202 |
    | admin | History300 |

Explanation

  • The Scenario Outline defines the test steps using parameters like <role> and <className>.
  • The Examples table provides multiple sets of data for each parameter.
  • Each row in theExamples table will generate a separate test case with the specified values.

Benefits of Using Scenario Outlines

  • Reusability: Allows you to reuse the same test steps for multiple sets of test data.
  • Maintainability: Reduces code duplication and makes it easier to update test data.
  • Clarity: Keeps the .feature file organized and easy to read.

3. Using Parameters for Special Test Data

In addition to Scenario Outlines, you can directly use parameters within feature files to specify dynamic values, such as task titles, or other context-specific data.

Example:

Feature: User login

Scenario: Valid user login
Given a user with the username "testUser" and password "Password123"
When the user logs in
Then the user should be redirected to the dashboard

In this case, parameters are directly embedded within the scenario steps without an Examples table.


4. Writing Step Definitions for Scenario Outlines

Step Definitions Example

Ensure that your step definitions are designed to handle the dynamic parameters from your feature files:

Given('a user with the role {string}', role => {
cy.loginAsRole(role)
})

When('the user creates a class named {string}', className => {
cy.createClass(className)
})

Then('the class {string} should be visible in the list', className => {
cy.verifyClassExists(className)
})

Then('the class {string} should no longer exist', className => {
cy.deleteClass(className)
cy.verifyClassDeleted(className)
})

Explanation

  • The {string} syntax captures the parameter from the feature file.
  • Each step is linked to the corresponding scenario, making your tests highly modular and reusable.

5. Naming Conventions

Folder Names

  • Use snake_case for longer folder names.
    • Example: common_logins

Feature File Names

  • Syntax: verb + noun
    • Example: createCourse.feature

Page Object File Names

  • Syntax: page + noun
    • Example: pageCourse.js, pageCommonCourse.js

Class Names Inside Page Objects

  • Syntax: FirstWord_SecondWord
    • Example: Course, Course_Common

Method Names Inside Classes

  • Syntax: verb + noun (CamelCase)
    • Example: fillCourseCreationForm()

Variable Names

  • Syntax: verb + noun or noun + verb (CamelCase)
    • Example: userEmail, loginButton

Data-testid Naming Convention

Test IDs are used to select elements on the web page, including buttons, input fields, and sections.

  • Syntax: firstword-secondword-thirdword

    • Example: content-card-task-menu-edit-icon
  • Usage: Assign test IDs to elements using:

    <button data-testid="add-course-button">Add Course</button>

6. Writing Classes and Methods

Guidelines

  • Assign test data IDs to a static variable with a # prefix so it indicate as a private variable within the class.
  • Break down complex interactions into smaller methods within the class for better modularity.
  • Refer to examples in the current end to end repository for guidance.

7. Writing Step Definitions

Step Definition Folder Structure

  • Create a step definition file within cypress/support/step_definitions/ based on the module name (e.g., rooms, courses, teams).
  • The naming convention for step definition files is based on the module name, followed by .spec.js.
  • Example:
    • editCourseSteps.spec.js
    • commonCourseSteps.spec.js (for shared steps across multiple scenarios)

Guidelines

  • Follow the same sequence as the feature file for consistency.
  • Create one common step definition file that can be reused across tests within the same module or across modules.
  • For module-specific step definitions, comment out any common steps and include a reference to their location.

Example 1: Dedicated Module Step Definitions

// editCourseSteps.spec.js
Given('a user is logged in', () => { ... });

When('the user edits the course details', () => { ... });

Then('the course should be updated', () => { ... });

Example 2: Referencing Common Step Definitions

// commonCourseSteps.spec.js
Given('a user is logged in', () => { ... });

// editCourseSteps.js
// Commented out common steps with reference
// Refer to: commonCourseSteps.spec.js
When('the user edits the course details', () => { ... });

Then('the course should be updated', () => { ... });

8. Additional Best Practices

  • Use consistent folder and file names as per the naming conventions above.
  • Keep the user journey sequence the same in both .feature files and step definition files to enhance readability.
- + \ No newline at end of file diff --git a/docs/e2e-system-tests/GettingStarted.html b/docs/e2e-system-tests/GettingStarted.html index d496146..b3ca2fa 100644 --- a/docs/e2e-system-tests/GettingStarted.html +++ b/docs/e2e-system-tests/GettingStarted.html @@ -4,13 +4,13 @@ Getting Started | Schulcloud-Verbund-Software Documentation - +

Getting Started

This section provides instructions for setting up the Cypress-Cucumber test environment to ensure a smooth onboarding process.


1. Pre-requisites

Before getting started, ensure the following tools are installed:

  • Node.js: Download Node v18
  • Git: Download Git
  • Browser: (Recommended: Microsoft Edge) Download Edge Browser
  • IDE: Choose any IDE (Recommended: VS Code)
  • Optional Tools: GitHub Desktop App
  • Recommended VS Code Extensions:
    • Cucumber (Gherkin) Full Support
    • EditorConfig
    • Prettier

2. Cloning the Repository

  • To get the project files locally, follow these steps:

    git clone <repository-url>
    cd <repository-folder>

Make sure you have access to the repository using your organization's credentials.


3. Setting Up Environment Variables for the Testing User Credentials and URLs

  1. Setting Up Environment Variables for Dev Environment/Cluster:

    • Duplicate the file devTemplate.env.json and rename the duplicated file to local.env.json inside the env_variables folder.
    • Include the required development namespace URLs for BRB/DBC/NBC.
    • Test user data on development clusters are created using the school API.
    • To retrieve the API keys for all three namespaces, navigate to 1Password (1PW).
    • Contact QA team for the necessary 1Password links.
  2. Setting Up Environment Variables for Staging Environment/Cluster:

    • Duplicate the file stagingTemplate.env.json and rename the duplicated file to staging.env.json in the env_variables folder.
    • Include the required staging namespace URLs for BRB/DBC/NBC.
    • Test data on the staging environment are fetched from the seed data on the server.
    • Add the environment-specific credentials to staging.env.json from 1Password (1PW).
    • Ensure all instances are included, as 1Password contains different vaults for each namespace with testing credentials.
    • Contact QA team for the necessary 1Password links.

4. Installing Dependencies

  • Use the following command to install all necessary project dependencies:

    npm ci

5. Running Cypress Tests

Once the setup is complete, you can run the tests:

  • To run all tests in headless mode:

    npm run cy:headless:stable:local
  • To run tests interactively in the Cypress UI:

    npm run cy:gui:stable:regression:staging:local

For more details on additional configurations and test options, refer to the Executing Tests Guide section in README.

- + \ No newline at end of file diff --git a/docs/e2e-system-tests/ProjectStructure.html b/docs/e2e-system-tests/ProjectStructure.html index 8c93407..c56904a 100644 --- a/docs/e2e-system-tests/ProjectStructure.html +++ b/docs/e2e-system-tests/ProjectStructure.html @@ -4,13 +4,13 @@ Project Structure | Schulcloud-Verbund-Software Documentation - +

Project Structure

Understanding the project directory layout will help you navigate and manage the Cypress-Cucumber E2E test framework effectively. This section provides a detailed breakdown of the folder structure and the purpose of each component.


Project Directory Layout

(root)
|
|---- .github/
| |____ automatic-trigger.yml # GitHub Actions workflow for automatic triggers
| |____ manual-trigger.yml # GitHub Actions workflow for manual runs
| |____ scheduled-trigger.yml # GitHub Actions workflow for scheduled runs
| |____ main.yml # GitHub Actions workflow for reusable jobs
|
|---- .vscode/ # Settings for recommended VS Code extensions
|
|---- env_variables/
| |____ template.env.json # Template for credentials & environment variables (rename as `local.env.json`)
|
|---- cypress/
| |___ downloads/ # Downloaded files during tests
| |___ fixtures/ # Test data files
| |___ e2e/ # Gherkin feature files
| |___ screenshots/ # Screenshots taken on test failures
| |___ support/
| |___ custom_commands/ # Custom Cypress commands used in tests
| |___ pages/ # Page Object methods for better test modularity
| |___ step_definitions/ # Step definitions for feature files
| |___ commands.js # Custom Cypress commands configuration
| |___ e2e.js # Global hooks and configurations
| |___ videos/ # Recorded test run videos
|
|---- docs/
| |___ branch_activation.md
| |___ folder_structure.md
| |___ executing_tests.md
| |___ setup.md
| |___ tags.md
|
|---- env_variables/
| |___ devTemplate.env.json
| |___ stagingTemplate.env.json
|
|---- reports/ # HTML reports and related assets
|
|---- logs/ # Logs generated during test runs
|
|---- node_modules/ # Project dependencies
|
|---- scripts/
| |____ aggregate-json-files.sh # Script to aggregate JSON files in CI
| |____ runSchoolApi.js # Script to interact with the School API
|
|---- .editorconfig # Editor configuration for consistent formatting
|---- .gitattributes # Git attributes for line endings and diff
|---- .prettierignore # Files and folders ignored by Prettier
|---- .prettierrc # Prettier configuration for code formatting
|---- .gitignore # Git ignore rules
|---- reporter.js # Custom reporter for generating HTML reports
|---- cypress.config.json # Cypress configuration settings
|---- LICENSE # License file
|---- package-lock.json # npm package lock file
|---- package.json # Project dependencies and scripts
|---- README.md # Project documentation and setup guide

Explanation of Key Directories and Files

  • .github/: Contains CI/CD workflows for automated, manual, and scheduled test executions.
  • .vscode/: Recommended settings for VS Code extensions to maintain consistent coding standards.
  • env_variables/: Holds environment configuration files. Duplicate template.env.json and rename it to local.env.json for local testing.
  • cypress/: The main directory for Cypress tests.
  • fixtures/: Stores reusable test data.
  • e2e/: Contains all Gherkin .feature files.
  • support/: Includes custom commands, page objects, and step definitions.
  • videos/ & screenshots/: Captures test artifacts.
  • docs/: Additional documentation for tags, configurations, and best practices.
  • reports/: Contains HTML reports generated after test runs.
  • scripts/: Helpful scripts for CI/CD and API interactions.
  • .prettierrc & .editorconfig: Configuration files to enforce consistent coding styles.
  • cypress.config.json: Central configuration file for Cypress test settings.
  • reporter.js: Custom script to generate detailed HTML reports.
- + \ No newline at end of file diff --git a/docs/e2e-system-tests/Tags.html b/docs/e2e-system-tests/Tags.html index 10537dc..1b03c76 100644 --- a/docs/e2e-system-tests/Tags.html +++ b/docs/e2e-system-tests/Tags.html @@ -4,13 +4,13 @@ Tags | Schulcloud-Verbund-Software Documentation - +

Tags

This section explains the tagging system used for Cypress and Cucumber tests. Tags help categorize and selectively run tests based on environments, test stability, or purpose.


Tag Descriptions

  • @stable_test: Tests that are stable and expected to pass in all environments.
  • @regression_test: Tests run before a release to ensure core functionality.
  • @school_api_test: Tests interacting with the school API.
  • @staging_test: Tests specific to the staging environment.
  • @pr: Tests run during the CI process for Pull Requests.
  • @unstable_test: Tests that may fail intermittently due to environmental factors.
  • @group-A / @group-B: Tags for grouping tests in parallel execution.
  • @schedule_run: Tests tagged for scheduled runs in CI.

Tag Hierarchy

  • Feature-level tags apply to all scenarios within that feature, unless overridden at the scenario level.

Examples:

  • @regression_test & @stable_test

    @regression_test
    Feature: Account Management

    @stable_test
    Scenario: Edit email as an internal user
    Given I am logged in as an internal user
    When I navigate to the account settings
    Then I should see that my email is editable
  • @unstable_test

    @unstable_test
    Feature: Add-ons Management

    Scenario Outline: Access Add-ons page
    Given I am logged in as '<user_role>'
    When I navigate to the Add-ons page
    Then I should see the Add-ons interface

    Examples:
    | user_role |
    | admin |
    | teacher |
  • @school_api_test & @staging_test

    @regression_test
    @stable_test
    Feature: User Management

    Scenario: Admin manages users
    Given I am logged in as an admin
    When I add a new user
    Then the user should appear in the list

    @school_api_test
    Examples: School API
    | user_role | user_email |
    | admin | admin@school.com |

    @staging_test
    Examples: Staging
    | user_role | user_email |
    | teacher | teacher@staging.com |
  • @pr

    @pr
    Feature: Critical Paths

    Scenario: Verify homepage loads
    Given the application is deployed
    When I navigate to the homepage
    Then the homepage should load correctly

For detailed information on the full usage of tags, please refer to the full documentation in README.

- + \ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html index e75251d..6836c49 100644 --- a/docs/intro.html +++ b/docs/intro.html @@ -4,13 +4,13 @@ Schulcloud Documentation | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/docs/nuxt-client/Accessibility.html b/docs/nuxt-client/Accessibility.html index a657e58..c425270 100644 --- a/docs/nuxt-client/Accessibility.html +++ b/docs/nuxt-client/Accessibility.html @@ -4,7 +4,7 @@ Accessibility (A11y) | Schulcloud-Verbund-Software Documentation - + @@ -12,7 +12,7 @@

Accessibility (A11y)

We want to make sure that our product can be used by anyone. This includes people with disabilities as well as people that have a slow connection, outdated or broken hardware or people that just have an unfavorable environment.

W3C Web Accessibility Initiative (WAI)

The WAI develops strategies, standards, resources to make the web accessibile.

An introduction to Accessibility can be found here: Introduction to Web Accessibilty

The WAI ARIA is the Acessible Rich Internet Applications suite of web standards. It makes Web content and Web applications more accessible with adding attributes to identify features for user interaction and enable e.g. keyboard users to move among regions.

A recommended approach using WAI-ARIA roles, states and properties can be found here: ARIA Authoring Practices Guide (APG)

Vuetify and Vue

We want to use Vuetfiy Components in our project. They provide key interaction for all mouse-based-actions and utilize HTML5 semantic elements where applicable.

See: Vuetify A11y

There is also a Accessibility Chapter in the Best Practices of the Vue Docs: Vue A11y

// TODO: link to good tutorial on how to test a11n // REMINDER: If we establish special a11n-components and/or tools - they should be described here

- + \ No newline at end of file diff --git a/docs/nuxt-client/CodeConventions.html b/docs/nuxt-client/CodeConventions.html index 88cdc07..55ca219 100644 --- a/docs/nuxt-client/CodeConventions.html +++ b/docs/nuxt-client/CodeConventions.html @@ -4,14 +4,14 @@ Code Conventions | Schulcloud-Verbund-Software Documentation - +

Code Conventions

data-testids

Please use <div ... data-testid="some-example" ...> in your HTML-code if you want to define a data-testid.

  • do not use uppercase-characters
  • only use one dash - right after data

We also recommend to use refs instead of data-testids. But if you do that, you need to be careful when removing them... as they could be used in the component-code AND in tests:

Also look here: Frontend Arc Group: Meeting Notes 2022-11-04

ts-ignore-comments

Everybody should try to avoid // @ts-ignore and try his/her best to define the types of variables in TypeScript files.

Also look here: Frontend Arc Group: Meeting Notes 2022-10-28

composables

Composables are a great way to make our code more reusable and to extract code from components. If you want to write a composable, consider using one of these well documented and well tested ones: VueUse - Collection of Vue Composition Utilities

If you write a composable:

  • it should have the extension .composable.ts
  • should be placed in your feature folder (see section "directory structure" above), if it is only used inside of your feature
  • should be placed in the global folder / src / composables, if it is used in multiple features
- + \ No newline at end of file diff --git a/docs/nuxt-client/Colors.html b/docs/nuxt-client/Colors.html index 556dfdb..3089a9a 100644 --- a/docs/nuxt-client/Colors.html +++ b/docs/nuxt-client/Colors.html @@ -4,7 +4,7 @@ Colors | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ Backgrounds have the bg-prefix and texts the text-prefix.

Examples

Using a color from Vuetify's color palette:

<div class="bg-blue">
Blue background
</div>

Using a color defined in our vuetify options as text color:

<p class="text-red">
Text has a red color
</p>

To use a variant of a color, you have to add the name of the variant seperated by hyphens:

<p class="text-red-darken-1">
Text has a darken variant of the red color
</p>

Use Colors in (S)CSS

For colors defined in our Vuetify options, Vuetify generates CSS variables. Now, custom properties are an rgb list, so we need to use rgba() to access them.

Examples

.alert {
background-color: rgba(var(--v-theme-primary-lighten-1));
color: rgba(var(--v-theme-primary));
}

In Vuetify 2, we could only use hex values without the alpha property. With Vuetify 3, it's now possible:

.example{
background-color: rgba(var(--v-theme-primary), 0.12);
}

Colors from Vuetify's colors palette (as of now) do not get generated as CSS variables. You will need to access them with map-get.

.alert {
background-color: map-get($grey, base);
color: map-get($blue, lighten-3);
}

"On-Surface" and "On-Background" Colors

"On" colors are important for making text, icons, and other elements recognizable and readable on various backgrounds.

  • on-surface: Used for text, icons, and other elements that appear on top of a surface. Surfaces can include components like cards, dialogs, and menus.

  • on-background: Used for text, icons, and other elements that appear on the primary background of an application or a component

We override the standard on-surface and on-background vuetify colors in our vuetify options and define them for each theme.

Definition Of Custom Colors

You can define more custom colors in our vuetify options like this:

...
colors: {
info: "#0a7ac9",
"icon-btn": colors.grey.darken3,
"on-surface": "#0f3551",
}
...

Rules

  • Talk to UX before introducing a new color
  • Do not overwrite vuetify colors
  • Use a semantic name to represent the use case
  • Prefer usage via map-get over new color definition, unless you introduce a new color
  • Either define style in template or in SCSS
- + \ No newline at end of file diff --git a/docs/nuxt-client/ComponentGuidelines.html b/docs/nuxt-client/ComponentGuidelines.html index 375089c..1c0f1d2 100644 --- a/docs/nuxt-client/ComponentGuidelines.html +++ b/docs/nuxt-client/ComponentGuidelines.html @@ -4,7 +4,7 @@ Component Development Guidelines | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ The first iteration might look like this when using it:

<my-button></my-button>

The next step might be adding a way to set the button label.

<my-button :label="'MyButton'"></my-button>

Careful! The label-prop is just a string. This will limit your Button to only being able to have text-based Labels in the future. It is a lot less flexible because the power of HTML was removed completely.

Compare it to this button:

<my-button>MyButton</my-button>

The label stays within the realm of HTML and we don't lose any HTML capabilities:

<my-button>Two Line <br> Button!</my-button>

<my-button>Button with <my-icon :icon="mdiCheck" /> in the label!</my-button>

Both of these examples are (almost) impossible with a prop-based label.

Rule: Readable text should be HTML and not a string-prop.

Composition over Configuration

Using slots for highly flexible ui components

Let's build a simple vertical-menu with two clickable options and add more and more requirements as we go.

note

Requirements

  • Menu with two clickable options
<ul>
<li>
<my-button>Option 1</my-button>
</li>
<li>
<my-button>Option 2</my-button>
</li>
</ul>
note

Updated Requirements

  • Menu with two clickable options
  • Menu with any number of clickable options

To make our menu reusable, one approach might be to add an options prop:

<!-- MyMenu.component.vue-->
<template>
<ul>
<li v-for="let option in options">
<my-button @click="option.action">{{option.label}}</my-button>
</li>
</ul>
</template>

<!-- usage -->

<my-menu
:options="[
{ label: "Option 1", action: callback1 },
{ label: "Option 2", action: callback2 },
]">
</my-menu>

This approach has two major problems:

First you probably already notice that we demoted the label from HTML to being just a string again. (See: HTML is not a string)

Second we abstracted the structure of our menu into an Array. This replaces perfectly good HTML with a datastructure.

Take a look at this HTML-based approach:

<my-menu>
<my-menu-option @click="callback1">Option 1</my-menu-option>
<my-menu-option @click="callback2">Option 2</my-menu-option>
</my-menu>

The original HTML-structure is preserved and only the default elements (ul, li, button) are abstracted in their own components. This leaves a lot of flexibility to interact with the structure (e.g. toggling options with v-if) while still making sure that the rendered output is valid.

Additionally, this is much easier to test since we do not have to deal with datastructures.

note

Updated Requirements

  • Menu with any number of clickable options
  • Menu-Options can be colored
  • Any number of Menu-Dividers can be placed at any position in the menu

Adding these new requirements in the HTML approach is very straightforward. We just have to add a prop to each my-menu-option to pick a color. Then we create a new my-menu-divider component.

Expanding the datastructure to support colors is easy, we just have to add a color-property. But the divider will be an actual problem. So far the datastructure was created to represent buttons. By adding the divider config object we will lose any uniformity of our config data. This will make it difficult to read, complicated to test und generally annoying to maintain.

Compare the two solutions in code:

<!-- HTML approach -->
<my-menu>
<my-menu-option @click="callback1">Option 1</my-menu-option>
<my-menu-divider />
<my-menu-option :color="'red'" @click="callback2">
Option 2
</my-menu-option>
</my-menu>

<!-- Datastructure approach -->
<my-menu
:options="[
{ label: "Option 1", action: callback1, color: 'default', type:'button' },
{ type: 'divider' },
{ label: "Option 2", action: callback2, color: 'red', type:'button' },
]">
</my-menu>

We can already see the datastructure approach falling apart. For complex menus this will be completely ineligible and difficult to understand.

Let's add more requirements to get closer to a real world menu.

note

New Requirements

  • Menu with any number of clickable options
  • Menu-Options can be colored
  • Any number of Menu-Dividers can be placed at any position in the menu
  • Menu-Options should have a disabled state
  • Menu-Options can be a button or link
  • Menu-Options can be nested dropdowns
<!-- HTML approach -->
<my-menu>
<my-menu-option @click="callback1" :disabled="true">Option 1</my-menu-option>
<my-menu-divider />
<my-menu-option :color="'red'" @click="callback2">Option 2</my-menu-option>
<my-menu-link :href="'wikipedia.com'">Link 1</my-menu-option>
<my-menu-nested-option>
<template #default> <!--Default slot for button label-->
Nested Option
</template>
<template #options> <!--Named slot for options in the inner menu-->
<my-menu-option @click="callback3">Option 3</my-menu-option>
<my-menu-divider />
<my-menu-option @click="callback4">Option 4</my-menu-option>
</template>
</my-menu-nested-option>
</my-menu>

<!--Datastructure approach-->

<good-luck>😅</good-luck>

Rule: Use Slots and small subcomponents to create robust and flexible features.

Rule: Do not use datastructures to represent HTML.

Destructure data over multiple components

We often have to deal with complex data that we want to show to the user.

Take a look at this simplified example:

const users: User[] = [
{
id: 1,
name: 'User 1',
email: 'user1@example.com'
}
{
id: 2,
name: 'User 2',
email: 'user2@example.com'
}
]
note

Requirements

  • Display the User Array in a table
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr v-for="let user in users">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
</tbody>
</table>

This template will quickly get large and hard to understand if we were to add styling, more fields of our user object or even interactions like editing or deleting entries.

How can we easily split up the template?

Destructuring Data

A rule of thumb can be to not handle more than one level of your data structure in a single component. The User object consists of three levels:

  1. Array
  2. Object
  3. Property

We can use this list to create subcomponents for the table:

  1. UserTable

    the host component where all components come together

    This will also be the outside Api of our implementation

  2. UserTableBody

    Component responsible for the Array-level of our data

  3. UserTableRow

    Component responsible for the Object-level of our data

  4. UserTableHeader

    Encapsulate <thead>

<!--UserTable.vue-->
<!--props: User[]-->
<template>
<user-table-head />
<user-table-body :users="users"></user-table-body>
</template>

<!--UserTableHead.vue-->
<template>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
</template>

<!--UserTableBody.vue-->
<!--props: User[]-->
<template>
<tbody>
<user-table-row v-for="let user in users" :user="user">
</user-table-row>
</tbody>
</template>

<!--UserTableRow.vue-->
<!--props: User-->
<template>
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
</template>

Splitting up the table into these sub-components keeps the template short and less complex. It also makes testing much easier since each level is only concerned about a certain part of the complexity in the data.

note

New Requirements

  • Display the User Array in a table
  • Deleting users should be possible

To add the new interaction button we will have to place it at the end of each row. To have a more pronounced structure we will place this button in a new component UserTableActions. This creates a well defined place for adding more actions in the future. We also have to expand UserTableHead by one <th>.

<!--UserTableRow.vue-->
<!--props: User-->
<template>
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>
<user-table-actions @delete="onDelete" @edit="onEdit"/>
</td>
</tr>
</template>

<!--UserTableActions.vue-->
<template>
<button @click="emit('delete')">Delete</button>
</template>

Adding this action also reveals the biggest disadvantage of this approach: We have to pass the emit all the way up to UserTable component. Since all children of UserTable are ui-components they cannot access any state or the api.

Updated Requirements

  • Display the User Array in a table
  • Deleting users should be possible
  • Delete button should be disabled while an async request is pending

Let's ignore state interactions for this example. But to fulfil this requirement we will add a disabled-prop to UserTable so that our outside logic can disable the buttons while requests are pending. But how do we deal with this internally.

Option 1 - Passing the prop

We can pass the disabled value through our whole component tree. That is a completely valid and comparatively easy solution but it can quickly create a lot of boilerplate.

Option 2 - provide/inject

Vue Docs: Prop-Drilling & provide/inject. This can safe some development time since we don't have to deal with all the boilerplate of Option 1 but it can also lead to a mess of injections if not used carefully. Since this table and it's children are already heavily dependant on each other (they serve one shared purpose: Displaying a User-Table) we can use prop-drilling if we keep the injection-key as a private property of the module.

Updated Requirements

  • Display the User Array in a table
  • Deleting users should be possible
  • Delete button should be disabled while an async request is pending
  • The Email should be a mailto-Link

Remember the three levels of our data: Array > Object > Property. So far we have destructured the Array and Object levels into separate components. The new requirement could be implemented like this:

<!--UserTableRow.vue-->
<!--props: User-->
<template>
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>
<a :href="'mailto:' + user.email">{{user.email}}</a>
</td>
<td>
<user-table-actions @delete="onDelete" @edit="onEdit"/>
</td>
</tr>
</template>

While this template is still easy to understand in this simplified example, if we imagine a table with over 10 columns and a few lines of HTML for each table-cell we can see that this will get messy quite quickly.

A great possibility to keep the template clean is to destructure one more level, down to the Property:

<!--UserTableRow.vue-->
<!--props: User-->
<template>
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>
<user-table-cell-email :email="user.email" />
</td>
<td>
<user-table-actions @delete="onDelete" @edit="onEdit"/>
</td>
</tr>
</template>

<!--UserTableCellEmail.vue-->
<!--props: String-->
<template>
<a :href="'mailto:' + email">{{email}}</a>
</template>

Note that we did not create components for the id and name properties. Since they are not handled any differently they can just be shown using interpolation.

Rule: Create a sub-component for each meaningful level in your data

Rule: Use provide/inject of props only in small and defined scopes

Naming components in destructured component trees

Destructuring data over components can lead to many small components and picking meaningful names can become a challenge.

To find a name without much effort whenever I am creating components I use a pattern-based approach:

  • <feature identifier><level><specific description>

Let's analyze the naming in the UserTable example to illustrate the pattern:

  1. UserTable

The root component of the implementation. Its name consists of the feature identifier UserTable and nothing else.

  1. UserTableHead, UserTableBody and UserTableRow

The children of the root component are named by the feature identifier and their appriopriate levels in the table: Head, Body and Row. They are still quite general components and do not need a specific description since they are unique to their levels.

  1. UserTableCellEmail

This is a highly specific component and therefore includes the feature identifier, the level and a specific description to reflect its specific usecase.

Following this pattern makes it quite easy to name things while destructuring. It also leads to a well organized folder in the workspace explorer since components on the same level will be listed closely together - e.g. all UserTableCell-components have the same "prefix".

The most difficult part in my experience is finding a good name for the level-part of the name. I usually try to use names that reflect the component as an HTML-Element: names of the part of a table, list and list-item for list-structures or option when dealing with dropdowns etc.

- + \ No newline at end of file diff --git a/docs/nuxt-client/GettingStarted.html b/docs/nuxt-client/GettingStarted.html index 46a2ac0..63f3973 100644 --- a/docs/nuxt-client/GettingStarted.html +++ b/docs/nuxt-client/GettingStarted.html @@ -4,13 +4,13 @@ Getting Started | Schulcloud-Verbund-Software Documentation - +

Getting Started

Development Setup

Note: Please don't use yarn !!! We decided to use npm across all of our repositories.

In order to run this client, you need to have the legacy-client and schulcloud-server set up and running. See for documentation on how to do that in the respective repositories.

Start the Server

  1. Clone the repository

    git clone git@github.com:hpi-schul-cloud/nuxt-client.git
  2. Install the required dependencies:

    npm ci
  3. Start the development server:

    npm run serve

    By default the server will listen on the URL http://localhost:4000

Unit Tests

# Run all (unit) tests
npm run test

Lint

npm run lint

Editor Setup

We are using Visual Studio Code as our default deveopment-IDE. In /.vscode you can find two templates to setup your IDE:

  • launch.default.json (copy its content and us it in launch.json)
  • settings.default.json (copy its content and us it in settings.json)

For a list of recommended Visual Studio Code extensions please refer to extensions.json.

- + \ No newline at end of file diff --git a/docs/nuxt-client/GitConventions.html b/docs/nuxt-client/GitConventions.html index 3692831..38be843 100644 --- a/docs/nuxt-client/GitConventions.html +++ b/docs/nuxt-client/GitConventions.html @@ -4,7 +4,7 @@ Git Conventions | Schulcloud-Verbund-Software Documentation - + @@ -12,7 +12,7 @@

Git Conventions

Each change should be done in a Ticket (no matter how small).

We use a Feature Branch model. Start a branch from main and make a PR to main.

Branch naming:

{{ PROJECT_ABBREVIATION }}-{{ NUMBER }}-word1-word2-word2

e.g.: BC-1234-course-copy

We try to keep branch names small. The Ticket Number should be in Uppercase (e.g BC-1234) but the namespace should be in lowercase. It should stay below 64 letters.

Pull Requests

Pull Requests must contain a relevant description (template provides useful information, when creating the PR).

In case of UI changes also put a screenshot and talk to UX if thats fine like it is. All Pull Requests Criterias (as defined in deployment pipeline) must be green before merge, e.g. 1 approving review, unit tests or QA checkbox in PR template.

We merge by squash strategy. The squashed commit subject should start with a ticket number and end with a PR number. Write commit messages in imperative and active.

Example:

BC-1993 - lesson lernstore and geogebra copy (#3532)

In order to make sure developers in the future can find out why changes have been made,
we would like some descriptive text here that explains what we did and why.

- change some important things
- change some other things
- refactor some existing things

# We dont need to mention tests, changes that didnt make it to main, linter, or other fixups
# only leave lines that are relevant changes compared to main
# comments like this will not actually show up in the git history

Note for working with Windows: We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.

- + \ No newline at end of file diff --git a/docs/nuxt-client/HintsForWorking.html b/docs/nuxt-client/HintsForWorking.html index 48de4e4..ce85118 100644 --- a/docs/nuxt-client/HintsForWorking.html +++ b/docs/nuxt-client/HintsForWorking.html @@ -4,7 +4,7 @@ Hints for Working | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ One example to find these icons and how they are represented can be found here.

As UX works with Figma you can also easily find the name of the chosen icon directly in Figma with the so called Dev Mode. You can easily access this mode by using the toggle and then see the name of the icon in the left sidebar (see screenshot). Afterwards you can either search for the icon on the page above (by using hyphens between words) or directly import it from the library into your component.

Figma Dev Mode

- + \ No newline at end of file diff --git a/docs/nuxt-client/HowTo.html b/docs/nuxt-client/HowTo.html index 0f2f41b..169f37f 100644 --- a/docs/nuxt-client/HowTo.html +++ b/docs/nuxt-client/HowTo.html @@ -4,7 +4,7 @@ How To | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ These generated classes and methods internally use axios to request data and use generated types - both for the input to the methods and for the returned types.

HINT

Please use the generated types in your stores and do not redefine the same types. This way consistency between Server and Api-Access stays stable.

Regenerating the clients

Only if the server-api or the filestore-api has changed, you need to regenerate them using the following npm-scripts:

For generating the files to access the server-api please use:

npm run generate-client:server

The same is implemented for generating the backend-api to our filestore-backend.

For generating the files to access the filestore-api please use:

npm run generate-client:filestorage

Hint

For regenerating the clients you need an up-to-date running backend-server running in your environment.

Using the generated api

The generated APIs can easily be used. Examples can be seen in any current store-implementation - like here:

src/store/share-course.ts:

import {
ShareTokenApiFactory,
ShareTokenApiInterface,
ShareTokenBodyParams,
ShareTokenBodyParamsParentTypeEnum,
ShareTokenResponse,
} from "../serverApi/v3/api";

...

export default class ShareCourseModule extends VuexModule {
...
private get shareApi(): ShareTokenApiInterface {
return ShareTokenApiFactory(undefined, "v3", $axios);
}

@Action
async createShareUrl(
payload: SharePayload
): Promise<ShareTokenResponse | undefined> {
const shareTokenPayload: ShareTokenBodyParams = {
parentType: ShareTokenBodyParamsParentTypeEnum.Courses,
parentId: this.courseId,
expiresInDays: payload.hasExpiryDate ? 21 : null,
schoolExclusive: payload.isSchoolInternal,
};
...
const shareTokenResult =
await this.shareApi.shareTokenControllerCreateShareToken(
shareTokenPayload
);
...
}
...
}

User-Permissions on Pages

The permissions are controlled by createPermissionGuard middleware method that receives two parameters. The first parameter should contain an array of the userPermission that is required to reach the page. The second parameter is an optional fallback route. If the second parameter isn't provided and the user has no permission to reach the page, an error page (401) is shown.

// src/router/routes.ts

// with a fallback route
{
path: "/your/route",
component: () => import("../pages/your.page.vue"),
name: "yourRouteName",
beforeEnter: createPermissionGuard(["ADMIN_VIEW"], "/yourFallBackRoute"),
},

// without a fallback,
// it shows a '401' file if the user doesn't have permissions
{
path: "/your/route",
component: () => import("../pages/your.page.vue"),
name: "yourRouteName",
beforeEnter: createPermissionGuard(["ADMIN_VIEW", "SCHOOL_EDIT"]),
},

Exception handling

useApplicationError is a composable providing a typed factory function for creating application errors. A global error handler for putting application errors takes those and puts them into a store and a global error page will display them.

Exceptions should be thrown using them - like this:

// src/pages/user-migration/UserMigration.page.vue
import { useApplicationError } from "@/composables/application-error.composable";

const { createApplicationError } = useApplicationError();
throw createApplicationError(HttpStatusCode.BadRequest);
// src/router/guards/permission.guard.ts
import { useApplicationError } from "@/composables/application-error.composable";
import { applicationErrorModule } from "@/store";

const { createApplicationError } = useApplicationError();
applicationErrorModule.setError(createApplicationError(401));

Also look here: Meeting Notes 2022-11-25

inject - fallback throwing an error

We want to provide a simple factory function that produces a unique, identifiable error, if an inject fails and we want to avoid adding code to your TypeScript-components only to prevent linter errors. The topic will be implemented with this ticket: Jira - BC-2813. It contains a lot of details on that issue.

... Details should be added here. soon...

Also look here: Frontend Arc Group: Meeting Notes 2022-12-02

- + \ No newline at end of file diff --git a/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies.html b/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies.html index 470215b..f26021f 100644 --- a/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies.html +++ b/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies.html @@ -4,13 +4,13 @@ Identifying and Resolving Circular Dependencies | Schulcloud-Verbund-Software Documentation - +

Identifying and Resolving Circular Dependencies

What is a circular dependency?

Circular depencies are a common issue when working with barrel-files (index.ts).

Let's look at a common dependency pattern:

Circular Import

In this example there are two Building-Blocks (e.g. folders that have a barrel file) which depend on each other. Using the SharedComponent in both Building-Blocks will result in a circular dependency.

That basically means that the compiler can not resolve the order to load the Building-Blocks which causes an error.

Resolving Circular Dependencies

The basic gist is: break the circle and separate the shared dependency in a separate module.

Fixed Circular Import

In this configuration the compiler can find an order to resolve the building-blocks correctly.

How to identify Circular Dependencies in Vue

I recreated the first example error in Vue. When Vue tries to render ComponentA I see the following Error in the console:

Error Message

This can be quite hard to decipher on a first glance but it contains all the info we need to identify the root cause of the circular dependency.

Error Message Explained

Based on the info from the message we can learn that ComponentB "closed the circle" by importing SharedComponent. From there we can trace back to see where SharedComponent is exposed and why it depends on ComponentB. In this case it is because they are both imported in ComponentA.

Keep in mind that the circular dependency can involve multiple building-blocks.

- + \ No newline at end of file diff --git a/docs/nuxt-client/ProjectStructure.html b/docs/nuxt-client/ProjectStructure.html index 55457f5..43bfcd4 100644 --- a/docs/nuxt-client/ProjectStructure.html +++ b/docs/nuxt-client/ProjectStructure.html @@ -4,13 +4,13 @@ Project Structure | Schulcloud-Verbund-Software Documentation - +

Project Structure

Filenames

Files should be consistently named like this:

file contentstylefilenamecomment
ComponentsPascalCaseYourComponent.vuebest practice
Pinia storesPascalCaseBoardCard.store.tsplaced in data- modules
ComposablesCamelCaseconfirmationDialog.composable.tscurrent convention
UtilsCamelCaseyourArea.util.tssuggestion: move from yourUtil.ts => your.util.ts
other filesCamelCaseyourFilename.ts
test files{{ basename }}.unit.tse.g. YourComponent.unit.ts, yourArea.util.unit.ts

Folders

Folders are written in KebabCase (e.g. feature-board).

Building Blocks

The project's code is separated into building blocks.

What is a building-block?

A building-block is a "container" were we place most of our applications logic and components. Each building-block is defined by an index.ts (Barrel-File) describing it's exported content (public API of a building-block) and a type.

Utilizing linting rules and the index.ts we can ensure that each building-block only exposes files which are meant to be used application-wide. This way we achieve a strong separation of concern across the whole application.

Our linting rule is based on the following concept: Enforce Project Boundaries | Nx

Note: in above documentation libraries are equivalent to building-blocks and tags represent the types defined below.

near future: All newer modules, that already follow our naming convention (see link above), will move from "components/" into a central "modules/" folder with one subfolder for each type of module (page/, data/, feature/, ui/, util/ ).

Types of building-blocks

There are different types of building blocks each with a different purpose.

typeexamplecomment
Pagemodules / page / dashboardContains a subpage of the application. Orchestrates Feature and UI building-blocks.
Featuremodules / feature / calendarComplex features with stateful / smart components. Usually specialized to fulfill specific roles in the App. Can also contain presentational components that are specialized for this feature.
UImodules / ui / formsStateless / presentational components which get their data via props and emit events. Usually less specialized.
Datamodules / data / authState and API-access. Does not contain any visual components. They are the data-sources of all smart components.
Util modules / util / form-validators Contains shared low-level code.

Hint: currently the modules are all placed under /components/feature-... etc. and will be refactored to the above with this ticket: BC-5513

Matrix of allowed imports

Each type is only allowed to import modules of some of the other types.

                                Allowed to Import
It is
pagefeaturedatauiutil
page
feature
data
ui
util

Type: Page

A page building-block represents a subpage of the application. It contains the layout component and orchestrates feature and ui building blocks to create a subpage. It can not be imported into any other type of building-block. It is only imported by the vue-router and should be lazy-loaded if possible.

Type: Feature

A feature building-block contains a set of files that represent a business use case in an application.

Most of the components of features are stateful / smart components that interact with data sources. This type also contains most of the UI logic, form validation code, etc.

Type: UI

A ui building-block mainly contains Stateless / presentational components which are used all across the application. They don't have access to stores and do not use features in their templates. All data needed for components in ui building-blocks comes from props.

Type: Data

A data building-block contains stores and api-services. It does not contain any view components. They serve as data-sources for feature and page building blocks.

Type: Util

A utility building-block contains low level code used by many building-blocks. Often there is no framework-specific code and the building-block is simply a collection of types, utilities, pure functions, factories or composables.

Placed in folder util

Life of a feature module

Starting a new Feature module

  1. create a new subfolder inside of modules/feature/-folder and give it a speaking name
  2. place any files of different purposes (for ui, data and/or util) inside of it.
  3. provide a barrel-file index.ts that defines it's exposed API - the functionality that other modules are allowed to use. This should only export the bare minimum.

Another module needs access to the data of your feature

  1. do not export your data-functionality from your feature
  2. refactor your feature-module and extract the data part of it into a new data-module
  3. use this new data-module in the original feature and the other module that also needs access

Note: if you know upfront, which parts will definitly be used outside your feature, implement the data-module upfront

Another module needs access to some of your UI-Components that form your feature

  1. do not export these components simply from your feature
  2. if the components are simple / dumb and do not need data-access: consider extracting them into a separate ui-module (example: ui/preview-image)
  3. if the components are coupled with some data-functionality but this does not belong to the core of your feature: consider extracting those parts into a separate feature-module (example: feature/alert-list)

Note: if you are sure that a part of your feature, will be reused in the project - consider extracting it from your ticket - before implementation - and implement it first.

How to pick the correct type for my Task

To render this graph in VS-Code markdown preview install this extension: bierner.markdown-mermaid

- + \ No newline at end of file diff --git a/docs/nuxt-client/WritingTests.html b/docs/nuxt-client/WritingTests.html index 33f77d4..7562f41 100644 --- a/docs/nuxt-client/WritingTests.html +++ b/docs/nuxt-client/WritingTests.html @@ -4,7 +4,7 @@ Writing Tests | Schulcloud-Verbund-Software Documentation - + @@ -15,7 +15,7 @@ Testing Key, Mouse and other DOM events

Testing Asynchronous Behavior

You can test asynchronous behavior by using Vue.nextTick():

await Vue.nextTick();
...

OR by triggering an effect and awaiting this effect to take place:

const btnNext = wrapper.find(`[data-testid="dialog-next"]`);
await btnNext.trigger("click");
...

see also: VueTestUtils - Testing Asynchronous Behavior

Exceptions

await expect(() => copyModule.copy(payload)).rejects.toThrow(
`CopyProcess unknown type: ${payload.type}`
);

console.error

// UserMigration.page.unit.ts
const consoleErrorSpy = jest
.spyOn(console, "error")
.mockImplementation();

...

expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.any(ApplicationError)
);
consoleErrorSpy.mockRestore();

Testing Composables

Mocking

Replaces methods, instances of classes (e.g. stores) with some functionality, that e.g. simply returns a value you want to use in your test. By mocking you can easily simulate certain scenarios like failing requests or certain return values from any "external" (as in "not part of the code i am currently testing") functionality. Jest provides very helpful methods for that. Examples from our codebase:

const mock = jest.fn().mockReturnValue(expectedTranslation);
copyModuleMock.copyByShareToken = jest.fn()
.mockResolvedValue(copyResults);

They can easily be tested like this:

expect(copyModuleMock.copyByShareToken).toHaveBeenCalled();

Or more specific like this:

expect(addFileMetaDataSpy).toHaveBeenCalledWith(
expect.objectContaining<FileMetaListResponse>({ size: 2 } as FileMetaListResponse)
);

See also here: VueTestUtils mount - mocks and stubs are now in global

Mocking injections

Mocking Vuex-Store

Mocking a vuex-store in a component

Example file: src/components/administration/AdminMigrationSection.unit.ts

import { createModuleMocks } from "@/utils/mock-store-module";
import YourModule from "@/store/YourModule";

let yourModule: jest.Mocked<YourModule>;

schoolsModule = createModuleMocks(YourModule, {
yourMethodName: {
...
},
...yourGetters,
}) as jest.Mocked<YourModule>;


mount(YourComponentToBeTested, {
...createComponentMocks({
...
}),
provide: {
yourModule,
},
});

expect(yourModule.<yourMethodName>).toHaveBeenCalledWith(...);

Testing a store

import YourModule from "./your-module";

const yourModule = new YourModule({});

...

// using `jest.spyOn()`
it("should call something", () => {
const yourActionNameMock = jest.spyOn(yourModule, "yourActionName");
yourModule.yourActionName();
expect(yourActionNameMock).toHaveBeenCalled();
});

// or using a method directly
it("should set something", () => {
yourModule.setLoading(true);
expect(yourModule.getLoading).toBe(true);
});

Mocking Pinia-Stores

*{{ tbd }} (when Pinia-Stores are enabled for the project)*

Mocking Composables

Sometimes - if a composable is simple and does not create sideeffects - it is okay to use it in the tests and avoid mocking it.

That's beneficial as it let's us stick to the BlackBox-Idea: we should not know what the component is using internally.

If you need to mock a composable, you can simple do this like in the following example. You only have to ensure to return everything the composable returns... but mocked versions of it.

...
jest.spyOn(ourExampleComposable, "useExample").mockReturnValue({
// return mocks of what the composable would have returned
});
...

Components that are hard to test

If you ever get into trouble to write good tests for your compents or code in general - this might be an indicator, that maybe your code is not structured good enough.

Consider:

  • spliting your component into smaller sub-components with a small API
  • extracting functionality into one or mutliple composables
  • using an existing composable (from VueUse or an existing one in the project)
  • using an existing vuetify-component instead of writing it all yourself
  • reshaping the communication workflow (parameters, events, inject/provide, stores, composables)
  • (replacing a Vuex-store with a Pinia-store)

For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd)

End-To-End-Tests

(aka Integration/Acceptance/System-Tests)

End-to-End-Tests are developed in a seperate repository end-to-end-tests

Documentation of e2e tests

Code-Coverage

For monitoring our code-coverage we are using Codacy. The current status can be seen on this Dashboard.

- + \ No newline at end of file diff --git a/docs/schulcloud-client/Getting started.html b/docs/schulcloud-client/Getting started.html index 7f240b0..c7deb25 100644 --- a/docs/schulcloud-client/Getting started.html +++ b/docs/schulcloud-client/Getting started.html @@ -4,13 +4,13 @@ Getting started | Schulcloud-Verbund-Software Documentation - +

Getting started

Make sure to have the following software installed

  • node 18
git clone git@github.com:hpi-schul-cloud/schulcloud-client.git

Install the packages

cd schulcloud-client
npm ci
npm run build

The last step is to start the dev server with.

npm run watch

If you want to change the instance you need to set an env variable

declare -x SC_THEME="n21"
node node_modules/gulp/bin/gulp.js clear-cache && node node_modules/gulp/bin/gulp.js
npm run watch
- + \ No newline at end of file diff --git a/docs/schulcloud-server/Api.html b/docs/schulcloud-server/Api.html index cc0b02b..fbf614c 100644 --- a/docs/schulcloud-server/Api.html +++ b/docs/schulcloud-server/Api.html @@ -4,7 +4,7 @@ API design | Schulcloud-Verbund-Software Documentation - + @@ -15,7 +15,7 @@ The @ApiResponse decorator is used to define the response.
The @ApiExtraModels decorator is used to define the response models.

The final response should either be an javascript Object or an array. We do not return primitives like string or boolean.

Swagger will automatically generate the response schema from the response object.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/access-legacy-code.html b/docs/schulcloud-server/Coding-Guidelines/access-legacy-code.html index 4d4ee49..cacc597 100644 --- a/docs/schulcloud-server/Coding-Guidelines/access-legacy-code.html +++ b/docs/schulcloud-server/Coding-Guidelines/access-legacy-code.html @@ -4,14 +4,14 @@ Access legacy Code | Schulcloud-Verbund-Software Documentation - +

Access legacy Code

Access Feathers Service from NestJS

The FeathersModule provides functionality to access legacy code. In order to introduce strong typing, it is necessary to write an adapter service for the feathers service you want to access. Place this adapter within your module, and use the FeathersServiceProvider to access the service you need

// inside your module, import the FeathersModule
@Module({
imports: [FeathersModule],
providers: [MyFeathersServiceAdapter],
})
export class MyModule {}

// inside of your service, inject the FeathersServiceProvider
@Injectable()
export class MyFeathersServiceAdapter {
constructor(private feathersServiceProvider: FeathersServiceProvider) {}

async get(): Promise<string[]> {
const service = this.feathersServiceProvider.getService(`path`);
const result = await service.get(...)

return result;
}

Access NestJS injectable from Feathers

To access a NestJS service from a legacy Feathers service you need to make the NestJS service known to the Feathers service-collection in main.ts.

This possibility should not be used for new features in Feathers, but it can help if you want to refactor a Feathers service to NestJs although other Feathers services depend on it.

    // main.ts
async function bootstrap() {
// (...)
feathersExpress.services['nest-rocket-chat'] = nestApp.get(RocketChatService);
// (...)
}

Afterwards you can access it the same way as you access other Feathers services with app.service('/nest-rocket-chat');

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/code-style.html b/docs/schulcloud-server/Coding-Guidelines/code-style.html index 195cfeb..2560c2a 100644 --- a/docs/schulcloud-server/Coding-Guidelines/code-style.html +++ b/docs/schulcloud-server/Coding-Guidelines/code-style.html @@ -4,13 +4,13 @@ Code Style | Schulcloud-Verbund-Software Documentation - +

Code Style

Function

Naming functions

The name of a function should clearly communicate what it does. There should be no need to read the implementation of a function to understand what it does.

There are a few keywords that we use with specific meaning:

"is..."

isTask(), isPublished(), isAuthenticated(), isValid()

A function with the prefix "is..." is checking wether the input belongs to a certain (sub)class, or fulfils a specific criteria.

The function should return a boolean, and have no sideeffects.

"check..."

checkPermission(), checkInputIsValid()

A function with the prefix "check..." is checking the condition described in its name, throwing an error if it does not apply.

"has..."

hasPermission(),

similar to "is...", the prefix "has..." means that the function is checking a condition, and returns a boolean. It does NOT throw an error.

Avoid direct returns of computations

avoid directly returning the result of some computation. Instead, use a variable to give the result of the computation a name.

Exceptions can be made when the result of the computation is already clear from the function name, and the function is sufficiently simple to not occlude its meaning.

public doSomething(): FileRecordParams[] {
// ... more logic here
const fileRecordParams = fileRecords.map((fileRecord) => Mapper.toParams(fileRecord));
// hint: this empty line can be increase the readability
return fileRecordParams;
}

public getName(): String {
return this.name;
}

public getInfo(): IInfo {
// ... more logic here
return { name, parentId, parentType }; // but if the return include many keys, please put it first to a const
}

avoid directly passing function results as parameters

function badExample(): void {
doSomething(this.extractFromParams(params), this.createNewConfiguration());
}

function goodExample(): void {
const neededParams = this.extractFromParams(params);
const configuration = this.createNewConfiguration();
doSomething(neededParams, configuration);
}

explicit return type

public doSomething(): FileRecords[] {
//...
return fileRecords
}

Interfaces

Avoid the "I" for interfaces

In most cases, it should not matter to the reader/external code wether something is an interface or an implementation. Only prefix the "I" when necessary, or when its specifically important to the external code to know that its an interface, for example when external code is required to implement the interface.

interface CourseRepo {
getById(id): Course
// ...
}

class InMemoryCourseRepo implements CourseRepo {
getById(id): Course {
return new Course()
}
}

Classes

Order of declarations

Classes are declared in the following order:

  1. properties
  2. constructor
  3. methods

Example:

export class Course {
// 1. properties
name: string;

// more properties...

// 2. constructor
constructor(props: { name: string }) {
// ...
}

// 3. methods
public getShortTitle(): string {
// ...
}

// more methods...
}

Naming classes

Classes should be named in CamelCase. They should have a Suffix with the kind of Object they represent, and from the beginning to the end go from specific to general.

  • CourseController
  • CourseCreateBodyParam
  • CourseCreateQueryParam
  • CourseCreateResponse
  • CourseDtoMapper
  • CourseUc
  • CourseAuthorisationDto
  • CourseDo
  • CourseService
  • CourseRepo
  • CourseEntity
  • CourseEntityMapper

Do NOT use JsDoc

You should always try to write code in a way that does not require further explanation to understand. Use proper names for functions and variables, and extract code and partial results into functions or variables in order to name them. If you feel like a function needs a JsDoc, treat that as a codesmell, and try to rewrite the code in a way that is more self-explanatory.

Do use empty lines

empty lines help to structure code. Use them wherever you want to seperate parts of a function from each other (and think about further extracting functions). Common uses include:

  • before return statement
  • before and after an if/else statement
  • between test sections (arrange, act, assert)
  • between "it()" statements in tests
- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/configuration.html b/docs/schulcloud-server/Coding-Guidelines/configuration.html index 05b239d..eee72a8 100644 --- a/docs/schulcloud-server/Coding-Guidelines/configuration.html +++ b/docs/schulcloud-server/Coding-Guidelines/configuration.html @@ -4,14 +4,14 @@ Configuration | Schulcloud-Verbund-Software Documentation - +

Configuration

The Configuration Management consists of three layers working together.

  • Application Layer: Defines the Variables the application needs as well es their type, and validates their values on application start.
  • Configmap: Provides the Configuration as environment variables, making them available to the application.
  • dof-configuration: defines configuration values for various instances and deployment stages.

Each of those layers is described in more detail below.

Application Configuration

WORK IN PROGRESS we are still working to refine how the configuration should work within the application. Specifically, we still want to:

  • remove the hpi-common library which currently takes care of defining a configuration schema and validating the values
  • streamline each modules definition of necessary configuration values in NestJs
  • consider how to implement runtime configuration and configuration changes
  • improve the workflow of introducing (and removing!) feature flags

TODO: document how config files in nest work TODO: document how the config schema works

Config Maps

Each of our code repositories contains the ansible files necessary for deploying the application(s) within.

The configmap can be found under ansible/roles/:application-name:/templates/:application-name-configmap.yml.j2.

apiVersion: v1
kind: ConfigMap
metadata:
name: application-name-configmap
namespace: {{ NAMESPACE }}
labels:
app: application-label
data:
SOME_CONFIGURATION_KEY: "{{ SOME_CONFIGURATION_KEY }}"
SOME_RENAMED_CONFIGURATION_KEY: "{{ SOME_ORIGINAL_CONFIGURATION_KEY }}"
HARDCODED_VALUE: "value"
COMPOSED_VALUE: "https://{{ DOMAIN }}/some/thing"

under ansible/roles/:application-name:/defaults we define default values to be used when no values are provided from the environment.

dof Configuration

The actual configuration values used for our deployments can be found in the dof_app_deploy repository under ansible/group_vars.

Here, you will find various yml files containing configuration values.

Kubernetes will apply each of these files in a defined order, beginning with the folder all, then applying the files in the folder corresponding to the deployment stage (eg. development, reference, production), and finally the files in the folder corresponding to the instance (eg. brb, dbc, thr, nbc.)

Values in files that are applied later will overwrite earlier values.

All of these values are gathered as kubernetes facts, and can theoretically be used by all config maps, ensuring the configurations of different applications are consistent with each other. Do note that a value must be mapped in a config map to be available to a given application.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/controllers.html b/docs/schulcloud-server/Coding-Guidelines/controllers.html index 6ac2e01..829c823 100644 --- a/docs/schulcloud-server/Coding-Guidelines/controllers.html +++ b/docs/schulcloud-server/Coding-Guidelines/controllers.html @@ -4,13 +4,13 @@ Controller | Schulcloud-Verbund-Software Documentation - +

Controller

A modules api layer is defined within of controllers.

The main responsibilities of a controller is to define the REST API interface as openAPI specification and map DTO's to match the logic layers interfaces.

    @ApiOperation({ summary: 'some descriptive information that will show up in the API documentation' })
@ApiResponse({ status: 200, type: BoardResponse })
@ApiResponse({ status: 400, type: ApiValidationError })
@ApiResponse({ status: 403, type: ForbiddenException })
@ApiResponse({ status: 404, type: NotFoundException })
@Post()
async create(@CurrentUser() currentUser: ICurrentUser, @Body() params: CreateNewsParams): Promise<NewsResponse> {
const news = await this.newsUc.create(
currentUser.userId,
currentUser.schoolId,
NewsMapper.mapCreateNewsToDomain(params)
);
const dto = NewsMapper.mapToResponse(news);

return dto;
}

JWT-Authentication

For authentication, use guards like JwtAuthGuard. It can be applied to a whole controller or a single controller method only. Then, the authenticated user can be injected using the @CurrentUser() decorator.

Validation

Global settings of the core-module ensure request/response validation against the api definition. Simple input types might additionally use a custom pipe while for complex types injected as query/body are validated by default when parsed as DTO class.

DTOs

All data that leaves or enters the system has to be defined and typed using DTOs.

export class CreateNewsParams {
@IsString()
@SanitizeHtml()
@ApiProperty({
description: 'Title of the News entity',
})
title!: string;

// ...
}

DTO File naming

Complex input DTOs are defined like [create-news].params.ts (class-name: CreateNewsParams).

When DTO's are shared between multiple modules, locate them in the layer-related shared folder.

Security: When exporting data, internal entities must be mapped to a response DTO class named like [news].response.dto. The mapping ensures which data of internal entities are exported.

openAPI specification

Defining the request/response DTOs in a controller will define the openAPI specification automatically. Additional validation rules and openAPI definitions can be added using decorators. For simplification, openAPI decorators should define a type and if a property is required, while additional decorators can be used from class-validator to validate content.

Mapping

You should define a mapper to easily create dtos from the uc responses, and the datatypes expected by ucs from params.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/deprection-workflow.html b/docs/schulcloud-server/Coding-Guidelines/deprection-workflow.html index a463b76..538f624 100644 --- a/docs/schulcloud-server/Coding-Guidelines/deprection-workflow.html +++ b/docs/schulcloud-server/Coding-Guidelines/deprection-workflow.html @@ -4,7 +4,7 @@ Deprecation Workflow | Schulcloud-Verbund-Software Documentation - + @@ -16,7 +16,7 @@ Step 2: Mark the old code with "@deprecated" add a hint in the comments to use the new code.
Step 3: Inform team
Step 4: Remove deprecated code (when is hard to say, once all dependencies are removed)

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/domain-object-validation.html b/docs/schulcloud-server/Coding-Guidelines/domain-object-validation.html index 0482a2e..8669d17 100644 --- a/docs/schulcloud-server/Coding-Guidelines/domain-object-validation.html +++ b/docs/schulcloud-server/Coding-Guidelines/domain-object-validation.html @@ -4,7 +4,7 @@ Domain Object Validation | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ A specification fulfills the following interface:

public interface Specification<T> {
boolean isSatisfiedBy(T t);
}

A specification checks if a domain object fulfills the conditions of the specification.

A specification can simply specify that a domain object is valid. E.g. a Task has an owner and a description. A specification can specify more complex and specialized conditions. E.g. Task where every student assigned to the task's course has handed in a submission.

The specification pattern in its full extend describes how to use logic operators to combine multiple specifications into combined specifications as well. Please don't build this as long as you don't need it. YAGNI. More about full specification pattern

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/event-handling.html b/docs/schulcloud-server/Coding-Guidelines/event-handling.html index 1009b1b..04e461c 100644 --- a/docs/schulcloud-server/Coding-Guidelines/event-handling.html +++ b/docs/schulcloud-server/Coding-Guidelines/event-handling.html @@ -4,13 +4,13 @@ Event Handling | Schulcloud-Verbund-Software Documentation - +

Event Handling

Internal Events are used as a mechanism for Dependency Inversion.

If you are implementing an operation in a module that needs to trigger an operation in another module, that is simple if you can simply import a service. However, if that other module already has a dependency on your module, that would lead to a dependency cycle. In this case, you need to inverse one of the dependencies via events.

The main thing you need to think about, is which module should know about which module(s). This is the dependency, and it only ever can point into one direction. As a general rule of thumb, the module that is more specific, or is changing more frequently, or is less central to the functionality of the system, should have the dependency on the other.

In the following example, the course module has a dependency on the user module, but NOT vice versa.

How to implement Event Handling

consider the following folder structure

- users
- api
- domain
- services
- events
- user-deleted.event.ts
- repo
- courses
- api
- domain
- services
- handlers
- user-deleted.handler.ts
- repo

each of the modules needs to import the CqrsModule

// users.module.ts
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';

@Module({
imports: [CqrsModule],
providers: [/* some things here */],
exports: [/* some things here */],
})
export class GroupModule {}

Defining an Event

The event is in the end simply a class, containing any data required to handle the event

// users/domain/events/user-deleted.event.ts
export class UserDeletedEvent {
id: EntityId

constructor(id: EntityId) {
this.id = id
}
}

Make sure to make your event public in the index file of your module

Sending an Event

// users/domain/services/service.ts
import { EventBus } from '@nestjs/cqrs';
import { UserDeletedEvent } from '../events';

@Injectable()
export class Service {
constructor(private readonly eventBus: EventBus) {}

public async delete(userId: EntityId): Promise<void> {
doStuffForDeletion()

await this.eventBus.publish(new UserDeletedEvent(userId));
}
}

Recieving an Event

// courses/domain/handler/user-deleted.handler.ts
import { UserDeletedEvent } from '@modules/users';
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { SomeService } from '../services'

@Injectable()
@EventsHandler(UserDeletedEvent)
export class GroupDeletedHandlerService implements IEventHandler<UserDeletedEvent> {
constructor(private readonly someService: SomeService) {}

public async handle(event: GroupDeletedEvent): Promise<void> {
await someService.doSomeStuff()
}
}

Note that the handler should not contain any logic, but only the orchestration of what needs to be done.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/exception-handling.html b/docs/schulcloud-server/Coding-Guidelines/exception-handling.html index 30fd052..960d2ec 100644 --- a/docs/schulcloud-server/Coding-Guidelines/exception-handling.html +++ b/docs/schulcloud-server/Coding-Guidelines/exception-handling.html @@ -4,13 +4,13 @@ Exception Handling | Schulcloud-Verbund-Software Documentation - +

Exception Handling

exception hierarchy

We separate our business exceptions from technical exceptions. While for technical exceptions, we use the predefined HTTPExceptions from NestJS, business exceptions inherit from abstract BusinessException.

By default, implementations of BusinessException must define

code: 500
type: "CUSTOM_ERROR_TYPE",
title: "Custom Error Type",
message: "Human readable details",
// additional: optionalData

There is a GlobalErrorFilter provided to handle exceptions, which cares about the response format of exceptions and logging. It overrides the default NestJS APP_FILTER in the core/error-module.

In client applications, for technical errors, evaluate the http-error-code, then for business exceptions, the type can be used as identifier and additional data can be evaluated.

For business errors we use 409/conflict as default to clearly have all business errors with one error code identified.

Sample: For API validation errors, 400/Bad Request will be extended with validationError: ValidationError[{ field: string, error: string }] and a custom type API_VALIDATION_ERROR.

Pipes can be used as input validation. To get errors reported in the correct format, they can define a custom exception factory when they should produce api validation error or other exceptions, handled by clients.

Chaining errors with the cause property

If you catch an error and throw a new one, put the original error in the cause property of the new error. See example:

try {
someMethod();
} catch(error) {
throw new ForbiddenException('some message', { cause: error });
}

Loggable exceptions

If you want the error log to contain more information than just the exception message, use or create an exception which implements the Loggable interface. Don't put data directly in the exception message!

A loggable exception should extend the respective Built-in HTTP exception from NestJS. For the name just put in "Loggable" before the word "Exception", e.g. "BadRequestLoggableException". Except for logging a loggable exception behaves like any other exception, specifically the error response is not affected by this.

See example below.

export class UnauthorizedLoggableException extends UnauthorizedException implements Loggable {
constructor(private readonly username: string, private readonly systemId?: string) {
super();
}

getLogMessage(): ErrorLogMessage {
const message = {
type: 'UNAUTHORIZED_EXCEPTION',
stack: this.stack,
data: {
userName: this.username,
systemId: this.systemId,
},
};

return message;
}
}
export class YourService {
public sampleServiceMethod(username, systemId) {
throw new UnauthorizedLoggableException(username, systemId);
}
}
- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/logging.html b/docs/schulcloud-server/Coding-Guidelines/logging.html index 53faabc..ab75415 100644 --- a/docs/schulcloud-server/Coding-Guidelines/logging.html +++ b/docs/schulcloud-server/Coding-Guidelines/logging.html @@ -4,13 +4,13 @@ Logging | Schulcloud-Verbund-Software Documentation - +

Logging

For logging use the Logger, exported by the logger module. It encapsulates a Winston logger. Its injection scope is transient, so you can set a context when you inject it.

For better privacy protection and searchability of logs, the logger cannot log arbitrary strings but only so called loggables. If you want to log something you have to use or create a loggable that implements the Loggable interface.

The message should be fixed in each loggable. If you want to log further data, put in the data field of the LogMessage, like in the example below.

export class YourLoggable implements Loggable {
constructor(private readonly userId: EntityId) {}

getLogMessage(): LogMessage {
return {
message: 'I am a log message.',
data: { userId: this.userId, },
};
}
}
import { Logger } from '@src/core/logger';

export class YourUc {
constructor(private logger: Logger) {
this.logger.setContext(YourUc.name);
}

public sampleUcMethod(user) {
this.logger.log(new YourLoggable(userId: user.id));
}
}

This produces a logging output like

[NestWinston] Info - 2023-05-31 15:20:30.888   [YourUc] {  message: 'I am a log message.',  data: {   userId: '0000d231816abba584714c9e'  }}

Log levels and error logging

The logger exposes the methods log, warn, debug and verbose. It does not expose an error method because we don't want errors to be logged manually. All errors are logged in the exception filter.

Legacy logger

While transitioning to the new logger for loggables, the old logger for strings is still available as LegacyLogger.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/micro-orm.html b/docs/schulcloud-server/Coding-Guidelines/micro-orm.html index 4ef490e..ae40a5f 100644 --- a/docs/schulcloud-server/Coding-Guidelines/micro-orm.html +++ b/docs/schulcloud-server/Coding-Guidelines/micro-orm.html @@ -4,14 +4,14 @@ Defining Entities | Schulcloud-Verbund-Software Documentation - +

Defining Entities

When defining entities with MikroORM (Version 5), the following should be considered: The property decorator requires explicit assignment of the type to the property and may not work correctly when working with type inference or assigning union types to a property. In these cases, the metadata may not be set correctly, which can lead to exceptions, for example, when using the em.assign() or em.aggregate() functions.

Therefore, the following is not sufficient:

  @Property()
termsAccepted = false;

@Property()
createdAt = new Date();

The following works:

 @Property()
termsAccepted: boolean = false;

@Property()
createdAt: Date = new Date();

The better way is to provide the type through the decorator:

 @Property({ type: 'boolean' })
termsAccepted = false;

@Property({ type: Date })
createdAt = new Date();

Errors can also occur when specifying multiple types (union types):

 @Poperty({ nullable: true })
dueDate: Date | null;

To set the metadata correctly, do the following:

 @Property({ type: Date, nullable: true })
dueDate: Date | null;

If type inference is not used, specifying the type through the property decorator is not necessary:

 @Property()
name: string;

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/modules-submodules.html b/docs/schulcloud-server/Coding-Guidelines/modules-submodules.html index 941e60e..bec6ed8 100644 --- a/docs/schulcloud-server/Coding-Guidelines/modules-submodules.html +++ b/docs/schulcloud-server/Coding-Guidelines/modules-submodules.html @@ -4,13 +4,13 @@ Implementation and usage of modules, submodule and barrel files in our project | Schulcloud-Verbund-Software Documentation - +
-

Implementation and usage of modules, submodule and barrel files in our project

In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.

Module Structure

Modules and Submodules

In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the export keyword. To import a module, you use the import keyword followed by the module name. Here's an example:

import { ModuleName } from '@modules/module-name';

Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts):

// @modules/module-name/index.ts
export { SubmoduleServiceName } from './submodule-name/service.ts';

Barrel Files

Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules.

If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again.

Here's an example of a barrel file:

// @modules/module-name/index.ts
export { PublicService } from './services/public-service.ts';
export { ServiceInterfaceA, InterfaceB } from './interfaces';
export { InterfaceC } from './submodule-name/interfaces';

!!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like export * from './services' in the barrel file.

And here's how you can import from the barrel:

// @modules/other-module-name/service.ts
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';

Handling Circular Dependencies

Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs.

Module Structure

Here are some strategies to handle circular dependencies:

  1. Refactor Your Code: The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle.
// @modules/moduleC/service.ts
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA';
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB';
  1. Use Interfaces: If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle.
// @modules/moduleC/service.ts
import { type PublicService } from '@modules/moduleA';
import { type PublicService } from '@modules/moduleB';
  1. Use Events: If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it.

Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible.

- +

Implementation and usage of modules, submodule and barrel files in our project

In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.

Module Structure

Modules and Submodules

In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the export keyword. To import a module, you use the import keyword followed by the module name. Here's an example:

import { ModuleName } from '@modules/module-name';

Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts):

// @modules/module-name/index.ts
export { SubmoduleServiceName } from './submodule-name/service.ts';

Barrel Files

Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules.

If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again.

Here's an example of a barrel file:

// @modules/module-name/index.ts
export { PublicService } from './services/public-service.ts';
export { ServiceInterfaceA, InterfaceB } from './interfaces';
export { InterfaceC } from './submodule-name/interfaces';

!!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like export * from './services' in the barrel file.

And here's how you can import from the barrel:

// @modules/other-module-name/service.ts
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';

Handling Circular Dependencies

Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs.

Module Structure

Here are some strategies to handle circular dependencies:

  1. Refactor Your Code: The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle.
// @modules/moduleC/service.ts
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA';
import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB';
  1. Use Interfaces: If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle.
// @modules/moduleC/service.ts
import { type PublicService } from '@modules/moduleA';
import { type PublicService } from '@modules/moduleB';
  1. Use Events: If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it.

Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible.

+ \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/repositories.html b/docs/schulcloud-server/Coding-Guidelines/repositories.html index 0f855f6..f7dbc61 100644 --- a/docs/schulcloud-server/Coding-Guidelines/repositories.html +++ b/docs/schulcloud-server/Coding-Guidelines/repositories.html @@ -4,13 +4,13 @@ Repositories | Schulcloud-Verbund-Software Documentation - +

Repositories

The repository is responsible to provide domain objects for the domain layer. Typically, it does so by accessing a database.

Since the domain layer should be isolated (not have knowledge of the outer layers), the domain layer should have an interface definition for each repository it wants to access. This naturally means that the interface can only mention domain objects, without any knowledge about database entities.

// somewhere in the domain layer...
export interface SchoolRepo {
getSchoolById(schoolId: EntityId): Promise<School>;
}

The repository itself can now implement this interface, in this example using MikroOrm to get data from a database

@Injectable()
export class SchoolMikroOrmRepo implements SchoolRepo {
constructor(private readonly em: EntityManager) {}

public async getSchoolById(schoolId: EntityId): Promise<School> {
const entity = await this.em.findOneOrFail(
SchoolEntity,
{ id: schoolId },
);

const school = SchoolEntityMapper.mapToDo(entity);

return school;
}
}

Note that the internal entity manager uses a different datatype, the SchoolEntity, to represent schooldata. This type includes information that is specific to mikroorm, and should not be mixed into the domain object definition. A mapper is used to explicitly map the entity to the domain object, and vice versa.

- + \ No newline at end of file diff --git a/docs/schulcloud-server/Coding-Guidelines/testing.html b/docs/schulcloud-server/Coding-Guidelines/testing.html index 7a76af4..12ec1ef 100644 --- a/docs/schulcloud-server/Coding-Guidelines/testing.html +++ b/docs/schulcloud-server/Coding-Guidelines/testing.html @@ -4,7 +4,7 @@ Testing | Schulcloud-Verbund-Software Documentation - + @@ -28,7 +28,7 @@ 1.2 provide the repo which should be tested
  • Get repo, orm and entityManager from testing module
  •     import { MongoMemoryDatabaseModule } from '@src/modules/database';

    let repo: NewsRepo;
    let em: EntityManager;
    let testModule: TestingModule;

    beforeAll(async () => {
    testModule: TestingModule = await Test.createTestingModule({ (1)
    imports: [
    MongoMemoryDatabaseModule.forRoot({ (1.1)
    entities: [News, CourseNews, ...],
    }),
    ],
    providers: [NewsRepo], (1.2)
    }).compile();
    repo = testModule.get(NewsRepo); (2)
    orm = testModule.get(MikroORM);
    em = testModule.get(EntityManager);
    })

    Post conditions (afterAll), Teardown

    After all tests are executed close the app and orm to release the resources by closing the test module.

        afterAll(async () => {
    await testModule.close();
    });

    When Jest reports open handles that not have been closed, ensure all Promises are awaited and all application parts started are correctly closed.

    Entity Factories

    To fill the in-memory-db we use factories. They are located in \apps\server\src\shared\testing\factory. If you create a new one, please add it to the index.ts in that folder.

    Accessing the in-memory-db

    While debugging the tests, the URL to the in-memory-db can be found in the EntityManager instance of your repo in em.config.options.clientUrl.

    Copy paste this URL to your DB Tool e.g. MongoDB Compass. You will find a database called 'test' with the data you created for your test.

    Mapping Tests

    Mapping tests are Unit Tests which verify the correct mapping between entities and Dto objects. These tests should not have any external dependencies to other layers like database or use cases.

    Use Case Tests

    Since a usecase only contains orchestration, its tests should be decoupled from the components it depends on. We thus use unittests to verify the orchestration where necessary

    All Dependencies should be mocked. Use Spies to verify necessary steps, such as authorisation checks.

    to be documented

    Controller Tests

    Controllers do not contain any logic, but exclusively information to map and validate between dataformats used on the network, and those used internally, as well as documentation of the api.

    Most of these things can not be covered by unit tests. Therefore we do not write specific unittests for them, and only cover them with api tests.

    API Tests

    The API tests are plumbing or integration tests. Their job is to make sure all components that interact to fulfill a specific api endpoint are wired up correctly, and fulfil the expectation set up in the documentation.

    API tests should be located in the folder controller/api-test of each module.

    They should call the endpoint like a external entity would, treating it like a blackbox. It should try all parameters available on the API, users with different roles, as well as relevant error cases.

    During the API test, all components that are part of the server, or controlled by the server, should be available. This includes an in-memory database.

    Any external services or servers that are outside our control should be mocked away via their respective adapters.

    References

    This guide is inspired by javascript-testing-best-practices by goldbergyoni

    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Development/git.html b/docs/schulcloud-server/Development/git.html index bd5b739..9130214 100644 --- a/docs/schulcloud-server/Development/git.html +++ b/docs/schulcloud-server/Development/git.html @@ -4,13 +4,13 @@ Git | Schulcloud-Verbund-Software Documentation - +

    Git

    Branch name conventions

    • Each change should be done in a ticket (no matter how small)
      • The ticket does not need to be refined for very small things
      • Might be relevant for reporting later
    • Folder (feature/..) should no longer be used
    • Stay below 64 letters
      • Do not simply use ticket title, usually we need a shorter description :-)
    • Ticket number needs to be uppercase (BC-1234)
      • Related to matching with Jira
      • Careful: namespace is lowercase
    BC-XXXX-kebab-case-short-description

    Commit message conventions

    • Squashed commit subject should start with a ticket number, and end with a PR number
    • Clean body (contains all commits by default)
      • Only leave changes relevant for main
      • Remove commits likes 'fix for linter', 'add tests', 'fix review comments'
      • See example below
    • Write commit messages in imperative and active
      • Good: "make the code better"
      • Bad: "made the code better", "makes the code better"
    • Feel free to write actual text
    BC-1993 - lesson lernstore and geogebra copy (#3532)

    In order to make sure developers in the future can find out why changes have been made,
    we would like some descriptive text here that explains what we did and why.

    - change some important things
    - change some other things
    - refactor some existing things

    # I dont need to mention tests, changes that didnt make it to main, linter, or other fixups
    # only leave lines that are relevant changes compared to main
    # comments like this will not actually show up in the git history

    Windows

    We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.

    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Development/keycloak.html b/docs/schulcloud-server/Development/keycloak.html index 657197a..c94f240 100644 --- a/docs/schulcloud-server/Development/keycloak.html +++ b/docs/schulcloud-server/Development/keycloak.html @@ -4,7 +4,7 @@ ErWIn-IDM (Keycloak) | Schulcloud-Verbund-Software Documentation - + @@ -19,7 +19,7 @@ and testing. In the table below you can see the username and password combinations for the Keycloak login.

    UsernamePasswordDescription
    keycloakkeycloakThe overall Keycloak administrator with all permissions.
    dbildungsclouddBildungscloudThe dBildungscloud realm specific administrator.

    Updating Seed Data

    1. Run Keycloak and make the desired changes
    2. Use docker container exec -it keycloak bash to start a bash in the container
    3. Use the Keycloak-CLI to export all Keycloak data with /opt/keycloak/bin/kc.sh export --dir /tmp/realms
    4. Save your changes with a commit
    5. If you start your container with a command from the docker section, your changes will be directly applied to the starting Keycloak container

    IMPORTANT: During the export process there will be some errors, that's because the export process will be done on the same port as the Keycloak server. This leads to Keycloak failing to start the server in import/export mode. Due to the transition from WildFly to Quarkus as application server there is currently no documentation on this topic.

    In order to re-apply the seeding data for a running keycloak container, you may run following commands (to be executed in the repository root):

    1. docker cp ./backup/idm/keycloak keycloak:/tmp/realms
    2. docker exec erwinidm /opt/keycloak/bin/kc.sh import --dir /tmp/realms

    NPM Commands

    A list of available NPM commands regarding Keycloak / IDM.

    CommandDescription
    setup:idm:seedSeeds users for development and testing purpose into the IDM
    setup:idm:configureConfigures identity and authentication providers and other details in the IDM
    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Development/rocket-chat.html b/docs/schulcloud-server/Development/rocket-chat.html index 233aae2..edcd834 100644 --- a/docs/schulcloud-server/Development/rocket-chat.html +++ b/docs/schulcloud-server/Development/rocket-chat.html @@ -4,7 +4,7 @@ Rocket.Chat | Schulcloud-Verbund-Software Documentation - + @@ -12,7 +12,7 @@

    Rocket.Chat

    Start Mongodb

    It makes sense for Rocket.Chat to launch its own mongodb in Docker. Reason for this is Rocket.Chat requires Mongodb as replicaSet setup.

    docker run --name rocket-chat-mongodb -m=256m -p27030:27017 -d docker.io/mongo --replSet rs0 --oplogSize 10

    Start mongoDB console and execute

    rs.initiate({"_id" : "rs0", "members" : [{"_id" : 0, "host" : "localhost:27017"}]})

    Start rocketChat

    (check the latest settings https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/roles/rocketchat/templates/configmap.yml.j2#L9)

    Please not that the displayed //172.29.173.128 is the IP address of the mongoDB docker container. You can get the ip over the command: docker inspect rocket-chat-mongodb | grep "IPAddress" (dependent on our system)

    docker run\
    -e CREATE_TOKENS_FOR_USERS=true \
    -e MONGO_URL=mongodb://172.29.173.128:27030/rocketchat \
    -e ADMIN_PASS=huhu \
    -e API_Enable_Rate_Limiter_Limit_Calls_Default=255 \
    -e Accounts_iframe_enabled=true \
    -e Accounts_iframe_url=http://localhost:4000/rocketChat/Iframe \
    -e Accounts_Iframe_api_url=http://localhost:4000/rocketChat/authGet \
    -e Accounts_AllowRealNameChange=false \
    -e Accounts_AllowUsernameChange=false \
    -e Accounts_AllowEmailChange=false \
    -e Accounts_AllowAnonymousRead=false \
    -e Accounts_Send_Email_When_Activating=false \
    -e Accounts_Send_Email_When_Deactivating=false \
    -e Accounts_UseDefaultBlockedDomainsList=false \
    -e Analytics_features_messages=false \
    -e Analytics_features_rooms=false \
    -e Analytics_features_users=false \
    -e Statistics_reporting=false \
    -e API_Enable_CORS=true \
    -e Discussion_enabled=false \
    -e FileUpload_Enabled=false \
    -e UI_Use_Real_Name=true \
    -e Threads_enabled=false \
    -e Accounts_SetDefaultAvatar=false \
    -e Iframe_Restrict_Access=false \
    -e Accounts_Iframe_api_method=GET \
    -e OVERWRITE_SETTING_Show_Setup_Wizard='completed' \
    -p 3000:3000 docker.io/rocketchat/rocket.chat:4.7.2

    ENVS

    You must also configure you server and legacy client application. Use the .env file in top of the project folders.

    dBildungscloud Backend Server

    ROCKETCHAT_SERVICE_ENABLED=true
    ROCKET_CHAT_URI="http://localhost:3000"
    ROCKET_CHAT_ADMIN_USER=admin
    ROCKET_CHAT_ADMIN_PASSWORD=huhu

    dBildungscloud Legacy Client

    ROCKETCHAT_SERVICE_ENABLED=true
    ROCKET_CHAT_URI="http://localhost:3000"
    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Development/vs-code.html b/docs/schulcloud-server/Development/vs-code.html index bc1f1b1..fbcb52b 100644 --- a/docs/schulcloud-server/Development/vs-code.html +++ b/docs/schulcloud-server/Development/vs-code.html @@ -4,13 +4,13 @@ VSCode | Schulcloud-Verbund-Software Documentation - +

    VSCode

    Launch scripts

    In the file ./vscode/launch.default.json you find following actions:

    • Attach to NestJS will allow to attach VSCode debugger to an already running application
    • Deubg NestJS via NPM will start the application and attach the debugger

    Settings

    In the file ./vscode/settings.default.json find suggested settings.

    See ./vscode/extensions.json for recommendations.

    Jest

    Jest is used to care of unit- and end2end tests on all *.spec.ts files.

    Allows to just see failing tests in Problems tab.

    and get small icons like ✔️ or a cross beside it-definitions inside of test files.

    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Getting started.html b/docs/schulcloud-server/Getting started.html index f8b383c..d768a52 100644 --- a/docs/schulcloud-server/Getting started.html +++ b/docs/schulcloud-server/Getting started.html @@ -4,14 +4,14 @@ Getting started | Schulcloud-Verbund-Software Documentation - +

    Getting started

    Make sure to have the following software installed

    • node 18
    • MongoDb 4.2+
    • Redis
    • RabbitMQ
    git clone git@github.com:hpi-schul-cloud/schulcloud-server.git

    Install the packages

    cd schulcloud-server
    npm ci

    Start MongoDb, Redis & RabbitMQ. Then seed the database

    npm run setup

    The last step is to start the dev server with.

    npm run nest:start:dev
    - + \ No newline at end of file diff --git a/docs/schulcloud-server/Migrations.html b/docs/schulcloud-server/Migrations.html index eb548a6..09445d3 100644 --- a/docs/schulcloud-server/Migrations.html +++ b/docs/schulcloud-server/Migrations.html @@ -4,7 +4,7 @@ Migrations | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ or use npm run nest:start:console -- database --help to see how to import/export single collection
  • npx mikro-orm migration:up - to apply the migration update the migrations collection
  • npm run backup (copies the collections to folder backup/DATE, move the changed files of this folder to backup/setup and commit the updated files) or use npm run nest:start:console -- database export --collection <collection> --override to override seed data directly. The updated collections, modified by your migration added in backup/setup folder
  • Commit the changes:

    • git add .
    • git commit -m "migration: <migration name>"
    • git push
    • test that the migration was applied npx mikro-orm migrations:pending (or better use npm run migration:pending) should return nothing

    Best Practices

    General

    • consider if a migration is the right tool for this job
    • recurring tasks are better placed in a script/console
    • test the migration well
    • if a migration should be deleted, do not delete the migration file itself, but remove the content of the up and down method and log a skip message
    • consider if the migration can be written to be idempotent (can be executed multiple times without problems)

    Performance

    • think about the size of the collection. On production, it can be that the collection has a lot of data, and your migration might take a long time or can lead to time-out errors.
    • use methods already provided by mongo for bulk operations (e.g. updateMany, deleteMany etc.)
    • but think about handle items separately with extra logging and separate error handling
    • load the data chunkwise and process them asynchronously
    • query the data with skip and limit (example migration)
    • or use async iterators with mongoose queries (blog post, example migration)
    • migrations should in general not be executed in parallel (on multiple pods)
    • Use for loops with synchronous execution and logging. Avoid subtasks awaiting Promise.all()
    • Beside loading data in chunks or cursors, performance must not be an issue.

    Error Handling/Logging

    • if a migration throws an error, the subsequent migrations are not executed as well
    • catch errors if the errors are acceptable for the next release, so it will not become a release blocker
    • log start and end of a migration (so that we know which migration currently is running)
    • log intermediate results for long-running migrations (so that we know if it is still running)
    • use log level alert or above, so the output can be seen on production
    • logging might be difficult to fix, instead it might be helpful to keep before-states in a separate collection

    Debugging

    • use env MIKRO_ORM_CLI_VERBOSE=1
    • can add mikro-orm debug info in config file
      • debug: true will log all queries
      • logger: console.log will log all queries
    • can add debug breakpoints in migration files
    • run with typical debug options for your IDE (e.g. in Webstorm create a Run/Debug npm command and run it with debug options)

    Caveats

    using entity manager

    • According to documentation, the entity manager should not be used in migrations. Instead, the migration should use the mongoClient to access the database.

    Outdated Migrations

    • By their nature, migrations become outdated as the database model changes. You are never expected to update migrations due to any changes in the code that are made.
    • If needed, for example because the migration shows errors due to a code (model) change, migrations can be deleted, since they will still be accessible in the git history.
    - + \ No newline at end of file diff --git a/docs/schulcloud-server/architecture.html b/docs/schulcloud-server/architecture.html index c0b4114..69f1f21 100644 --- a/docs/schulcloud-server/architecture.html +++ b/docs/schulcloud-server/architecture.html @@ -4,7 +4,7 @@ Software Architecture | Schulcloud-Verbund-Software Documentation - + @@ -12,7 +12,7 @@

    Software Architecture

    Goals

    Our architecture aims to achieve the following goals:

    • Maintainability
      • it should be easy as possible to make changes that do not change the behaviour of the system (refactoring)
      • it should be easy to exchange entire components of the system, without impact on other components.
    • Extendability
      • it should be easy to add new functionality to the system
    • Agility
      • it should be easy to react to changing requirements during our development process
    • Change Security
    • it should be easy to determine the correctness of the system after making any changes

    Principles

    In order to achieve these goals, we try to follow the principles detailed below. These principles apply to all layers of our software, from lines of code and methods to modules and architectural layers.

    • Single Responsibility / Seperation of Concerns
      • each piece of code should have a single layer of abstraction/detail
      • each piece of code should have a single reason to change
    • Open/Closed Principle
      • design to be open to extension, but closed to modification
      • Liskov Substitution
      • the specific input may be more generic than its interface
      • the specific output may be more specialized than its interface
    • Interface Segregation
      • multiple small interfaces are preferred over big interfaces
    • Dependency Inversion Principle
      • always depend on interfaces, not implementations
      • higher level parts should not depend on lower level parts.
    • Keep It Simple (KISS)
      • any piece of code should be simple and readable
      • any logic should be broken down to be trivial
      • beware of overenginiering and premature optimisation
    • You Aint Gonna Need It (YAGNI)
      • keep decisions open for as long as possible
      • build only what you need to build, stay flexible for future requirements
    • Do Not Repeat Yourself (DRY)
      • do not solve the same responsability or concern in multiple places
      • beware of things that look similar, but are not. for example, things that change for different reasons should not be combined, even if their code looks the same

    Server Layer Architecture

    We generally distinguish three different layers in our server architecture: The API Layer, the Repository Layer, and the Domain Layer.

    Architecture Layers

    Note that based on the Dependency Inversion Principle, the Domain Layer does not have any dependencies. Instead, both the API and Repository Layer depend on its abstractions.

    Domain Layer

    The Domain Layer contains the business logic of the application. As mentioned above, it is not allowed to know about anything outside the domain layer itself.

    API Layer Dependencies

    Any operation within the system is defined by a usecase (UC). It describes how an external actor, for example a user, can interact with the system.

    Each usecase defines what needs to be done to authorize it, and what needs to be done to fulfill it. To this end, it orchestrates services.

    A service is a public part of a domain module, that provides an interface for logic. It might be a simple class doing simple calculations, an interface to a complex hierarchy of classes within a module, or anything in between.

    The domain layer might also define other classes, types, and interfaces to be used internally by its services, as well as the interface definitions for the repository layer. That way, the domain does not have to depend on the repositories, and the repositories have to depend on the domain instead (dependency inversion)

    TODO: the exact way of implementing the interfaces between repositories and domain layer is still in active discussion and development within the architecture chapter

    API Layer

    The API Layer is responsible for providing the API that is exposed outside the system, and to map the various incoming requests into domain DTOs.

    API Layer Dependencies

    The params.dto and response.dto are used to automatically generate the API Documentation based on openAPI. The params.dto also contains information that is used for input validation.

    The controller is responsible for sanitizing and authenticating incoming requests, and to map to and from the format that the domain usecase implementations expect. To this end, mappers are being used.

    Repository Layer

    The Repository Layer is responsible for outgoing requests to external services. The most prominent example is accessing the database, but the same principles apply for sending emails or other interactions with external systems.

    Repository Layer Dependencies

    In order to access these external systems without knowing them, the domain layer may define interfaces that describe how it would like to use external services in its own domain language. The repositories implement these interfaces, recieving and returning exclusively objects or dtos defined in the domain.

    The datamodel itself is defined through Entities, that have to be mapped into domain objects before they can be returned to the domain layer. We use MikroORM to create, persist and load the entities and their references among each other.

    Modules

    The codebase is broken into modules, each dealing with a part of the businesslogic, or seperated technical concerns. These modules define what code is available where, and ensure a clean dependency graph.

    All Code written should be part of exactly one module. Each module contains any services, typedefinitions, interfaces, repositories, mappers, and other files it needs internally to function.

    When something is needed in more than one module, it needs to be explicitly exported by the module, to be part of its public interface. It can then be imported by other modules. Services are exported published via the dependency injection mechanism provided by Nestjs.

    @Module({
    providers: [InternalRepo, InternalService, PublicService],
    exports: [PublicService],
    })
    export class ExampleModule {}

    @Module({
    imports: [ExampleModule]
    providers: [SomeOtherService],
    })
    export class OtherModule {}

    Notice that in the above example, the PublicService can be used anywhere within the OtherModule, including in the SomeOtherService, whereas the InternalRepo and InternalService can not.

    Things that cant be injectables, like types and interfaces, are exported via the index file at the root of the module.

    Code that needs to be shared across many modules can either be put into their own seperate module, if there is a clearly defined seperate concern covered by it, or into the shared module if not.

    Api Modules

    The controllers and the corresponding usecases, along with the api tests for these routes, are seperated into api modules

    @Module({
    imports: [ExampleModule]
    providers: [ExampleUc],
    controllers: [ExampleController],
    })
    export class ExampleApiModule {}

    This allows us to include the domain modules in different server deployments, without each of them having all api definitions. This also means that no usecase can ever be imported, as only services are ever exported, enforcing a seperation of concerns between logic and orchestration.

    Horizontal Architecture

    The application is split into different modules that implement different parts of our domain.

    The exact split of modules is still work in progress, or left open as implementation detail. Some important considerations are:

    • things with high cohesion and coupling should be in the same module
    • things with low coupling should be in seperate modules
    • the modules define an explicit public interface of usecases and types they expose to other modules
    • no module should ever try to access a class of a different module that is not explicitly exported
    • no injectable should ever be defined in more than one module
    • a module should only export services to be used by other modules.
    • a module that other modules might need to import, especially in another mikroservice, should not contain controllers.
    - + \ No newline at end of file diff --git a/docs/schulcloud-server/our nextjs modules/room-member-module.html b/docs/schulcloud-server/our nextjs modules/room-member-module.html index 0d9277f..1e6e467 100644 --- a/docs/schulcloud-server/our nextjs modules/room-member-module.html +++ b/docs/schulcloud-server/our nextjs modules/room-member-module.html @@ -4,7 +4,7 @@ Room Member Module | Schulcloud-Verbund-Software Documentation - + @@ -13,7 +13,7 @@ Why? => Because that allows us to assign each user a separate role within the room.
    e.g. ROOM_EDITOR, ROOM_VIEWER, ...

    These roles are not related to the user's global role.

    GroupEntity

    The userGroup property uses the GroupEntity from the Group module. This structure allows for multiple users to be associated with a room through a single group.

    class GroupEntity {
    id: EntityId;
    name: string;
    users: GroupUserEmbeddable[];
    // other properties...
    }

    GroupUserEmbeddable

    Each user in the group is represented by a GroupUserEmbeddable:

    class GroupUserEmbeddable {
    user: User;
    role: Role;
    }

    This structure allows for flexible assignment of roles to users within the context of a room.

    Key Points

    1. The RoomMemberEntity doesn't directly store user IDs or roles. Instead, it uses a GroupEntity to manage this information.
    2. This design allows for easy management of multiple users and roles for a single room.
    3. The roomId is stored directly on the RoomMemberEntity for efficient querying of members for a specific room.
    4. The domainObject property facilitates the separation between the database entity and the domain object. This is an optimization to not loose the unit of work using mikro-orm.

    This structure provides a flexible and scalable way to manage room memberships, allowing for complex permission and role scenarios within rooms.

    Service

    The RoomMemberService is a service for the RoomMember entity. It provides methods for creating, updating, and deleting RoomMember entities.

    class RoomMemberService {
    constructor(private readonly roomMembersRepo: RoomMemberRepo) {}
    }

    Usage

    The RoomMemberService is designed to be injected into the Room module for managing user access and roles within rooms.

    API

    There is no API for now. Member specific writes/reads can be implemented by adding an API to the RoomMember module. Like adding/removing users to a room.

    - + \ No newline at end of file diff --git a/docs/services/etherpad/How it works.html b/docs/services/etherpad/How it works.html index 4421592..a1c3d70 100644 --- a/docs/services/etherpad/How it works.html +++ b/docs/services/etherpad/How it works.html @@ -4,13 +4,13 @@ How it works | Schulcloud-Verbund-Software Documentation - +

    How it works

    Configuration

    • FEATURE_ETHERPAD_ENABLED - Used to enable etherpad feature in feathers backend

    • FEATURE_COLUMN_BOARD_COLLABORATIVE_TEXT_EDITOR_ENABLED - Enables etherpad feature on column boards

    • ETHERPAD__COOKIE_EXPIRES_SECONDS - Time in seconds after which a session expires

    • ETHERPAD__COOKIE_RELEASE_THRESHOLD - Time in seconds after which a session is not returned to the user

    • ETHERPAD__API_KEY - Api key used for authentication of schulcloud server requests

    • ETHERPAD__URI - Uri of etherpad api for all calls like create, delete etc. Used as base path for api client in nest and feathers backend.

    • ETHERPAD__PAD_URI - URI to etherpad client api. Used in backend in collaborative text editor and lesson to build return url. Used in legacy client to build url.

    • ETHERPAD_OLD_PAD_URI - Used in feathers backend to restrict access to old etherpad urls to lesson context. Only defined in default.schema.json and not in dof-app-deploy

    • ETHERPAD__PAD_PATH - Path to etherpad client api. Used in legacy client to set path on cookie.

    • ETHERPAD__NEW_DOMAIN - Etherpad Domain. Used in legacy client to validate url

    • ETHERPAD__OLD_DOMAIN - Old Etherpad Domain. Used in legacy client to identify old urls in lessons and transform them to urls with new domain.

    Creating and Opening an Etherpad Element on a Column Board

    Requests of etherpad creation

    Creating an Etherpad Element

    1. The user initiates the process by creating an Etherpad element on a column board.
    2. Vue then sends a request to the board module in the Schulcloud server for a new Etherpad element.
    3. The server responds by returning an Etherpad element which is then displayed on the board.

    Interacting with the Etherpad Element

    1. When the user clicks on the Etherpad element, the Vue client sends a request to the collaborative text editor module in the Schulcloud server for a new Etherpad.
    2. The Schulcloud server is capable of creating Etherpads based on parent types. The parent type is further used for handling authorisation of the etherpad. Currently, the only parent type is a column board element.

    Grouping and Creating Etherpads

    1. The Schulcloud server first creates a group that clusters several Etherpads together. Each group shares a session and a new group is created for every column board element. This requires sending a request to the Etherpad server.
    2. Once the group is created, the server can send a request to create an Etherpad within that group.
    1. For session creation, the server first needs to request an Etherpad author and then the session for that author.
    2. With that session, the server can set a session cookie in the client's browser and return the Etherpad URL to Vue.

    Opening the Etherpad

    In the final step, Vue opens the Etherpad URL in a new browser tab, enabling the user to interact directly with the Etherpad. It's important to note that the Etherpad client interface displayed to the user is served by the Etherpad server, and as such, it is not a part of our own codebase.

    Etherpad Adapter

    For the communication with the Etherpad server, the Schulcloud server uses an adapter. This adapter is responsible for handling all requests to the Etherpad server and ensuring that the correct data is sent and received. This adapter uses a client that is generated by open api generator. Client generation can be triggered with generate-client:etherpad and should be executed after update of etherpad server.

    Authentication

    The authentication process in our system works via a token. This token is sent with each request as a parameter to ensure secure communication.

    The token is defined by the ETHERPAD_API_KEY environment variable. This variable holds the key used for authentication, ensuring that only authorized requests are processed.

    In the Schulcloud server, this API key is passed to the etherpad adapter on its initialization in the collaborative text editor module.

    Session managment

    Etherpad Session Creation and Expiration

    Each interaction with an Etherpad element initiates the creation of a new session. The lifespan of this session is determined by the ETHERPAD_COOKIE__EXPIRES_SECONDS environment variable. Once this time period elapses, the user loses access to the pad. This loss of access is indicated by the display of a non-translated English message: "You do not have permission to access this pad." However, even after the session expires, users can still view the pad content as long as they do not interact with it.

    Session Reuse

    Upon subsequent interactions with the Etherpad element, an existing session for that element may be reused. Whether an old session is reused or a new one is created depends on the environment variable settings. The ETHERPAD_COOKIE_RELEASE_THRESHOLD variable determines the remaining validity period of a session for it to be delivered to the user. In the current production environment, both ETHERPAD_COOKIE_RELEASE_THRESHOLD and ETHERPAD_COOKIE__EXPIRES_SECONDS are set to the same value of 2 hours. This configuration results in the creation of a new session for each interaction with an Etherpad element.

    Session Renewal

    Currently there is no automatic session renewal upon interaction. Etherpad provides the COOKIE_SESSION_REFRESH_INTERVAL configuration variable, which specifies the time period after which a user's session is automatically renewed in an open tab. However, this variable is currently unset, and the default value of 1 day has no effect because it exceeds the ETHERPAD_COOKIE__EXPIRES_SECONDS value. Therefore, sessions are not automatically renewed.

    Session Removal

    When a user logs out of Schulcloud, all sessions are terminated, and the user loses access to the pads upon interaction. However, Etherpad sessions are not automatically removed upon user auto-logout in Schulcloud. As long as ETHERPAD_COOKIE__EXPIRES_SECONDS and JWT_TIMEOUT_SECONDS are set to the same value, all sessions should theoretically become invalid after auto-logout.

    A cookie named sessionID is stored for each session. These cookies are not programmatically removed after the ETHERPAD_COOKIE__EXPIRES_SECONDS period or upon Schulcloud logout.

    Legacy Topics

    Same session behavior also applies to legacy code. Env vars are used by both implementations.

    In contrast to the nest code in legacy code a session is created for every course and stored as a cookie for every etherpad.

    - + \ No newline at end of file diff --git a/docs/services/etherpad/Local setup.html b/docs/services/etherpad/Local setup.html index 96966c0..c284460 100644 --- a/docs/services/etherpad/Local setup.html +++ b/docs/services/etherpad/Local setup.html @@ -4,14 +4,14 @@ Local Setup | Schulcloud-Verbund-Software Documentation - +

    Local Setup

    Running the Etherpad server

    To run the Etherpad service for the local development and resting purposes you can follow the below steps:

    1. Create a new dir that will contain all the needed files that we'll want to use when running the Etherpad service. Create a directory called sc-etherpad and then enter it, in Unix-like systems you can use the following command:

      `mkdir sc-etherpad && cd sc-etherpad`
    2. Create a new file called APIKEY.txt in it, with the following content:

      381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd

      We'll use this file to set a fixed Etherpad's API key on the Etherpad server's start.

    3. Create also a file called settings.env with the following content:

      REQUIRE_SESSION="true"
      PAD_OPTIONS_SHOW_CHAT="true"
      DISABLE_IP_LOGGING="true"
      DEFAULT_PAD_TEXT="Schreib etwas!\n\nDieses Etherpad wird synchronisiert, während du tippst, so dass alle Betrachter jederzeit den selben Text sehen. So könnt ihr auf einfache Weise gemeinsam an Dokumenten arbeiten."
      DB_TYPE=mongodb
      DB_URL=mongodb://host.docker.internal:27017/etherpad

      We'll use this file to provide all the needed environment variables to the Etherpad's server.

      Please note the last line, that contains the MongoDB connection string:

      DB_URL=mongodb://host.docker.internal:27017/etherpad

      Here we're using the host.docker.internal hostname which should make it possible for the Etherpad's container to connect to the host's local network and should work out of the box e.g. on macOS. But please modify it accordingly to your needs and your Docker's network configuration. An alternative configuaration would be to use DB_URL=mongodb://localhost:27017/etherpad and than add --network="host" to the following docker run command.

    4. Next, start the Etherpad's container:

      docker run -d \
      -p 9001:9001 \
      --env-file ./settings.env \
      -v ./APIKEY.txt:/opt/etherpad-lite/APIKEY.txt \
      --name sc-etherpad \
      docker.io/etherpad/etherpad:2.0.0

      Please note we're using the docker.io/etherpad/etherpad:2.0.0 image in the command above which might be not the one that is being used anytime in the future when you read this article. To make sure you're using the current version (the one that is currently being used in the SchulCloud platform), please refer to the https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/group_vars/infra/dof_etherpad.yml file.

    Now we should have the Etherpad service running locally on port 9001, we can verify it's working properly e.g. by fetching the current Etherpad's API version:

    ~ curl -v http://127.0.0.1:9001/api
    * Trying 127.0.0.1:9001...
    * Connected to 127.0.0.1 (127.0.0.1) port 9001
    > GET /api HTTP/1.1
    > Host: 127.0.0.1:9001
    > User-Agent: curl/8.4.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < X-Powered-By: Express
    < X-UA-Compatible: IE=Edge,chrome=1
    < Referrer-Policy: same-origin
    < Content-Type: application/json; charset=utf-8
    < Content-Length: 26
    < ETag: W/"1a-2HmNLqF3wPt+KQRlEmGwH4O+xu4"
    < Date: Fri, 29 Mar 2024 13:27:00 GMT
    < Connection: keep-alive
    < Keep-Alive: timeout=5
    <
    * Connection #0 to host 127.0.0.1 left intact
    {"currentVersion":"1.3.0"}

    We can also verify that the API key has been set successfully, let's use the example API call from the Etherpad's documentation ( https://etherpad.org/doc/v2.0.0/#_example_1 ):

    ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\?apikey\=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd\&name\=Michael\&authorMapper\=7
    * Trying 127.0.0.1:9001...
    * Connected to 127.0.0.1 (127.0.0.1) port 9001
    > GET /api/1/createAuthorIfNotExistsFor?apikey=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd&name=Michael&authorMapper=7 HTTP/1.1
    > Host: 127.0.0.1:9001
    > User-Agent: curl/8.4.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < X-Powered-By: Express
    < X-UA-Compatible: IE=Edge,chrome=1
    < Referrer-Policy: same-origin
    < Content-Type: application/json; charset=utf-8
    < Content-Length: 66
    < ETag: W/"42-bg92QA1xRFO6QmkNRbNXhfsFBUM"
    < Date: Fri, 29 Mar 2024 13:40:05 GMT
    < Connection: keep-alive
    < Keep-Alive: timeout=5
    <
    * Connection #0 to host 127.0.0.1 left intact
    {"code":0,"message":"ok","data":{"authorID":"a.7BgkAuzbHXR1G8Cv"}}

    In case of an unsuccessful result (e.g. improperly set or invalid API key) we would receive the following response:

    ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\?apikey\=secret\&name\=Michael\&authorMapper\=7
    * Trying 127.0.0.1:9001...
    * Connected to 127.0.0.1 (127.0.0.1) port 9001
    > GET /api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 HTTP/1.1
    > Host: 127.0.0.1:9001
    > User-Agent: curl/8.4.0
    > Accept: */*
    >
    < HTTP/1.1 401 Unauthorized
    < X-Powered-By: Express
    < X-UA-Compatible: IE=Edge,chrome=1
    < Referrer-Policy: same-origin
    < Content-Type: application/json; charset=utf-8
    < Content-Length: 54
    < ETag: W/"36-dbJd0O+vdNi3zPpwRXE+1EGLTho"
    < Date: Fri, 29 Mar 2024 13:39:44 GMT
    < Connection: keep-alive
    < Keep-Alive: timeout=5
    <
    * Connection #0 to host 127.0.0.1 left intact
    {"code":4,"message":"no or wrong API Key","data":null}
    - + \ No newline at end of file diff --git a/docs/syncronizations/TSP/Architecture.html b/docs/syncronizations/TSP/Architecture.html index 91bec1d..03abe76 100644 --- a/docs/syncronizations/TSP/Architecture.html +++ b/docs/syncronizations/TSP/Architecture.html @@ -4,13 +4,13 @@ Architecture | Schulcloud-Verbund-Software Documentation - +

    Architecture

    TspSyncStrategy

    This strategy handles the sync of schools, students, teachers and classes from TSP and replaces both the legacy TspBaseSyncer and TspSchoolSyncer. It responds to the target "tsp".

    The flow looks like this (some of the logic for syncing is done in the provisioning strategy which is shared with the login):

    Flow of the TSP sync strategy

    - + \ No newline at end of file diff --git a/docs/syncronizations/TSP/How it works.html b/docs/syncronizations/TSP/How it works.html index aedfaba..e10a2b0 100644 --- a/docs/syncronizations/TSP/How it works.html +++ b/docs/syncronizations/TSP/How it works.html @@ -4,13 +4,13 @@ How it works | Schulcloud-Verbund-Software Documentation - +

    How it works

    Configuration

    • FEATURE_TSP_SYNC_ENABLED - Activates the sync strategy inside the sync console

    • WITH_TSP_SYNC - Activates the cronjob in Kubernetes

    • TSP_API_CLIENT_BASE_URL - Base URL for the TSP API

    • TSP_API_TOKEN_LIFETIME_MS - Lifetime of the access token for the TSP API in milliseconds

    • TSP_SYNC_SCHOOL_LIMIT - The amount of schools the sync handles at once

    • TSP_SYNC_SCHOOL_DAYS_TO_FETCH - The amount of days for which the sync fetches schools from the TSP API

    • TSP_SYNC_DATA_LIMIT - The amount of school data updates the sync handles at once

    • TSP_SYNC_DATA_DAYS_TO_FETCH - The amount of days for which the sync fetches school data from the TSP API

    • FEATURE_TSP_MIGRATION_ENABLED - Activates the migration of TSP users within the sync

    • TSP_SYNC_MIGRATION_LIMIT - The amount of users the sync migrates at once

    Sync console

    This is a console application that allows you to start the synchronization process for different sources.

    Usage

    To start the synchronization process, run the following command:

    npm run nest:start:console sync run <target>

    Where <target> is the name of the system you want to start the synchronization for. The currently available systems are:

    • tsp - Synchronize Thüringer Schulportal.

    If the target is not provided, the synchronization will not start and the available targets will be displayed in an error message.

    {
    message: 'Either synchronization is not activated or the target entered is invalid',
    data: { enteredTarget: 'tsp', availableTargets: { TSP: 'tsp' }}
    }

    TSP synchronization

    The TSP synchronization is controlled with the feature flag FEATURE_TSP_SYNC_ENABLED.

    - + \ No newline at end of file diff --git a/docs/tldraw-server/How it works.html b/docs/tldraw-server/How it works.html index e7fd18a..4a60f43 100644 --- a/docs/tldraw-server/How it works.html +++ b/docs/tldraw-server/How it works.html @@ -4,14 +4,14 @@ How it works | Schulcloud-Verbund-Software Documentation - +

    How it works

    The terms Redis and Valkey are used here synonymously and should describe the used in-memory-database.

    Configuration

    • AUTHORIZATION_API_HOST - host address of the authorization endpoint (schuldcloud-server)
    • FEATURE_TLDRAW_ENABLED - flag determining if tldraw is enabled
    • LOGGER_LOG_LEVEL - logging level
    • LOGGER_EXIT_ON_ERROR - flag whether an error will cause the application to stop
    • METRICS_COLLECT_DEFAULT - flag whether the default metrics shall be collected
    • REDIS_CLUSTER_ENABLED - flag whether a redis cluster or used or not
    • REDIS_URL - redis connection string
    • REDIS_SENTINEL_SERVICE_NAME - name of the redis sentinel service
    • REDIS_PREFIX - prefix to be used with redis database
    • REDIS_SENTINEL_NAME - name of the redis sentinel
    • REDIS_SENTINEL_PASSWORD - password for the redis sentinel
    • S3_ACCESS_KEY - access key for S3 storage
    • S3_BUCKET - name of the S3 bucket
    • S3_ENDPOINT - URL of the S3 service
    • S3_PORT - port number for the S3 service
    • S3_SECRET_KEY - secret key for S3 storage
    • S3_SSL - flag to enable or disable SSL for S3 storage
    • TLDRAW_ASSETS_ENABLED - enables uploading assets to tldraw board
    • TLDRAW_ASSETS_MAX_SIZE_BYTES - maximum asset size in bytes
    • TLDRAW_ASSETS_ALLOWED_MIME_TYPES_LIST - list of allowed assets MIME types
    • TLDRAW_WEBSOCKET_PATH - path for the tldraw websocket connection
    • TLDRAW_WEBSOCKET_URL - URL for the tldraw websocket connection
    • WORKER_MIN_MESSAGE_LIFETIME - minimal lifetime of a update message consumed by the worker
    • WORKER_TASK_DEBOUNCE - minimum idle time (in milliseconds) of the pending task messages to be claimed
    • WORKER_TRY_CLAIM_COUNT - the maximum number of task messages to claim
    • X_API_ALLOWED_KEYS - list of allowed xAPI keys

    In order to have deletion functionality fully working locally you have to fill those feature flags, e.g.:

    tldraw-server :

    • X_API_ALLOWED_KEYS="7ccd4e11-c6f6-48b0-81eb-abcdef123456"

    schulcloud-server :

    • TLDRAW_ADMIN_API_CLIENT_API_KEY="7ccd4e11-c6f6-48b0-81eb-abcdef123456"
    • TLDRAW_ADMIN_API_CLIENT_BASE_URL="http://localhost:3349"

    Create

    Create tldraw workflow

    The Tldraw board can be created by the user on the courses ColumnBoard. It has a representation in ColumnBoard as DrawingElement inside a card (BoardNode in db). After creating representation as DrawingElement we can enter actual Tldraw SPA client on click.

    1. User enters ColumnBoard and creates Representation of whiteboard (tldraw) in Card.
    2. Data is saved and feedback with proper creation is given - user can see Representation and can enter whiteboard.
    3. By entering whiteboard user is redirected to SPA tldraw-client.
    4. Tldraw-client is starting WS connection with tldraw-server.
    5. Tldraw-server first checks if user has permission to this resource (by checking if user has a permission to Representation of whiteboard - BoardNode). Id of Representation is same as drawingName, which is visible in tldraw-client url.
    6. If user has permission tldraw-server is allowing to remain connected and getting drawing data from S3 storage. If there is no drawing data available, the tldraw-server will create a new document automatically.

    Usage

    Usage tldraw workflow

    Connection

    1. User joins tldraw board.
    2. Tldraw-client connects to one of the tldraw-server pods and tries to establish websocket connection.
    3. Tldraw-server calls schulcloud-server via HTTP requests to check user permissions. If everything is fine, the websocket connection is established.
    4. Tldraw-server gets stored tldraw board data from S3 storage and sends it via websocket to connected user.
    5. Tldraw-server starts subscribing to Redis PUBSUB channel corresponding to tldraw board name to listen to changes from other pods.

    Sending updates/storing data

    1. Tldraw-client sends user's drawing changes to the tldraw-server via websocket connection.
    2. Tldraw-server stores the board update in the valkey db - basically creates a diff between what's already stored and what's being updated.
    3. Tldraw-server pushes the update to the boards Redis channel so that connected clients on different pods have synchronized board data.
    4. Other pods subscribing to Redis channel send updates to their connected clients via websocket whenever they see a new message on Redis channel.
    5. Finally the worker will run and persist the current state of the drawing data by applying all currently available updates from valkey on top of the stored drawing data and update the S3 storage accordingly.

    Delete

    Delete tldraw workflow

    1. User from schulcloud app in ColumnBoard deletes whiteboard (tldraw) instance form Card.
    2. Schulcloud-server is removing representation data in schulcloud-database - BoardNodes collection.
    3. Schulcloud-server is calling tldraw-server to delete all data that has given id.
    4. Tldraw-server sends a delete action via websocket to inform connected clients about deletion. Clients redirect away from Tldraw-board to ensure that no new messages are added to valkey database.
    5. Finally the worker will run, clear all updates and data from the valkey db and delete the drawing data from the S3 storage.

    Assets

    files upload

    Images/GIFs can be uploaded into tldraw whiteboard by every user with access to the board. We use SVS FileStorageService to physically store uploaded assets while tldraw only holds URL to a resource.

    The files are uploaded by calling schulcloud-api's fileController upload endpoint. This is possible because tldraw is represented as a boardnode called drawing-element. Mongo id of this drawing-element is a roomId used in URL param when connecting to a specific board.

    files deletion

    The deletion of files is handled directly by the tldraw-client itself. On deletion in the UI, the client sends a delete request to the file storage. While awaiting the answer from file storage the editing of the Tldraw-board is blocked to prevent race conditions to the file storage.

    - + \ No newline at end of file diff --git a/docs/tldraw-server/Local setup.html b/docs/tldraw-server/Local setup.html index a9d0680..900aea7 100644 --- a/docs/tldraw-server/Local setup.html +++ b/docs/tldraw-server/Local setup.html @@ -4,13 +4,13 @@ Local setup | Schulcloud-Verbund-Software Documentation - +

    Local setup

    To run tldraw locally:

    1. Run redis i.e. in a docker container, it will work on localhost:6379 by default which is what the REDIS_URI env var is set to, for example on wsl: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-windows/ .
    2. In the tldraw-server make a copy of .env.default and rename it to .env, in order to use the default configuration.
    3. To run
      1. npm run nest:start:dev (schulcloud-server)
      2. npm run nest:start:files-storage:dev (schulcloud-server with s3, if you want to upload files)
      3. npm run start:server:dev (tldraw-server)
      4. npm run start:worker:dev (tldraw-server)
      5. npm run dev (schulcloud-client)
      6. npm run servce (nuxt-client)
      7. npm run dev (tldraw-client)

    Create new whiteboard:

    1. Go to a course.
    2. Go to 'Column board'.
    3. Create a new card and a new 'Whiteboard' element within it, then click it.
    4. A new browser tab with URL like: http://localhost:4000/tldraw?roomName=65c37329b2f97cc714d31c00 will open.
    5. Change the port part from 4000 to 3046, which is the default port of tldraw-client app.
    6. You should see a working tldraw whiteboard now.
    - + \ No newline at end of file diff --git a/docs/tldraw-server/Technical details.html b/docs/tldraw-server/Technical details.html index 8d1b9ab..9c56dc6 100644 --- a/docs/tldraw-server/Technical details.html +++ b/docs/tldraw-server/Technical details.html @@ -4,13 +4,13 @@ Technical details | Schulcloud-Verbund-Software Documentation - +

    Technical details

    Introduction

    In tldraw-server, Yjs, Redis and S3 storage are integrated to support collaborative drawing features, data synchronization, and persistent storage in a scalable and efficient manner. The combination of Yjs (used for collaborative editing), Redis (for real-time data synchronization), and S3 (for file storage) enables the platform to handle complex collaborative interactions and large-scale, persistent storage of drawing data.

    How tldraw Uses Redis and S3 Storage:

    1. Real-time Collaboration with Yjs:
      • Yjs is the backbone of real-time collaboration in tldraw. It provides a CRDT-based (Conflict-free Replicated Data Type) framework for handling shared documents where changes made by one user are automatically and consistently synchronized across all other users in the same session.
      • In tldraw, the drawing canvas or document is represented as a Yjs document. Users can draw shapes, lines, text, or modify the canvas in real time.
      • The Yjs document tracks all changes in the form of small, incremental edits. These edits could be changes to the position of objects, the creation of new objects (e.g., shapes or lines), or modification of existing elements.

    . Redis for Real-Time Data Synchronization:

    • Redis is used to store and synchronize these Yjs documents across multiple users in real time.
    • Redis, being a fast in-memory key-value store, provides low-latency updates that are crucial for real-time collaboration. It is used for:
      • Broadcasting updates: When one user makes a change, Yjs sends that change to the Redis server, which then distributes the change to all other connected users.
      • Data persistence: Changes are stored in Redis and can be fetched by other users at any time to maintain consistency.
    • The use of Redis Pub/Sub allows different instances of the tldraw application to subscribe to channels. When one user makes a change, the Redis system publishes the change, and other users (who are subscribed to that document) get updated immediately.
    1. S3 Storage for Persistent and Large-Scale File Storage:

      • While Redis ensures real-time synchronization and collaboration, S3 (Amazon Simple Storage Service) is used for persistent storage of larger files or data that need to be saved across sessions.
      • S3 is highly scalable and can store large amounts of data. For tldraw, S3 is primarily used for:
        • Storing canvases and drawings: While Redis handles real-time data synchronization and storage of changes, the worker service is responsible for periodically persisting the state of the collaborative canvas to S3 storage.
      • S3 as a file store: Unlike Redis, which is an in-memory store designed for fast access and transient data, S3 is optimized for storing larger data in a persistent manner. This makes it suitable for storing media files, large canvas snapshots, and other assets that don't need to be constantly updated in real-time.
    2. Integration Between Redis and S3:

      • Redis and S3 serve different but complementary purposes:
        • Redis handles real-time synchronization of drawing changes and interactions, ensuring that users see each other's edits in near real-time.
        • S3 handles long-term storage and backup of the drawings or canvases themselves. For example, when a user closes the app or saves their session, the drawing data is saved to S3.
      • The worker service will save snapshots of the collaborative document or canvas to S3 periodically, ensuring that the state of the canvas is preserved even if a user disconnects or the server restarts.
    3. How tldraw's Workflow Would Look Using Redis and S3:

      • Step 1: Collaborative Drawing Session

        • Users interact with the tldraw canvas, making real-time changes (drawing shapes, text, etc.).
        • The changes are immediately propagated through Yjs and Redis. Redis stores these changes in memory and broadcasts them to other users.
        • Each user's drawing operations are synchronized, so everyone sees the same live canvas.
      • Step 2: Saving the Canvas

        • On a regular basis the worker service will send a snapshot of the canvas (or the entire Yjs document) to S3. This can include data about the shapes, their positions, colors, and any other relevant canvas data.
        • The snapshot can be stored as a JSON object, an image, or another format, depending on how tldraw chooses to serialize the data.
      • Step 3: Retrieving Saved Data

        • When a user returns to the drawing session or opens the application at a later time, the application queries S3 for the last saved canvas snapshot.
        • Once retrieved, the snapshot is loaded back into the application, allowing users to continue editing from where they left off.
        • Redis can be used in the background to ensure that any new changes made by users are synchronized in real time while they are working on the canvas.
    4. Scalability and Fault Tolerance:

      • Redis Scaling: As we are using Valkey, by default there will only be one primary Valkey instance and we will have two replica instances ready to jump in once the primary instance fails. But these two replica sets make sure, that Valkey is always available.
      • S3 Scaling: S3 is designed to scale automatically and handle large amounts of storage without performance degradation. This makes it ideal for storing large or numerous drawing assets, like high-resolution images or full snapshots of large canvases.
      • Tldraw-server Scaling: So far the number of pods the tldraw-server is deployed on is fixed and no load based scaling is applied so far.

    Example Scenario:

    • User A and User B start a collaborative session in tldraw, and they can see each other's updates in real time (thanks to Redis).
    • After some time, the worker service saves the drawing to S3, and now the drawing is stored in S3 as a persistent snapshot.
    • User B, who was not connected when the session ended, can later load the canvas from S3, where the most recent version is stored.
    • Meanwhile, as new users join the session, Redis continues to handle the real-time synchronization of the drawing, ensuring smooth interaction.

    Conclusion:

    In tldraw, Redis and S3 are integrated to deliver a collaborative and scalable experience. Redis ensures real-time synchronization of drawing changes among multiple users, while S3 provides persistent and scalable storage for canvas data. This combination allows tldraw to offer seamless collaboration, persistent storage, and fault-tolerant handling of large-scale data.

    Backend

    Deployments

    Tldraw is deployed as a separate application from schoulcloud-server and consists of the following deployments :

    • tldraw-server-deployment - deployment for tldraw-server's instances.
    • tldraw-worker-deployment - deployment for worker's instances.
    • tldraw-client-deployment - deployment for tldraw-client's instances.

    Tldraw-server code structure

    • tldraw-config.controller.ts - controller that exposes tldraw server configuration to be used by the tldraw client.
    • tldraw-document.controller.ts - controller that expose HTTP deletion method outside the tldraw-server application.
    • tldraw-document.service.ts - service used by TldrawDocumentController.
    • redis.service.ts - encapsulates the logic for creating and managing Redis instances, supporting both standalone and sentinel configurations, and integrates seamlessly with the NestJS framework.
    • ioredis.adapter.ts - encapsulates the logic for interacting with Redis, including defining custom commands and subscribing to channels. It leverages the ioredis library and integrates with the application's configuration and logging systems to provide a robust and flexible Redis adapter.
    • api.service.ts - API service for Redis.
    • ws.service.ts - Responsibe for Redis communication.
    • metrics.service.ts - service resonsible for storing application-level metrics.
    • worker.service.ts - responsible for persisting the current state of changed tldraw documents into the file storage.

    On the backend side we are also using Yjs library to store tldraw board in memory and to calculate diffs after the board is changed.

    Frontend

    Key Files

    • stores/setup.ts – this file provides a real-time collaboration environment for a drawing application using the WebSocket and Yjs libraries.
    • hooks/useMultiplayerState.ts – custom hook for managing multiplayer state.
    • App.tsx – main application component integrating Tldraw and multiplayer state.

    Frontend Technologies

    The frontend of the project is built using React and leverages various libraries and tools for enhanced functionality. Here is an overview of the key frontend technologies:

    • React: A JavaScript library for building user interfaces.
    • Yjs: A real-time collaboration framework for synchronizing shared state.
    • Tldraw: A library for drawing functionalities in the application. We use the old version of tldraw: https://github.com/tldraw/tldraw-v1, after the tldraw team releases the official update of the new version, we will work on the new version and integrate it with the needs of our users.

    State Managment

    1. Yjs is integrated into the project for real-time collaboration. The central state (shapes, bindings, assets) is managed using Yjs maps.
    2. store.ts handles the configuration of Yjs, WebSocket connections, and provides centralized maps for shapes, bindings, and assets
    3. useMultiplayerState.ts -This hook manages the multiplayer state, including loading rooms, handling file system operations, and updating Yjs maps:
      • Mounting and handling changes in Tldraw App.
      • Presence management and user updates.
      • File system operations like opening and saving projects.

    useTldrawUiSanitizer.ts

    This hook is designed to observe changes in the DOM, specifically targeting certain buttons and a horizontal rule (< hr>), and hides them if they match a specific ID pattern. We hide this elements and left just only Language and Keyboard shortcuts.

    Event Handling

    • onMount: Handles mounting of the Tldraw app.
    • onChangePage: Manages page changes and updates Yjs maps.
    • onUndo and onRedo: Handle undo and redo operations.
    • onChangePresence: Manages presence changes in the collaborative environment.
    • onAssetCreate: This function is triggered when a user attempts to upload an asset (like an image or a file).
    - + \ No newline at end of file diff --git a/index.html b/index.html index dc8d399..9d34f3e 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ Hello from Schulcloud-Verbund-Software Documentation | Schulcloud-Verbund-Software Documentation - +

    Schulcloud-Verbund-Software Documentation

    This documentation serves as a guide to understand the code style, best practices, and implementation details of the project.
    Whether you are a developer looking to contribute to the project, a team member aiming to understand the project structure, or simply curious about how things work behind the scenes, this documentation is here to help.

    - + \ No newline at end of file diff --git a/markdown-page.html b/markdown-page.html index b408f2c..ec4df82 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -4,13 +4,13 @@ Markdown page example | Schulcloud-Verbund-Software Documentation - + - + \ No newline at end of file diff --git a/search-index.json b/search-index.json index 1633680..477fa10 100644 --- a/search-index.json +++ b/search-index.json @@ -1 +1 @@ -[{"documents":[{"i":1,"t":"Code Conventions","u":"/docs/e2e-system-tests/CodeConventions","b":["Documentation","e2e-system-tests"]},{"i":22,"t":"Getting Started","u":"/docs/e2e-system-tests/GettingStarted","b":["Documentation","e2e-system-tests"]},{"i":34,"t":"Project Structure","u":"/docs/e2e-system-tests/ProjectStructure","b":["Documentation","e2e-system-tests"]},{"i":40,"t":"Tags","u":"/docs/e2e-system-tests/Tags","b":["Documentation","e2e-system-tests"]},{"i":46,"t":"Congratulations!","u":"/docs/How to update the docs/congratulations","b":["Documentation","How to update the docs"]},{"i":50,"t":"Create a Blog Post","u":"/docs/How to update the docs/create-a-blog-post","b":["Documentation","How to update the docs"]},{"i":54,"t":"Create a Document","u":"/docs/How to update the docs/create-a-document","b":["Documentation","How to update the docs"]},{"i":60,"t":"Create a Page","u":"/docs/How to update the docs/create-a-page","b":["Documentation","How to update the docs"]},{"i":66,"t":"Deploy your site","u":"/docs/How to update the docs/deploy-your-site","b":["Documentation","How to update the docs"]},{"i":71,"t":"Markdown Features","u":"/docs/How to update the docs/markdown-features","b":["Documentation","How to update the docs"]},{"i":85,"t":"Detect Dependency Cycles","u":"/docs/Informations/detect-dependency-cycles","b":["Documentation","Informations"]},{"i":95,"t":"Schulcloud Documentation","u":"/docs/intro","b":["Documentation"]},{"i":97,"t":"Accessibility (A11y)","u":"/docs/nuxt-client/Accessibility","b":["Documentation","nuxt-client"]},{"i":103,"t":"Code Conventions","u":"/docs/nuxt-client/CodeConventions","b":["Documentation","nuxt-client"]},{"i":111,"t":"Colors","u":"/docs/nuxt-client/Colors","b":["Documentation","nuxt-client"]},{"i":124,"t":"Component Development Guidelines","u":"/docs/nuxt-client/ComponentGuidelines","b":["Documentation","nuxt-client"]},{"i":134,"t":"Getting Started","u":"/docs/nuxt-client/GettingStarted","b":["Documentation","nuxt-client"]},{"i":145,"t":"Git Conventions","u":"/docs/nuxt-client/GitConventions","b":["Documentation","nuxt-client"]},{"i":149,"t":"Hints for Working","u":"/docs/nuxt-client/HintsForWorking","b":["Documentation","nuxt-client"]},{"i":152,"t":"How To","u":"/docs/nuxt-client/HowTo","b":["Documentation","nuxt-client"]},{"i":168,"t":"Identifying and Resolving Circular Dependencies","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","b":["Documentation","nuxt-client"]},{"i":175,"t":"Project Structure","u":"/docs/nuxt-client/ProjectStructure","b":["Documentation","nuxt-client"]},{"i":190,"t":"Writing Tests","u":"/docs/nuxt-client/WritingTests","b":["Documentation","nuxt-client"]},{"i":239,"t":"API design","u":"/docs/schulcloud-server/Api","b":["Documentation","schulcloud-server"]},{"i":244,"t":"Getting started","u":"/docs/schulcloud-client/Getting started","b":["Documentation","schulcloud-client"]},{"i":246,"t":"Access legacy Code","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":251,"t":"Controller","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":265,"t":"Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":273,"t":"Deprecation Workflow","u":"/docs/schulcloud-server/Coding-Guidelines/deprection-workflow","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":276,"t":"Domain Object Validation","u":"/docs/schulcloud-server/Coding-Guidelines/domain-object-validation","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":278,"t":"Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":288,"t":"Exception Handling","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":294,"t":"Logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":300,"t":"Defining Entities","u":"/docs/schulcloud-server/Coding-Guidelines/micro-orm","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":302,"t":"Implementation and usage of modules, submodule and barrel files in our project","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":310,"t":"Repositories","u":"/docs/schulcloud-server/Coding-Guidelines/repositories","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":312,"t":"Git","u":"/docs/schulcloud-server/Development/git","b":["Documentation","schulcloud-server","Development"]},{"i":319,"t":"Testing","u":"/docs/schulcloud-server/Coding-Guidelines/testing","b":["Documentation","schulcloud-server","Coding-Guidelines"]},{"i":355,"t":"ErWIn-IDM (Keycloak)","u":"/docs/schulcloud-server/Development/keycloak","b":["Documentation","schulcloud-server","Development"]},{"i":371,"t":"Rocket.Chat","u":"/docs/schulcloud-server/Development/rocket-chat","b":["Documentation","schulcloud-server","Development"]},{"i":382,"t":"VSCode","u":"/docs/schulcloud-server/Development/vs-code","b":["Documentation","schulcloud-server","Development"]},{"i":391,"t":"Getting started","u":"/docs/schulcloud-server/Getting started","b":["Documentation","schulcloud-server"]},{"i":393,"t":"Migrations","u":"/docs/schulcloud-server/Migrations","b":["Documentation","schulcloud-server"]},{"i":419,"t":"How it works","u":"/docs/services/etherpad/How it works","b":["Documentation","services","etherpad"]},{"i":450,"t":"Room Member Module","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","b":["Documentation","schulcloud-server","our nextjs modules"]},{"i":468,"t":"Architecture","u":"/docs/syncronizations/TSP/Architecture","b":["Documentation","syncronizations","TSP"]},{"i":471,"t":"Local Setup","u":"/docs/services/etherpad/Local setup","b":["Documentation","services","etherpad"]},{"i":474,"t":"How it works","u":"/docs/syncronizations/TSP/How it works","b":["Documentation","syncronizations","TSP"]},{"i":483,"t":"How it works","u":"/docs/tldraw-server/How it works","b":["Documentation","tldraw-server"]},{"i":501,"t":"Technical details","u":"/docs/tldraw-server/Technical details","b":["Documentation","tldraw-server"]},{"i":522,"t":"Local setup","u":"/docs/tldraw-server/Local setup","b":["Documentation","tldraw-server"]},{"i":527,"t":"Software Architecture","u":"/docs/schulcloud-server/architecture","b":["Documentation","schulcloud-server"]},{"i":546,"t":"Code Style","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","b":["Documentation","schulcloud-server","Coding-Guidelines"]}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/1",[0,2.446,1,2.693]],["t/22",[2,2.446,3,2.446]],["t/34",[4,2.693,5,3.024]],["t/40",[6,4.46]],["t/46",[7,4.46]],["t/50",[8,2.227,9,2.917,10,2.917]],["t/54",[8,2.693,11,3.024]],["t/60",[8,2.693,12,3.527]],["t/66",[13,3.527,14,3.527]],["t/71",[15,3.527,16,3.527]],["t/85",[17,2.917,18,2.501,19,2.917]],["t/95",[11,3.024,20,3.527]],["t/97",[21,3.024,22,3.527]],["t/103",[0,2.446,1,2.693]],["t/111",[23,4.46]],["t/124",[24,2.917,25,2.917,26,2.917]],["t/134",[2,2.446,3,2.446]],["t/145",[1,2.693,27,3.024]],["t/149",[28,3.527,29,2.446]],["t/152",[]],["t/168",[18,2.132,30,2.487,31,2.487,32,2.487]],["t/175",[4,2.693,5,3.024]],["t/190",[33,3.527,34,3.024]],["t/239",[35,3.527,36,3.527]],["t/244",[2,2.446,3,2.446]],["t/246",[0,2.023,21,2.501,37,2.917]],["t/251",[38,4.46]],["t/265",[39,4.46]],["t/273",[40,3.527,41,3.527]],["t/276",[42,2.917,43,2.917,44,2.917]],["t/278",[45,3.527,46,3.024]],["t/288",[46,3.024,47,3.527]],["t/294",[48,4.46]],["t/300",[49,3.527,50,3.527]],["t/302",[4,1.316,51,1.724,52,1.724,53,1.478,54,1.724,55,1.724,56,1.724]],["t/310",[57,4.46]],["t/312",[27,3.824]],["t/319",[34,3.824]],["t/355",[58,2.917,59,2.917,60,2.917]],["t/371",[61,4.46]],["t/382",[62,4.46]],["t/391",[2,2.446,3,2.446]],["t/393",[63,4.46]],["t/419",[29,3.093]],["t/450",[53,2.501,64,2.917,65,2.917]],["t/468",[66,3.824]],["t/471",[67,3.024,68,3.024]],["t/474",[29,3.093]],["t/483",[29,3.093]],["t/501",[69,3.527,70,3.527]],["t/522",[67,3.024,68,3.024]],["t/527",[66,3.024,71,3.527]],["t/546",[0,2.446,72,3.527]]],"invertedIndex":[["a11i",{"_index":22,"t":{"97":{"position":[[14,6]]}}}],["access",{"_index":21,"t":{"97":{"position":[[0,13]]},"246":{"position":[[0,6]]}}}],["api",{"_index":35,"t":{"239":{"position":[[0,3]]}}}],["architectur",{"_index":66,"t":{"468":{"position":[[0,12]]},"527":{"position":[[9,12]]}}}],["barrel",{"_index":55,"t":{"302":{"position":[[51,6]]}}}],["blog",{"_index":9,"t":{"50":{"position":[[9,4]]}}}],["circular",{"_index":32,"t":{"168":{"position":[[26,8]]}}}],["code",{"_index":0,"t":{"1":{"position":[[0,4]]},"103":{"position":[[0,4]]},"246":{"position":[[14,4]]},"546":{"position":[[0,4]]}}}],["color",{"_index":23,"t":{"111":{"position":[[0,6]]}}}],["compon",{"_index":24,"t":{"124":{"position":[[0,9]]}}}],["configur",{"_index":39,"t":{"265":{"position":[[0,13]]}}}],["congratul",{"_index":7,"t":{"46":{"position":[[0,16]]}}}],["control",{"_index":38,"t":{"251":{"position":[[0,10]]}}}],["convent",{"_index":1,"t":{"1":{"position":[[5,11]]},"103":{"position":[[5,11]]},"145":{"position":[[4,11]]}}}],["creat",{"_index":8,"t":{"50":{"position":[[0,6]]},"54":{"position":[[0,6]]},"60":{"position":[[0,6]]}}}],["cycl",{"_index":19,"t":{"85":{"position":[[18,6]]}}}],["defin",{"_index":49,"t":{"300":{"position":[[0,8]]}}}],["depend",{"_index":18,"t":{"85":{"position":[[7,10]]},"168":{"position":[[35,12]]}}}],["deploy",{"_index":13,"t":{"66":{"position":[[0,6]]}}}],["deprec",{"_index":40,"t":{"273":{"position":[[0,11]]}}}],["design",{"_index":36,"t":{"239":{"position":[[4,6]]}}}],["detail",{"_index":70,"t":{"501":{"position":[[10,7]]}}}],["detect",{"_index":17,"t":{"85":{"position":[[0,6]]}}}],["develop",{"_index":25,"t":{"124":{"position":[[10,11]]}}}],["document",{"_index":11,"t":{"54":{"position":[[9,8]]},"95":{"position":[[11,13]]}}}],["domain",{"_index":42,"t":{"276":{"position":[[0,6]]}}}],["entiti",{"_index":50,"t":{"300":{"position":[[9,8]]}}}],["erwin",{"_index":58,"t":{"355":{"position":[[0,5]]}}}],["event",{"_index":45,"t":{"278":{"position":[[0,5]]}}}],["except",{"_index":47,"t":{"288":{"position":[[0,9]]}}}],["featur",{"_index":16,"t":{"71":{"position":[[9,8]]}}}],["file",{"_index":56,"t":{"302":{"position":[[58,5]]}}}],["get",{"_index":2,"t":{"22":{"position":[[0,7]]},"134":{"position":[[0,7]]},"244":{"position":[[0,7]]},"391":{"position":[[0,7]]}}}],["git",{"_index":27,"t":{"145":{"position":[[0,3]]},"312":{"position":[[0,3]]}}}],["guidelin",{"_index":26,"t":{"124":{"position":[[22,10]]}}}],["handl",{"_index":46,"t":{"278":{"position":[[6,8]]},"288":{"position":[[10,8]]}}}],["hint",{"_index":28,"t":{"149":{"position":[[0,5]]}}}],["identifi",{"_index":30,"t":{"168":{"position":[[0,11]]}}}],["idm",{"_index":59,"t":{"355":{"position":[[6,3]]}}}],["implement",{"_index":51,"t":{"302":{"position":[[0,14]]}}}],["keycloak",{"_index":60,"t":{"355":{"position":[[10,10]]}}}],["legaci",{"_index":37,"t":{"246":{"position":[[7,6]]}}}],["local",{"_index":67,"t":{"471":{"position":[[0,5]]},"522":{"position":[[0,5]]}}}],["log",{"_index":48,"t":{"294":{"position":[[0,7]]}}}],["markdown",{"_index":15,"t":{"71":{"position":[[0,8]]}}}],["member",{"_index":65,"t":{"450":{"position":[[5,6]]}}}],["migrat",{"_index":63,"t":{"393":{"position":[[0,10]]}}}],["modul",{"_index":53,"t":{"302":{"position":[[28,8]]},"450":{"position":[[12,6]]}}}],["object",{"_index":43,"t":{"276":{"position":[[7,6]]}}}],["page",{"_index":12,"t":{"60":{"position":[[9,4]]}}}],["post",{"_index":10,"t":{"50":{"position":[[14,4]]}}}],["project",{"_index":4,"t":{"34":{"position":[[0,7]]},"175":{"position":[[0,7]]},"302":{"position":[[71,7]]}}}],["repositori",{"_index":57,"t":{"310":{"position":[[0,12]]}}}],["resolv",{"_index":31,"t":{"168":{"position":[[16,9]]}}}],["rocket.chat",{"_index":61,"t":{"371":{"position":[[0,11]]}}}],["room",{"_index":64,"t":{"450":{"position":[[0,4]]}}}],["schulcloud",{"_index":20,"t":{"95":{"position":[[0,10]]}}}],["setup",{"_index":68,"t":{"471":{"position":[[6,5]]},"522":{"position":[[6,5]]}}}],["site",{"_index":14,"t":{"66":{"position":[[12,4]]}}}],["softwar",{"_index":71,"t":{"527":{"position":[[0,8]]}}}],["start",{"_index":3,"t":{"22":{"position":[[8,7]]},"134":{"position":[[8,7]]},"244":{"position":[[8,7]]},"391":{"position":[[8,7]]}}}],["structur",{"_index":5,"t":{"34":{"position":[[8,9]]},"175":{"position":[[8,9]]}}}],["style",{"_index":72,"t":{"546":{"position":[[5,5]]}}}],["submodul",{"_index":54,"t":{"302":{"position":[[37,9]]}}}],["tag",{"_index":6,"t":{"40":{"position":[[0,4]]}}}],["technic",{"_index":69,"t":{"501":{"position":[[0,9]]}}}],["test",{"_index":34,"t":{"190":{"position":[[8,5]]},"319":{"position":[[0,7]]}}}],["usag",{"_index":52,"t":{"302":{"position":[[19,5]]}}}],["valid",{"_index":44,"t":{"276":{"position":[[14,10]]}}}],["vscode",{"_index":62,"t":{"382":{"position":[[0,6]]}}}],["work",{"_index":29,"t":{"149":{"position":[[10,7]]},"419":{"position":[[7,5]]},"474":{"position":[[7,5]]},"483":{"position":[[7,5]]}}}],["workflow",{"_index":41,"t":{"273":{"position":[[12,8]]}}}],["write",{"_index":33,"t":{"190":{"position":[[0,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":3,"t":"1. Writing Feature Files","u":"/docs/e2e-system-tests/CodeConventions","h":"#1-writing-feature-files","p":1},{"i":4,"t":"Template Structure","u":"/docs/e2e-system-tests/CodeConventions","h":"#template-structure","p":1},{"i":6,"t":"Guidelines","u":"/docs/e2e-system-tests/CodeConventions","h":"#guidelines","p":1},{"i":8,"t":"2. Using Parameters in Feature Files","u":"/docs/e2e-system-tests/CodeConventions","h":"#2-using-parameters-in-feature-files","p":1},{"i":10,"t":"3. Using Parameters for Special Test Data","u":"/docs/e2e-system-tests/CodeConventions","h":"#3-using-parameters-for-special-test-data","p":1},{"i":12,"t":"4. Writing Step Definitions for Scenario Outlines","u":"/docs/e2e-system-tests/CodeConventions","h":"#4-writing-step-definitions-for-scenario-outlines","p":1},{"i":14,"t":"5. Naming Conventions","u":"/docs/e2e-system-tests/CodeConventions","h":"#5-naming-conventions","p":1},{"i":16,"t":"6. Writing Classes and Methods","u":"/docs/e2e-system-tests/CodeConventions","h":"#6-writing-classes-and-methods","p":1},{"i":18,"t":"7. Writing Step Definitions","u":"/docs/e2e-system-tests/CodeConventions","h":"#7-writing-step-definitions","p":1},{"i":20,"t":"8. Additional Best Practices","u":"/docs/e2e-system-tests/CodeConventions","h":"#8-additional-best-practices","p":1},{"i":24,"t":"1. Pre-requisites","u":"/docs/e2e-system-tests/GettingStarted","h":"#1-pre-requisites","p":22},{"i":26,"t":"2. Cloning the Repository","u":"/docs/e2e-system-tests/GettingStarted","h":"#2-cloning-the-repository","p":22},{"i":28,"t":"3. Setting Up Environment Variables for the Testing User Credentials and URLs","u":"/docs/e2e-system-tests/GettingStarted","h":"#3-setting-up-environment-variables-for-the-testing-user-credentials-and-urls","p":22},{"i":30,"t":"4. Installing Dependencies","u":"/docs/e2e-system-tests/GettingStarted","h":"#4-installing-dependencies","p":22},{"i":32,"t":"5. Running Cypress Tests","u":"/docs/e2e-system-tests/GettingStarted","h":"#5-running-cypress-tests","p":22},{"i":36,"t":"Project Directory Layout","u":"/docs/e2e-system-tests/ProjectStructure","h":"#project-directory-layout","p":34},{"i":38,"t":"Explanation of Key Directories and Files","u":"/docs/e2e-system-tests/ProjectStructure","h":"#explanation-of-key-directories-and-files","p":34},{"i":42,"t":"Tag Descriptions","u":"/docs/e2e-system-tests/Tags","h":"#tag-descriptions","p":40},{"i":44,"t":"Tag Hierarchy","u":"/docs/e2e-system-tests/Tags","h":"#tag-hierarchy","p":40},{"i":48,"t":"What's next?","u":"/docs/How to update the docs/congratulations","h":"#whats-next","p":46},{"i":52,"t":"Create your first Post","u":"/docs/How to update the docs/create-a-blog-post","h":"#create-your-first-post","p":50},{"i":56,"t":"Create your first Doc","u":"/docs/How to update the docs/create-a-document","h":"#create-your-first-doc","p":54},{"i":58,"t":"Configure the Sidebar","u":"/docs/How to update the docs/create-a-document","h":"#configure-the-sidebar","p":54},{"i":62,"t":"Create your first React Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-react-page","p":60},{"i":64,"t":"Create your first Markdown Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-markdown-page","p":60},{"i":68,"t":"Build your site","u":"/docs/How to update the docs/deploy-your-site","h":"#build-your-site","p":66},{"i":73,"t":"Front Matter","u":"/docs/How to update the docs/markdown-features","h":"#front-matter","p":71},{"i":75,"t":"Links","u":"/docs/How to update the docs/markdown-features","h":"#links","p":71},{"i":77,"t":"Images","u":"/docs/How to update the docs/markdown-features","h":"#images","p":71},{"i":79,"t":"Code Blocks","u":"/docs/How to update the docs/markdown-features","h":"#code-blocks","p":71},{"i":81,"t":"Admonitions","u":"/docs/How to update the docs/markdown-features","h":"#admonitions","p":71},{"i":83,"t":"MDX and React Components","u":"/docs/How to update the docs/markdown-features","h":"#mdx-and-react-components","p":71},{"i":87,"t":"text export","u":"/docs/Informations/detect-dependency-cycles","h":"#text-export","p":85},{"i":89,"t":"image export (Ubuntu/Wsl)","u":"/docs/Informations/detect-dependency-cycles","h":"#image-export-ubuntuwsl","p":85},{"i":91,"t":"examples","u":"/docs/Informations/detect-dependency-cycles","h":"#examples","p":85},{"i":93,"t":"more solutions","u":"/docs/Informations/detect-dependency-cycles","h":"#more-solutions","p":85},{"i":99,"t":"W3C Web Accessibility Initiative (WAI)","u":"/docs/nuxt-client/Accessibility","h":"#w3c-web-accessibility-initiative-wai","p":97},{"i":101,"t":"Vuetify and Vue","u":"/docs/nuxt-client/Accessibility","h":"#vuetify-and-vue","p":97},{"i":105,"t":"data-testids","u":"/docs/nuxt-client/CodeConventions","h":"#data-testids","p":103},{"i":107,"t":"ts-ignore-comments","u":"/docs/nuxt-client/CodeConventions","h":"#ts-ignore-comments","p":103},{"i":109,"t":"composables","u":"/docs/nuxt-client/CodeConventions","h":"#composables","p":103},{"i":113,"t":"Usage","u":"/docs/nuxt-client/Colors","h":"#usage","p":111},{"i":114,"t":"Color Classes","u":"/docs/nuxt-client/Colors","h":"#color-classes","p":111},{"i":116,"t":"Use Colors in (S)CSS","u":"/docs/nuxt-client/Colors","h":"#use-colors-in-scss","p":111},{"i":118,"t":"\"On-Surface\" and \"On-Background\" Colors","u":"/docs/nuxt-client/Colors","h":"#on-surface-and-on-background-colors","p":111},{"i":120,"t":"Definition Of Custom Colors","u":"/docs/nuxt-client/Colors","h":"#definition-of-custom-colors","p":111},{"i":122,"t":"Rules","u":"/docs/nuxt-client/Colors","h":"#rules","p":111},{"i":125,"t":"HTML is not a string","u":"/docs/nuxt-client/ComponentGuidelines","h":"#html-is-not-a-string","p":124},{"i":127,"t":"Composition over Configuration","u":"/docs/nuxt-client/ComponentGuidelines","h":"#composition-over-configuration","p":124},{"i":128,"t":"Using slots for highly flexible ui components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#using-slots","p":124},{"i":130,"t":"Destructure data over multiple components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#destructure-data-in-component-trees","p":124},{"i":132,"t":"Naming components in destructured component trees","u":"/docs/nuxt-client/ComponentGuidelines","h":"#naming-in-destructured-trees","p":124},{"i":135,"t":"Development Setup","u":"/docs/nuxt-client/GettingStarted","h":"#development-setup","p":134},{"i":137,"t":"Start the Server","u":"/docs/nuxt-client/GettingStarted","h":"#start-the-server","p":134},{"i":139,"t":"Unit Tests","u":"/docs/nuxt-client/GettingStarted","h":"#unit-tests","p":134},{"i":141,"t":"Lint","u":"/docs/nuxt-client/GettingStarted","h":"#lint","p":134},{"i":143,"t":"Editor Setup","u":"/docs/nuxt-client/GettingStarted","h":"#editor-setup","p":134},{"i":147,"t":"Pull Requests","u":"/docs/nuxt-client/GitConventions","h":"#pull-requests","p":145},{"i":150,"t":"Working with Material Design Icons","u":"/docs/nuxt-client/HintsForWorking","h":"#working-with-material-design-icons","p":149},{"i":154,"t":"Feature Flags","u":"/docs/nuxt-client/HowTo","h":"#feature-flags-","p":152},{"i":156,"t":"Using generated API and it's types","u":"/docs/nuxt-client/HowTo","h":"#using-generated-api-and-its-types-","p":152},{"i":158,"t":"Regenerating the clients","u":"/docs/nuxt-client/HowTo","h":"#regenerating-the-clients","p":152},{"i":160,"t":"Using the generated api","u":"/docs/nuxt-client/HowTo","h":"#using-the-generated-api","p":152},{"i":162,"t":"User-Permissions on Pages","u":"/docs/nuxt-client/HowTo","h":"#user-permissions-on-pages-","p":152},{"i":164,"t":"Exception handling","u":"/docs/nuxt-client/HowTo","h":"#exception-handling-","p":152},{"i":166,"t":"inject - fallback throwing an error","u":"/docs/nuxt-client/HowTo","h":"#inject---fallback-throwing-an-error-","p":152},{"i":169,"t":"What is a circular dependency?","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#what-is-a-circular-dependency","p":168},{"i":171,"t":"Resolving Circular Dependencies","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#resolving-circular-dependencies","p":168},{"i":173,"t":"How to identify Circular Dependencies in Vue","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#how-to-identify-circular-dependencies-in-vue","p":168},{"i":176,"t":"Filenames","u":"/docs/nuxt-client/ProjectStructure","h":"#filenames","p":175},{"i":178,"t":"Folders","u":"/docs/nuxt-client/ProjectStructure","h":"#folders","p":175},{"i":180,"t":"Building Blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#building-blocks","p":175},{"i":182,"t":"What is a building-block?","u":"/docs/nuxt-client/ProjectStructure","h":"#what-is-a-building-block","p":175},{"i":184,"t":"Types of building-blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#types-of-building-blocks","p":175},{"i":186,"t":"Life of a feature module","u":"/docs/nuxt-client/ProjectStructure","h":"#life-of-a-feature-module","p":175},{"i":188,"t":"How to pick the correct type for my Task","u":"/docs/nuxt-client/ProjectStructure","h":"#how-to-pick-the-correct-type-for-my-task","p":175},{"i":192,"t":"Basics","u":"/docs/nuxt-client/WritingTests","h":"#basics","p":190},{"i":194,"t":"Unit-Tests vs. Component-Tests","u":"/docs/nuxt-client/WritingTests","h":"#unit-tests-vs-component-tests","p":190},{"i":196,"t":"Positive & negative Tests","u":"/docs/nuxt-client/WritingTests","h":"#positive--negative-tests","p":190},{"i":198,"t":"Use Vue-Test-Utils","u":"/docs/nuxt-client/WritingTests","h":"#use-vue-test-utils","p":190},{"i":200,"t":"Use TypeScript","u":"/docs/nuxt-client/WritingTests","h":"#use-typescript","p":190},{"i":202,"t":"Name your tests like your components","u":"/docs/nuxt-client/WritingTests","h":"#name-your-tests-like-your-components","p":190},{"i":204,"t":"Structure your tests using (multiple) \"describe\"-blocks","u":"/docs/nuxt-client/WritingTests","h":"#structure-your-tests-using-multiple-describe-blocks","p":190},{"i":206,"t":"Name the test like a sentence \"it should...\"","u":"/docs/nuxt-client/WritingTests","h":"#name-the-test-like-a-sentence-it-should","p":190},{"i":208,"t":"data-testids","u":"/docs/nuxt-client/WritingTests","h":"#data-testids","p":190},{"i":210,"t":"Setup-methods","u":"/docs/nuxt-client/WritingTests","h":"#setup-methods","p":190},{"i":212,"t":"Testing","u":"/docs/nuxt-client/WritingTests","h":"#testing","p":190},{"i":213,"t":"Events","u":"/docs/nuxt-client/WritingTests","h":"#events","p":190},{"i":215,"t":"Testing Asynchronous Behavior","u":"/docs/nuxt-client/WritingTests","h":"#testing-asynchronous-behavior","p":190},{"i":217,"t":"Exceptions","u":"/docs/nuxt-client/WritingTests","h":"#exceptions","p":190},{"i":219,"t":"console.error","u":"/docs/nuxt-client/WritingTests","h":"#consoleerror","p":190},{"i":221,"t":"Testing Composables","u":"/docs/nuxt-client/WritingTests","h":"#testing-composables","p":190},{"i":223,"t":"Mocking","u":"/docs/nuxt-client/WritingTests","h":"#mocking","p":190},{"i":225,"t":"Mocking injections","u":"/docs/nuxt-client/WritingTests","h":"#mocking-injections","p":190},{"i":227,"t":"Mocking Vuex-Store","u":"/docs/nuxt-client/WritingTests","h":"#mocking-vuex-store","p":190},{"i":229,"t":"Mocking Pinia-Stores","u":"/docs/nuxt-client/WritingTests","h":"#mocking-pinia-stores","p":190},{"i":231,"t":"Mocking Composables","u":"/docs/nuxt-client/WritingTests","h":"#mocking-composables","p":190},{"i":233,"t":"Components that are hard to test","u":"/docs/nuxt-client/WritingTests","h":"#components-that-are-hard-to-test","p":190},{"i":235,"t":"End-To-End-Tests","u":"/docs/nuxt-client/WritingTests","h":"#end-to-end-tests","p":190},{"i":237,"t":"Code-Coverage","u":"/docs/nuxt-client/WritingTests","h":"#code-coverage","p":190},{"i":240,"t":"nest.js","u":"/docs/schulcloud-server/Api","h":"#nestjs","p":239},{"i":242,"t":"Responses","u":"/docs/schulcloud-server/Api","h":"#responses","p":239},{"i":247,"t":"Access Feathers Service from NestJS","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-feathers-service-from-nestjs","p":246},{"i":249,"t":"Access NestJS injectable from Feathers","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-nestjs-injectable-from-feathers","p":246},{"i":253,"t":"JWT-Authentication","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#jwt-authentication","p":251},{"i":255,"t":"Validation","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#validation","p":251},{"i":257,"t":"DTOs","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dtos","p":251},{"i":259,"t":"DTO File naming","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dto-file-naming","p":251},{"i":261,"t":"openAPI specification","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#openapi-specification","p":251},{"i":263,"t":"Mapping","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#mapping","p":251},{"i":267,"t":"Application Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#application-configuration","p":265},{"i":269,"t":"Config Maps","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#config-maps","p":265},{"i":271,"t":"dof Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#dof-configuration","p":265},{"i":274,"t":"When to use 2 step migration","u":"/docs/schulcloud-server/Coding-Guidelines/deprection-workflow","h":"#when-to-use-2-step-migration","p":273},{"i":280,"t":"How to implement Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#how-to-implement-event-handling","p":278},{"i":282,"t":"Defining an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#defining-an-event","p":278},{"i":284,"t":"Sending an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#sending-an-event","p":278},{"i":286,"t":"Recieving an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#recieving-an-event","p":278},{"i":290,"t":"Chaining errors with the cause property","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#chaining-errors-with-the-cause-property","p":288},{"i":292,"t":"Loggable exceptions","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#loggable-exceptions","p":288},{"i":296,"t":"Log levels and error logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#log-levels-and-error-logging","p":294},{"i":298,"t":"Legacy logger","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#legacy-logger","p":294},{"i":304,"t":"Modules and Submodules","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#modules-and-submodules","p":302},{"i":306,"t":"Barrel Files","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#barrel-files","p":302},{"i":308,"t":"Handling Circular Dependencies","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#handling-circular-dependencies","p":302},{"i":313,"t":"Branch name conventions","u":"/docs/schulcloud-server/Development/git","h":"#branch-name-conventions","p":312},{"i":315,"t":"Commit message conventions","u":"/docs/schulcloud-server/Development/git","h":"#commit-message-conventions","p":312},{"i":317,"t":"Windows","u":"/docs/schulcloud-server/Development/git","h":"#windows","p":312},{"i":321,"t":"General Test Conventions","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#general-test-conventions","p":319},{"i":322,"t":"Lean Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#lean-tests","p":319},{"i":324,"t":"Naming Convention","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#naming-convention","p":319},{"i":326,"t":"Isolation","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#isolation","p":319},{"i":328,"t":"Test Structure","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#test-structure","p":319},{"i":330,"t":"Testing Samples","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#testing-samples","p":319},{"i":331,"t":"Handling of function return values","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#handling-of-function-return-values","p":319},{"i":333,"t":"Promises and Timouts in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#promises-and-timouts-in-tests","p":319},{"i":335,"t":"Expecting errors in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#expecting-errors-in-tests","p":319},{"i":337,"t":"Testing Utilities","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#testing-utilities","p":319},{"i":339,"t":"Mocking","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mocking","p":319},{"i":341,"t":"Unit Tests vs Integration Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#unit-tests-vs-integration-tests","p":319},{"i":343,"t":"Repo Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#repo-tests","p":319},{"i":345,"t":"Mapping Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mapping-tests","p":319},{"i":347,"t":"Use Case Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#use-case-tests","p":319},{"i":349,"t":"Controller Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#controller-tests","p":319},{"i":351,"t":"API Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#api-tests","p":319},{"i":353,"t":"References","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#references","p":319},{"i":357,"t":"Docker","u":"/docs/schulcloud-server/Development/keycloak","h":"#docker","p":355},{"i":359,"t":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering","p":355},{"i":361,"t":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering with LDAP provisioning","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering-with-ldap-provisioning","p":355},{"i":363,"t":"Test local environment","u":"/docs/schulcloud-server/Development/keycloak","h":"#test-local-environment","p":355},{"i":365,"t":"Seeding Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#seeding-data","p":355},{"i":367,"t":"Updating Seed Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#updating-seed-data","p":355},{"i":369,"t":"NPM Commands","u":"/docs/schulcloud-server/Development/keycloak","h":"#npm-commands","p":355},{"i":372,"t":"Start Mongodb","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-mongodb","p":371},{"i":374,"t":"Start rocketChat","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-rocketchat","p":371},{"i":376,"t":"ENVS","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#envs","p":371},{"i":378,"t":"dBildungscloud Backend Server","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-backend-server","p":371},{"i":380,"t":"dBildungscloud Legacy Client","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-legacy-client","p":371},{"i":383,"t":"Launch scripts","u":"/docs/schulcloud-server/Development/vs-code","h":"#launch-scripts","p":382},{"i":385,"t":"Settings","u":"/docs/schulcloud-server/Development/vs-code","h":"#settings","p":382},{"i":387,"t":"Recommended extensions","u":"/docs/schulcloud-server/Development/vs-code","h":"#recommended-extensions","p":382},{"i":389,"t":"Jest","u":"/docs/schulcloud-server/Development/vs-code","h":"#jest","p":382},{"i":394,"t":"Migrations for server database","u":"/docs/schulcloud-server/Migrations","h":"#migrations-for-server-database","p":393},{"i":396,"t":"Create a new migration","u":"/docs/schulcloud-server/Migrations","h":"#create-a-new-migration","p":393},{"i":398,"t":"Apply migrations","u":"/docs/schulcloud-server/Migrations","h":"#apply-migrations","p":393},{"i":400,"t":"Undo migration","u":"/docs/schulcloud-server/Migrations","h":"#undo-migration","p":393},{"i":402,"t":"Notes about setup","u":"/docs/schulcloud-server/Migrations","h":"#notes-about-setup","p":393},{"i":404,"t":"Test migration","u":"/docs/schulcloud-server/Migrations","h":"#test-migration","p":393},{"i":406,"t":"Committing a migration","u":"/docs/schulcloud-server/Migrations","h":"#committing-a-migration","p":393},{"i":408,"t":"Best Practices","u":"/docs/schulcloud-server/Migrations","h":"#best-practices","p":393},{"i":409,"t":"General","u":"/docs/schulcloud-server/Migrations","h":"#general","p":393},{"i":411,"t":"Performance","u":"/docs/schulcloud-server/Migrations","h":"#performance","p":393},{"i":413,"t":"Error Handling/Logging","u":"/docs/schulcloud-server/Migrations","h":"#error-handlinglogging","p":393},{"i":415,"t":"Debugging","u":"/docs/schulcloud-server/Migrations","h":"#debugging","p":393},{"i":417,"t":"Caveats","u":"/docs/schulcloud-server/Migrations","h":"#caveats","p":393},{"i":420,"t":"Configuration","u":"/docs/services/etherpad/How it works","h":"#configuration","p":419},{"i":422,"t":"Creating and Opening an Etherpad Element on a Column Board","u":"/docs/services/etherpad/How it works","h":"#creating-and-opening-an-etherpad-element-on-a-column-board","p":419},{"i":423,"t":"Creating an Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#creating-an-etherpad-element","p":419},{"i":425,"t":"Interacting with the Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#interacting-with-the-etherpad-element","p":419},{"i":427,"t":"Grouping and Creating Etherpads","u":"/docs/services/etherpad/How it works","h":"#grouping-and-creating-etherpads","p":419},{"i":429,"t":"Session Creation and Cookie Setting","u":"/docs/services/etherpad/How it works","h":"#session-creation-and-cookie-setting","p":419},{"i":431,"t":"Opening the Etherpad","u":"/docs/services/etherpad/How it works","h":"#opening-the-etherpad","p":419},{"i":433,"t":"Etherpad Adapter","u":"/docs/services/etherpad/How it works","h":"#etherpad-adapter","p":419},{"i":435,"t":"Authentication","u":"/docs/services/etherpad/How it works","h":"#authentication","p":419},{"i":437,"t":"Session managment","u":"/docs/services/etherpad/How it works","h":"#session-managment","p":419},{"i":438,"t":"Etherpad Session Creation and Expiration","u":"/docs/services/etherpad/How it works","h":"#etherpad-session-creation-and-expiration","p":419},{"i":440,"t":"Session Reuse","u":"/docs/services/etherpad/How it works","h":"#session-reuse","p":419},{"i":442,"t":"Session Renewal","u":"/docs/services/etherpad/How it works","h":"#session-renewal","p":419},{"i":444,"t":"Session Removal","u":"/docs/services/etherpad/How it works","h":"#session-removal","p":419},{"i":446,"t":"Cookie Management","u":"/docs/services/etherpad/How it works","h":"#cookie-management","p":419},{"i":448,"t":"Legacy Topics","u":"/docs/services/etherpad/How it works","h":"#legacy-topics","p":419},{"i":452,"t":"Model Relationships","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#model-relationships","p":450},{"i":454,"t":"RoomMemberEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#roommemberentity","p":450},{"i":456,"t":"GroupEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupentity","p":450},{"i":458,"t":"GroupUserEmbeddable","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupuserembeddable","p":450},{"i":460,"t":"Key Points","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#key-points","p":450},{"i":462,"t":"Service","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#service","p":450},{"i":464,"t":"Usage","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#usage","p":450},{"i":466,"t":"API","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#api","p":450},{"i":469,"t":"TspSyncStrategy","u":"/docs/syncronizations/TSP/Architecture","h":"#tspsyncstrategy","p":468},{"i":472,"t":"Running the Etherpad server","u":"/docs/services/etherpad/Local setup","h":"#running-the-etherpad-server","p":471},{"i":475,"t":"Configuration","u":"/docs/syncronizations/TSP/How it works","h":"#configuration","p":474},{"i":477,"t":"Sync console","u":"/docs/syncronizations/TSP/How it works","h":"#sync-console","p":474},{"i":479,"t":"Usage","u":"/docs/syncronizations/TSP/How it works","h":"#usage","p":474},{"i":481,"t":"TSP synchronization","u":"/docs/syncronizations/TSP/How it works","h":"#tsp-synchronization","p":474},{"i":485,"t":"Configuration","u":"/docs/tldraw-server/How it works","h":"#configuration","p":483},{"i":487,"t":"Create","u":"/docs/tldraw-server/How it works","h":"#create","p":483},{"i":489,"t":"Usage","u":"/docs/tldraw-server/How it works","h":"#usage","p":483},{"i":490,"t":"Connection","u":"/docs/tldraw-server/How it works","h":"#connection","p":483},{"i":492,"t":"Sending updates/storing data","u":"/docs/tldraw-server/How it works","h":"#sending-updatesstoring-data","p":483},{"i":494,"t":"Delete","u":"/docs/tldraw-server/How it works","h":"#delete","p":483},{"i":496,"t":"Assets","u":"/docs/tldraw-server/How it works","h":"#assets","p":483},{"i":497,"t":"files upload","u":"/docs/tldraw-server/How it works","h":"#files-upload","p":483},{"i":499,"t":"files deletion","u":"/docs/tldraw-server/How it works","h":"#files-deletion","p":483},{"i":502,"t":"Introduction","u":"/docs/tldraw-server/Technical details","h":"#introduction","p":501},{"i":504,"t":"How tldraw Uses Redis and S3 Storage:","u":"/docs/tldraw-server/Technical details","h":"#how-tldraw-uses-redis-and-s3-storage","p":501},{"i":506,"t":"Example Scenario:","u":"/docs/tldraw-server/Technical details","h":"#example-scenario","p":501},{"i":508,"t":"Conclusion:","u":"/docs/tldraw-server/Technical details","h":"#conclusion","p":501},{"i":510,"t":"Backend","u":"/docs/tldraw-server/Technical details","h":"#backend","p":501},{"i":511,"t":"Deployments","u":"/docs/tldraw-server/Technical details","h":"#deployments","p":501},{"i":513,"t":"Tldraw-server code structure","u":"/docs/tldraw-server/Technical details","h":"#tldraw-server-code-structure","p":501},{"i":515,"t":"Frontend","u":"/docs/tldraw-server/Technical details","h":"#frontend","p":501},{"i":516,"t":"Key Files","u":"/docs/tldraw-server/Technical details","h":"#key-files","p":501},{"i":518,"t":"Frontend Technologies","u":"/docs/tldraw-server/Technical details","h":"#frontend-technologies","p":501},{"i":520,"t":"State Managment","u":"/docs/tldraw-server/Technical details","h":"#state-managment","p":501},{"i":523,"t":"To run tldraw locally:","u":"/docs/tldraw-server/Local setup","h":"#to-run-tldraw-locally","p":522},{"i":525,"t":"Create new whiteboard:","u":"/docs/tldraw-server/Local setup","h":"#create-new-whiteboard","p":522},{"i":528,"t":"Goals","u":"/docs/schulcloud-server/architecture","h":"#goals","p":527},{"i":530,"t":"Principles","u":"/docs/schulcloud-server/architecture","h":"#principles","p":527},{"i":532,"t":"Server Layer Architecture","u":"/docs/schulcloud-server/architecture","h":"#server-layer-architecture","p":527},{"i":534,"t":"Domain Layer","u":"/docs/schulcloud-server/architecture","h":"#domain-layer","p":527},{"i":536,"t":"API Layer","u":"/docs/schulcloud-server/architecture","h":"#api-layer","p":527},{"i":538,"t":"Repository Layer","u":"/docs/schulcloud-server/architecture","h":"#repository-layer","p":527},{"i":540,"t":"Modules","u":"/docs/schulcloud-server/architecture","h":"#modules","p":527},{"i":542,"t":"Api Modules","u":"/docs/schulcloud-server/architecture","h":"#api-modules","p":527},{"i":544,"t":"Horizontal Architecture","u":"/docs/schulcloud-server/architecture","h":"#horizontal-architecture","p":527},{"i":547,"t":"Function","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#function","p":546},{"i":548,"t":"Naming functions","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-functions","p":546},{"i":550,"t":"Avoid direct returns of computations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-direct-returns-of-computations","p":546},{"i":552,"t":"avoid directly passing function results as parameters","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-directly-passing-function-results-as-parameters","p":546},{"i":554,"t":"explicit return type","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#explicit-return-type","p":546},{"i":556,"t":"Interfaces","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#interfaces","p":546},{"i":557,"t":"Avoid the \"I\" for interfaces","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-the-i-for-interfaces","p":546},{"i":559,"t":"Classes","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#classes","p":546},{"i":560,"t":"Order of declarations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#order-of-declarations","p":546},{"i":562,"t":"Naming classes","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-classes","p":546},{"i":564,"t":"Do NOT use JsDoc","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-not-use-jsdoc","p":546},{"i":566,"t":"Do use empty lines","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-use-empty-lines","p":546}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/3",[0,3.644,1,3.178,2,3.178,3,2.675]],["t/4",[4,5.515,5,4.33]],["t/6",[6,6.736]],["t/8",[2,2.805,3,2.361,7,2.981,8,1.987,9,2.981]],["t/10",[8,1.778,9,2.668,10,2.878,11,3.198,12,1.313,13,2.191]],["t/12",[1,2.51,14,2.878,15,2.668,16,2.668,17,2.878,18,3.198]],["t/14",[19,4.203,20,2.983,21,3.482]],["t/16",[1,3.178,22,4.048,23,3.178,24,3.644]],["t/18",[1,3.178,15,3.377,16,3.377,25,4.048]],["t/20",[26,4.048,27,4.048,28,3.644,29,3.644]],["t/24",[0,4.203,30,4.669,31,4.669]],["t/26",[7,3.895,32,4.669,33,4.203]],["t/28",[10,2.188,12,0.999,34,2.028,35,2.431,36,2.188,37,2.431,38,2.188,39,2.431,40,2.431]],["t/30",[14,4.203,41,4.669,42,3.482]],["t/32",[12,1.663,19,3.644,43,3.377,44,4.048]],["t/36",[45,4.669,46,4.203,47,4.669]],["t/38",[3,2.675,46,3.644,48,4.048,49,3.377]],["t/42",[50,4.964,51,5.515]],["t/44",[50,4.964,52,5.515]],["t/48",[53,5.515,54,5.515]],["t/52",[55,2.892,56,3.666,57,4.669]],["t/56",[55,2.892,56,3.666,58,4.669]],["t/58",[59,3.779,60,5.515]],["t/62",[55,2.507,56,3.178,61,3.644,62,3.377]],["t/64",[55,2.507,56,3.178,62,3.377,63,4.048]],["t/68",[64,4.33,65,5.515]],["t/73",[66,5.515,67,5.515]],["t/75",[68,6.736]],["t/77",[69,6.063]],["t/79",[70,4.601,71,4.114]],["t/81",[72,6.736]],["t/83",[61,4.203,73,4.669,74,3.199]],["t/87",[75,5.515,76,4.964]],["t/89",[69,4.203,76,4.203,77,4.669]],["t/91",[78,6.063]],["t/93",[79,5.515,80,5.515]],["t/99",[81,3.573,82,3.573,83,2.981,84,3.573,85,3.573]],["t/101",[86,5.515,87,4.601]],["t/105",[13,3.779,88,4.964]],["t/107",[89,4.669,90,4.669,91,4.669]],["t/109",[92,5.62]],["t/113",[93,5.289]],["t/114",[23,4.33,94,4.33]],["t/116",[8,2.597,94,3.666,95,4.669]],["t/118",[94,3.666,96,4.669,97,4.669]],["t/120",[16,3.895,94,3.666,98,4.669]],["t/122",[99,6.736]],["t/125",[100,5.515,101,5.515]],["t/127",[59,3.199,102,4.669,103,4.203]],["t/128",[8,1.778,74,2.191,104,3.198,105,3.198,106,3.198,107,3.198]],["t/130",[13,2.448,74,2.448,103,3.216,108,3.216,109,3.216]],["t/132",[20,2.283,74,3.716,108,3.216,110,3.573]],["t/135",[111,5.515,112,3.933]],["t/137",[113,4.601,114,3.933]],["t/139",[12,2.265,115,4.601]],["t/141",[116,6.736]],["t/143",[112,3.933,117,5.515]],["t/147",[118,5.515,119,5.515]],["t/150",[120,4.048,121,4.048,122,4.048,123,4.048]],["t/154",[2,4.33,124,5.515]],["t/156",[8,1.987,125,2.805,126,2.548,127,3.573,128,2.805]],["t/158",[129,5.515,130,4.964]],["t/160",[8,2.597,125,3.666,126,3.33]],["t/162",[38,4.203,62,3.895,131,4.669]],["t/164",[132,4.601,133,4.33]],["t/166",[134,3.377,135,4.048,136,4.048,137,3.019]],["t/169",[42,4.114,138,4.33]],["t/171",[42,3.482,138,3.666,139,4.669]],["t/173",[42,3.019,87,3.377,138,3.178,140,4.048]],["t/176",[141,6.736]],["t/178",[142,6.736]],["t/180",[64,4.33,71,4.114]],["t/182",[64,4.33,71,4.114]],["t/184",[64,3.666,71,3.482,128,3.666]],["t/186",[2,3.666,143,4.669,144,3.666]],["t/188",[128,3.178,145,4.048,146,4.048,147,4.048]],["t/192",[148,6.736]],["t/194",[12,2.227,74,2.448,115,2.981,149,3.216]],["t/196",[12,1.663,150,4.048,151,4.048,152,4.048]],["t/198",[8,2.252,12,1.663,87,3.377,153,3.644]],["t/200",[8,3.068,154,5.515]],["t/202",[12,1.918,20,2.983,74,3.199]],["t/204",[5,2.51,8,1.778,12,1.313,71,2.385,109,2.878,155,3.198]],["t/206",[12,1.918,20,2.983,156,4.669]],["t/208",[13,3.779,88,4.964]],["t/210",[24,4.964,112,3.933]],["t/212",[12,2.767]],["t/213",[157,5.024]],["t/215",[12,1.918,158,4.669,159,4.669]],["t/217",[132,5.62]],["t/219",[160,6.736]],["t/221",[12,2.265,92,4.601]],["t/223",[161,4.45]],["t/225",[134,4.601,161,3.644]],["t/227",[161,3.085,162,4.669,163,4.203]],["t/229",[161,3.085,163,4.203,164,4.669]],["t/231",[92,4.601,161,3.644]],["t/233",[12,1.918,74,3.199,165,4.669]],["t/235",[12,1.918,166,6.599]],["t/237",[70,4.601,167,5.515]],["t/240",[168,6.736]],["t/242",[169,6.736]],["t/247",[83,3.377,170,3.644,171,3.644,172,3.644]],["t/249",[83,3.377,134,3.377,170,3.644,172,3.644]],["t/253",[173,5.515,174,4.964]],["t/255",[175,6.736]],["t/257",[176,6.063]],["t/259",[3,3.085,20,2.983,176,4.203]],["t/261",[177,5.515,178,5.515]],["t/263",[179,5.62]],["t/267",[59,3.779,180,5.515]],["t/269",[179,4.601,181,5.515]],["t/271",[59,3.779,182,5.515]],["t/274",[7,3.377,8,2.252,15,3.377,183,2.774]],["t/280",[133,3.666,157,3.482,184,4.669]],["t/282",[157,4.114,185,5.515]],["t/284",[157,4.114,186,4.964]],["t/286",[157,4.114,187,5.515]],["t/290",[137,3.019,188,4.048,189,4.048,190,4.048]],["t/292",[132,4.601,191,5.515]],["t/296",[137,3.019,192,5.953,193,4.048]],["t/298",[194,4.601,195,5.515]],["t/304",[144,4.33,196,5.515]],["t/306",[3,3.644,197,5.515]],["t/308",[42,3.482,133,3.666,138,3.666]],["t/313",[20,2.983,21,3.482,198,4.669]],["t/315",[21,3.482,199,4.203,200,4.669]],["t/317",[201,6.736]],["t/321",[12,1.918,21,3.482,125,3.666]],["t/322",[12,2.265,202,5.515]],["t/324",[20,3.524,21,4.114]],["t/326",[203,6.736]],["t/328",[5,4.33,12,2.265]],["t/330",[12,2.265,204,5.515]],["t/331",[133,3.178,205,3.178,206,3.377,207,4.048]],["t/333",[12,1.918,208,4.669,209,4.669]],["t/335",[12,1.918,137,3.482,210,4.669]],["t/337",[12,2.265,153,4.964]],["t/339",[161,4.45]],["t/341",[12,2.227,115,2.981,149,3.216,211,3.573]],["t/343",[12,2.265,212,5.515]],["t/345",[12,2.265,179,4.601]],["t/347",[8,2.597,12,1.918,213,4.669]],["t/349",[12,2.265,214,5.515]],["t/351",[12,2.265,126,3.933]],["t/353",[215,6.736]],["t/357",[216,6.736]],["t/359",[112,1.734,161,1.606,217,2.188,218,2.028,219,2.188,220,2.188,221,2.188,222,2.188,223,2.188]],["t/361",[112,1.495,161,1.385,217,1.887,218,1.749,219,1.887,220,1.887,221,1.887,222,1.887,223,1.887,224,2.096,225,2.096]],["t/363",[12,1.918,36,4.203,226,4.203]],["t/365",[13,3.779,227,4.964]],["t/367",[13,3.199,227,4.203,228,4.669]],["t/369",[229,5.515,230,5.515]],["t/372",[113,4.601,231,5.515]],["t/374",[113,4.601,232,5.515]],["t/376",[233,6.736]],["t/378",[114,3.33,234,4.203,235,4.203]],["t/380",[130,4.203,194,3.895,234,4.203]],["t/383",[236,5.515,237,5.515]],["t/385",[34,5.62]],["t/387",[238,5.515,239,5.515]],["t/389",[240,6.736]],["t/394",[114,3.33,183,3.199,241,4.669]],["t/396",[55,2.892,183,3.199,242,4.203]],["t/398",[183,3.779,243,5.515]],["t/400",[183,3.779,244,5.515]],["t/402",[112,3.933,245,5.515]],["t/404",[12,2.265,183,3.779]],["t/406",[183,3.779,199,4.964]],["t/408",[28,4.964,29,4.964]],["t/409",[125,5.289]],["t/411",[246,6.736]],["t/413",[137,4.114,247,5.515]],["t/415",[248,6.736]],["t/417",[249,6.736]],["t/420",[59,4.615]],["t/422",[55,1.98,250,2.878,251,2.113,252,2.668,253,3.198,254,3.198]],["t/423",[55,2.892,251,3.085,252,3.895]],["t/425",[251,3.085,252,3.895,255,4.669]],["t/427",[55,2.892,251,3.085,256,4.669]],["t/429",[34,3.377,257,2.887,258,3.644,259,3.644]],["t/431",[250,4.964,251,3.644]],["t/433",[251,3.644,260,5.515]],["t/435",[174,6.063]],["t/437",[257,3.933,261,4.601]],["t/438",[251,2.675,257,2.887,258,3.644,262,4.048]],["t/440",[257,3.933,263,5.515]],["t/442",[257,3.933,264,5.515]],["t/444",[257,3.933,265,5.515]],["t/446",[259,4.964,261,4.601]],["t/448",[194,4.601,266,5.515]],["t/452",[267,5.515,268,5.515]],["t/454",[269,6.736]],["t/456",[270,6.736]],["t/458",[271,6.736]],["t/460",[49,4.601,272,5.515]],["t/462",[171,6.063]],["t/464",[93,5.289]],["t/466",[126,4.804]],["t/469",[273,6.736]],["t/472",[43,3.895,114,3.33,251,3.085]],["t/475",[59,4.615]],["t/477",[274,5.515,275,5.515]],["t/479",[93,5.289]],["t/481",[276,5.515,277,5.515]],["t/485",[59,4.615]],["t/487",[55,4.172]],["t/489",[93,5.289]],["t/490",[218,5.62]],["t/492",[13,3.199,186,4.203,278,4.669]],["t/494",[279,6.063]],["t/496",[280,6.736]],["t/497",[3,3.644,281,5.515]],["t/499",[3,3.644,279,4.964]],["t/502",[282,6.736]],["t/504",[8,1.987,283,2.981,284,3.573,285,3.573,286,3.573]],["t/506",[17,4.964,78,4.964]],["t/508",[287,6.736]],["t/510",[235,6.063]],["t/511",[288,6.736]],["t/513",[5,3.178,70,3.377,114,2.887,283,3.377]],["t/515",[289,6.063]],["t/516",[3,3.644,49,4.601]],["t/518",[289,4.964,290,5.515]],["t/520",[261,4.601,291,5.515]],["t/523",[43,3.895,226,4.203,283,3.895]],["t/525",[55,2.892,242,4.203,292,4.669]],["t/528",[293,6.736]],["t/530",[294,6.736]],["t/532",[114,3.33,295,3.666,296,4.203]],["t/534",[295,4.33,297,5.515]],["t/536",[126,3.933,295,4.33]],["t/538",[33,4.964,295,4.33]],["t/540",[144,5.289]],["t/542",[126,3.933,144,4.33]],["t/544",[296,4.964,298,5.515]],["t/547",[205,5.289]],["t/548",[20,3.524,205,4.33]],["t/550",[206,3.377,299,3.377,300,4.048,301,4.048]],["t/552",[9,2.668,205,2.51,299,2.668,302,3.198,303,3.198,304,3.198]],["t/554",[128,3.666,206,3.895,305,4.669]],["t/556",[306,6.063]],["t/557",[299,4.601,306,4.964]],["t/559",[23,5.289]],["t/560",[307,5.515,308,5.515]],["t/562",[20,3.524,23,4.33]],["t/564",[8,3.068,309,5.515]],["t/566",[8,2.597,310,4.669,311,4.669]]],"invertedIndex":[["",{"_index":151,"t":{"196":{"position":[[9,1]]}}}],["1",{"_index":0,"t":{"3":{"position":[[0,2]]},"24":{"position":[[0,2]]}}}],["2",{"_index":7,"t":{"8":{"position":[[0,2]]},"26":{"position":[[0,2]]},"274":{"position":[[12,1]]}}}],["3",{"_index":10,"t":{"10":{"position":[[0,2]]},"28":{"position":[[0,2]]}}}],["4",{"_index":14,"t":{"12":{"position":[[0,2]]},"30":{"position":[[0,2]]}}}],["5",{"_index":19,"t":{"14":{"position":[[0,2]]},"32":{"position":[[0,2]]}}}],["6",{"_index":22,"t":{"16":{"position":[[0,2]]}}}],["7",{"_index":25,"t":{"18":{"position":[[0,2]]}}}],["8",{"_index":26,"t":{"20":{"position":[[0,2]]}}}],["access",{"_index":83,"t":{"99":{"position":[[8,13]]},"247":{"position":[[0,6]]},"249":{"position":[[0,6]]}}}],["adapt",{"_index":260,"t":{"433":{"position":[[9,7]]}}}],["addit",{"_index":27,"t":{"20":{"position":[[3,10]]}}}],["admonit",{"_index":72,"t":{"81":{"position":[[0,11]]}}}],["api",{"_index":126,"t":{"156":{"position":[[16,3]]},"160":{"position":[[20,3]]},"351":{"position":[[0,3]]},"466":{"position":[[0,3]]},"536":{"position":[[0,3]]},"542":{"position":[[0,3]]}}}],["appli",{"_index":243,"t":{"398":{"position":[[0,5]]}}}],["applic",{"_index":180,"t":{"267":{"position":[[0,11]]}}}],["architectur",{"_index":296,"t":{"532":{"position":[[13,12]]},"544":{"position":[[11,12]]}}}],["asset",{"_index":280,"t":{"496":{"position":[[0,6]]}}}],["asynchron",{"_index":158,"t":{"215":{"position":[[8,12]]}}}],["authent",{"_index":174,"t":{"253":{"position":[[4,14]]},"435":{"position":[[0,14]]}}}],["avoid",{"_index":299,"t":{"550":{"position":[[0,5]]},"552":{"position":[[0,5]]},"557":{"position":[[0,5]]}}}],["backend",{"_index":235,"t":{"378":{"position":[[15,7]]},"510":{"position":[[0,7]]}}}],["background",{"_index":97,"t":{"118":{"position":[[21,11]]}}}],["barrel",{"_index":197,"t":{"306":{"position":[[0,6]]}}}],["basic",{"_index":148,"t":{"192":{"position":[[0,6]]}}}],["behavior",{"_index":159,"t":{"215":{"position":[[21,8]]}}}],["best",{"_index":28,"t":{"20":{"position":[[14,4]]},"408":{"position":[[0,4]]}}}],["block",{"_index":71,"t":{"79":{"position":[[5,6]]},"180":{"position":[[9,6]]},"182":{"position":[[19,6]]},"184":{"position":[[18,6]]},"204":{"position":[[49,6]]}}}],["board",{"_index":254,"t":{"422":{"position":[[53,5]]}}}],["branch",{"_index":198,"t":{"313":{"position":[[0,6]]}}}],["broker",{"_index":223,"t":{"359":{"position":[[58,9]]},"361":{"position":[[58,9]]}}}],["build",{"_index":64,"t":{"68":{"position":[[0,5]]},"180":{"position":[[0,8]]},"182":{"position":[[10,8]]},"184":{"position":[[9,8]]}}}],["case",{"_index":213,"t":{"347":{"position":[[4,4]]}}}],["caus",{"_index":189,"t":{"290":{"position":[[25,5]]}}}],["caveat",{"_index":249,"t":{"417":{"position":[[0,7]]}}}],["chain",{"_index":188,"t":{"290":{"position":[[0,8]]}}}],["circular",{"_index":138,"t":{"169":{"position":[[10,8]]},"171":{"position":[[10,8]]},"173":{"position":[[16,8]]},"308":{"position":[[9,8]]}}}],["class",{"_index":23,"t":{"16":{"position":[[11,7]]},"114":{"position":[[6,7]]},"559":{"position":[[0,7]]},"562":{"position":[[7,7]]}}}],["client",{"_index":130,"t":{"158":{"position":[[17,7]]},"380":{"position":[[22,6]]}}}],["clone",{"_index":32,"t":{"26":{"position":[[3,7]]}}}],["code",{"_index":70,"t":{"79":{"position":[[0,4]]},"237":{"position":[[0,4]]},"513":{"position":[[14,4]]}}}],["color",{"_index":94,"t":{"114":{"position":[[0,5]]},"116":{"position":[[4,6]]},"118":{"position":[[33,6]]},"120":{"position":[[21,6]]}}}],["column",{"_index":253,"t":{"422":{"position":[[46,6]]}}}],["command",{"_index":230,"t":{"369":{"position":[[4,8]]}}}],["comment",{"_index":91,"t":{"107":{"position":[[10,8]]}}}],["commit",{"_index":199,"t":{"315":{"position":[[0,6]]},"406":{"position":[[0,10]]}}}],["compon",{"_index":74,"t":{"83":{"position":[[14,10]]},"128":{"position":[[35,10]]},"130":{"position":[[31,10]]},"132":{"position":[[7,10],[34,9]]},"194":{"position":[[15,9]]},"202":{"position":[[26,10]]},"233":{"position":[[0,10]]}}}],["compos",{"_index":92,"t":{"109":{"position":[[0,11]]},"221":{"position":[[8,11]]},"231":{"position":[[8,11]]}}}],["composit",{"_index":102,"t":{"127":{"position":[[0,11]]}}}],["comput",{"_index":301,"t":{"550":{"position":[[24,12]]}}}],["conclus",{"_index":287,"t":{"508":{"position":[[0,11]]}}}],["config",{"_index":181,"t":{"269":{"position":[[0,6]]}}}],["configur",{"_index":59,"t":{"58":{"position":[[0,9]]},"127":{"position":[[17,13]]},"267":{"position":[[12,13]]},"271":{"position":[[4,13]]},"420":{"position":[[0,13]]},"475":{"position":[[0,13]]},"485":{"position":[[0,13]]}}}],["connect",{"_index":218,"t":{"359":{"position":[[13,7]]},"361":{"position":[[13,7]]},"490":{"position":[[0,10]]}}}],["consol",{"_index":275,"t":{"477":{"position":[[5,7]]}}}],["console.error",{"_index":160,"t":{"219":{"position":[[0,13]]}}}],["control",{"_index":214,"t":{"349":{"position":[[0,10]]}}}],["convent",{"_index":21,"t":{"14":{"position":[[10,11]]},"313":{"position":[[12,11]]},"315":{"position":[[15,11]]},"321":{"position":[[13,11]]},"324":{"position":[[7,10]]}}}],["cooki",{"_index":259,"t":{"429":{"position":[[21,6]]},"446":{"position":[[0,6]]}}}],["correct",{"_index":146,"t":{"188":{"position":[[16,7]]}}}],["coverag",{"_index":167,"t":{"237":{"position":[[5,8]]}}}],["creat",{"_index":55,"t":{"52":{"position":[[0,6]]},"56":{"position":[[0,6]]},"62":{"position":[[0,6]]},"64":{"position":[[0,6]]},"396":{"position":[[0,6]]},"422":{"position":[[0,8]]},"423":{"position":[[0,8]]},"427":{"position":[[13,8]]},"487":{"position":[[0,6]]},"525":{"position":[[0,6]]}}}],["creation",{"_index":258,"t":{"429":{"position":[[8,8]]},"438":{"position":[[17,8]]}}}],["credenti",{"_index":39,"t":{"28":{"position":[[57,11]]}}}],["custom",{"_index":98,"t":{"120":{"position":[[14,6]]}}}],["cypress",{"_index":44,"t":{"32":{"position":[[11,7]]}}}],["data",{"_index":13,"t":{"10":{"position":[[37,4]]},"105":{"position":[[0,4]]},"130":{"position":[[12,4]]},"208":{"position":[[0,4]]},"365":{"position":[[8,4]]},"367":{"position":[[14,4]]},"492":{"position":[[24,4]]}}}],["databas",{"_index":241,"t":{"394":{"position":[[22,8]]}}}],["dbildungscloud",{"_index":234,"t":{"378":{"position":[[0,14]]},"380":{"position":[[0,14]]}}}],["debug",{"_index":248,"t":{"415":{"position":[[0,9]]}}}],["declar",{"_index":308,"t":{"560":{"position":[[9,12]]}}}],["defin",{"_index":185,"t":{"282":{"position":[[0,8]]}}}],["definit",{"_index":16,"t":{"12":{"position":[[16,11]]},"18":{"position":[[16,11]]},"120":{"position":[[0,10]]}}}],["delet",{"_index":279,"t":{"494":{"position":[[0,6]]},"499":{"position":[[6,8]]}}}],["depend",{"_index":42,"t":{"30":{"position":[[14,12]]},"169":{"position":[[19,11]]},"171":{"position":[[19,12]]},"173":{"position":[[25,12]]},"308":{"position":[[18,12]]}}}],["deploy",{"_index":288,"t":{"511":{"position":[[0,11]]}}}],["describ",{"_index":155,"t":{"204":{"position":[[38,10]]}}}],["descript",{"_index":51,"t":{"42":{"position":[[4,12]]}}}],["design",{"_index":122,"t":{"150":{"position":[[22,6]]}}}],["destructur",{"_index":108,"t":{"130":{"position":[[0,11]]},"132":{"position":[[21,12]]}}}],["develop",{"_index":111,"t":{"135":{"position":[[0,11]]}}}],["direct",{"_index":300,"t":{"550":{"position":[[6,6]]}}}],["directli",{"_index":302,"t":{"552":{"position":[[6,8]]}}}],["directori",{"_index":46,"t":{"36":{"position":[[8,9]]},"38":{"position":[[19,11]]}}}],["doc",{"_index":58,"t":{"56":{"position":[[18,3]]}}}],["docker",{"_index":216,"t":{"357":{"position":[[0,6]]}}}],["dof",{"_index":182,"t":{"271":{"position":[[0,3]]}}}],["domain",{"_index":297,"t":{"534":{"position":[[0,6]]}}}],["dto",{"_index":176,"t":{"257":{"position":[[0,4]]},"259":{"position":[[0,3]]}}}],["editor",{"_index":117,"t":{"143":{"position":[[0,6]]}}}],["element",{"_index":252,"t":{"422":{"position":[[33,7]]},"423":{"position":[[21,7]]},"425":{"position":[[30,7]]}}}],["empti",{"_index":310,"t":{"566":{"position":[[7,5]]}}}],["end",{"_index":166,"t":{"235":{"position":[[0,3],[7,3]]}}}],["env",{"_index":233,"t":{"376":{"position":[[0,4]]}}}],["environ",{"_index":36,"t":{"28":{"position":[[14,11]]},"363":{"position":[[11,11]]}}}],["error",{"_index":137,"t":{"166":{"position":[[30,5]]},"290":{"position":[[9,6]]},"296":{"position":[[15,5]]},"335":{"position":[[10,6]]},"413":{"position":[[0,5]]}}}],["erwin",{"_index":221,"t":{"359":{"position":[[48,5]]},"361":{"position":[[48,5]]}}}],["etherpad",{"_index":251,"t":{"422":{"position":[[24,8]]},"423":{"position":[[12,8]]},"425":{"position":[[21,8]]},"427":{"position":[[22,9]]},"431":{"position":[[12,8]]},"433":{"position":[[0,8]]},"438":{"position":[[0,8]]},"472":{"position":[[12,8]]}}}],["event",{"_index":157,"t":{"213":{"position":[[0,6]]},"280":{"position":[[17,5]]},"282":{"position":[[12,5]]},"284":{"position":[[11,5]]},"286":{"position":[[13,5]]}}}],["exampl",{"_index":78,"t":{"91":{"position":[[0,8]]},"506":{"position":[[0,7]]}}}],["except",{"_index":132,"t":{"164":{"position":[[0,9]]},"217":{"position":[[0,10]]},"292":{"position":[[9,10]]}}}],["expect",{"_index":210,"t":{"335":{"position":[[0,9]]}}}],["expir",{"_index":262,"t":{"438":{"position":[[30,10]]}}}],["explan",{"_index":48,"t":{"38":{"position":[[0,11]]}}}],["explicit",{"_index":305,"t":{"554":{"position":[[0,8]]}}}],["export",{"_index":76,"t":{"87":{"position":[[5,6]]},"89":{"position":[[6,6]]}}}],["extens",{"_index":239,"t":{"387":{"position":[[12,10]]}}}],["fallback",{"_index":135,"t":{"166":{"position":[[9,8]]}}}],["feather",{"_index":170,"t":{"247":{"position":[[7,8]]},"249":{"position":[[30,8]]}}}],["featur",{"_index":2,"t":{"3":{"position":[[11,7]]},"8":{"position":[[23,7]]},"154":{"position":[[0,7]]},"186":{"position":[[10,7]]}}}],["file",{"_index":3,"t":{"3":{"position":[[19,5]]},"8":{"position":[[31,5]]},"38":{"position":[[35,5]]},"259":{"position":[[4,4]]},"306":{"position":[[7,5]]},"497":{"position":[[0,5]]},"499":{"position":[[0,5]]},"516":{"position":[[4,5]]}}}],["filenam",{"_index":141,"t":{"176":{"position":[[0,9]]}}}],["first",{"_index":56,"t":{"52":{"position":[[12,5]]},"56":{"position":[[12,5]]},"62":{"position":[[12,5]]},"64":{"position":[[12,5]]}}}],["flag",{"_index":124,"t":{"154":{"position":[[8,5]]}}}],["flexibl",{"_index":106,"t":{"128":{"position":[[23,8]]}}}],["folder",{"_index":142,"t":{"178":{"position":[[0,7]]}}}],["front",{"_index":66,"t":{"73":{"position":[[0,5]]}}}],["frontend",{"_index":289,"t":{"515":{"position":[[0,8]]},"518":{"position":[[0,8]]}}}],["function",{"_index":205,"t":{"331":{"position":[[12,8]]},"547":{"position":[[0,8]]},"548":{"position":[[7,9]]},"552":{"position":[[23,8]]}}}],["gener",{"_index":125,"t":{"156":{"position":[[6,9]]},"160":{"position":[[10,9]]},"321":{"position":[[0,7]]},"409":{"position":[[0,7]]}}}],["goal",{"_index":293,"t":{"528":{"position":[[0,5]]}}}],["group",{"_index":256,"t":{"427":{"position":[[0,8]]}}}],["groupent",{"_index":270,"t":{"456":{"position":[[0,11]]}}}],["groupuserembedd",{"_index":271,"t":{"458":{"position":[[0,19]]}}}],["guidelin",{"_index":6,"t":{"6":{"position":[[0,10]]}}}],["handl",{"_index":133,"t":{"164":{"position":[[10,8]]},"280":{"position":[[23,8]]},"308":{"position":[[0,8]]},"331":{"position":[[0,8]]}}}],["handling/log",{"_index":247,"t":{"413":{"position":[[6,16]]}}}],["hard",{"_index":165,"t":{"233":{"position":[[20,4]]}}}],["hierarchi",{"_index":52,"t":{"44":{"position":[[4,9]]}}}],["highli",{"_index":105,"t":{"128":{"position":[[16,6]]}}}],["horizont",{"_index":298,"t":{"544":{"position":[[0,10]]}}}],["html",{"_index":100,"t":{"125":{"position":[[0,4]]}}}],["icon",{"_index":123,"t":{"150":{"position":[[29,5]]}}}],["ident",{"_index":219,"t":{"359":{"position":[[21,8]]},"361":{"position":[[21,8]]}}}],["identifi",{"_index":140,"t":{"173":{"position":[[7,8]]}}}],["idm",{"_index":222,"t":{"359":{"position":[[54,3]]},"361":{"position":[[54,3]]}}}],["ignor",{"_index":90,"t":{"107":{"position":[[3,6]]}}}],["imag",{"_index":69,"t":{"77":{"position":[[0,6]]},"89":{"position":[[0,5]]}}}],["implement",{"_index":184,"t":{"280":{"position":[[7,9]]}}}],["initi",{"_index":84,"t":{"99":{"position":[[22,10]]}}}],["inject",{"_index":134,"t":{"166":{"position":[[0,6]]},"225":{"position":[[8,10]]},"249":{"position":[[14,10]]}}}],["instal",{"_index":41,"t":{"30":{"position":[[3,10]]}}}],["integr",{"_index":211,"t":{"341":{"position":[[14,11]]}}}],["interact",{"_index":255,"t":{"425":{"position":[[0,11]]}}}],["interfac",{"_index":306,"t":{"556":{"position":[[0,10]]},"557":{"position":[[18,10]]}}}],["introduct",{"_index":282,"t":{"502":{"position":[[0,12]]}}}],["isol",{"_index":203,"t":{"326":{"position":[[0,9]]}}}],["it'",{"_index":127,"t":{"156":{"position":[[24,4]]}}}],["jest",{"_index":240,"t":{"389":{"position":[[0,4]]}}}],["jsdoc",{"_index":309,"t":{"564":{"position":[[11,5]]}}}],["jwt",{"_index":173,"t":{"253":{"position":[[0,3]]}}}],["key",{"_index":49,"t":{"38":{"position":[[15,3]]},"460":{"position":[[0,3]]},"516":{"position":[[0,3]]}}}],["launch",{"_index":236,"t":{"383":{"position":[[0,6]]}}}],["layer",{"_index":295,"t":{"532":{"position":[[7,5]]},"534":{"position":[[7,5]]},"536":{"position":[[4,5]]},"538":{"position":[[11,5]]}}}],["layout",{"_index":47,"t":{"36":{"position":[[18,6]]}}}],["ldap",{"_index":224,"t":{"361":{"position":[[73,4]]}}}],["lean",{"_index":202,"t":{"322":{"position":[[0,4]]}}}],["legaci",{"_index":194,"t":{"298":{"position":[[0,6]]},"380":{"position":[[15,6]]},"448":{"position":[[0,6]]}}}],["level",{"_index":193,"t":{"296":{"position":[[4,6]]}}}],["life",{"_index":143,"t":{"186":{"position":[[0,4]]}}}],["line",{"_index":311,"t":{"566":{"position":[[13,5]]}}}],["link",{"_index":68,"t":{"75":{"position":[[0,5]]}}}],["lint",{"_index":116,"t":{"141":{"position":[[0,4]]}}}],["local",{"_index":226,"t":{"363":{"position":[[5,5]]},"523":{"position":[[14,8]]}}}],["log",{"_index":192,"t":{"296":{"position":[[0,3],[21,7]]}}}],["loggabl",{"_index":191,"t":{"292":{"position":[[0,8]]}}}],["logger",{"_index":195,"t":{"298":{"position":[[7,6]]}}}],["manag",{"_index":261,"t":{"437":{"position":[[8,9]]},"446":{"position":[[7,10]]},"520":{"position":[[6,9]]}}}],["map",{"_index":179,"t":{"263":{"position":[[0,7]]},"269":{"position":[[7,4]]},"345":{"position":[[0,7]]}}}],["markdown",{"_index":63,"t":{"64":{"position":[[18,8]]}}}],["materi",{"_index":121,"t":{"150":{"position":[[13,8]]}}}],["matter",{"_index":67,"t":{"73":{"position":[[6,6]]}}}],["mdx",{"_index":73,"t":{"83":{"position":[[0,3]]}}}],["messag",{"_index":200,"t":{"315":{"position":[[7,7]]}}}],["method",{"_index":24,"t":{"16":{"position":[[23,7]]},"210":{"position":[[6,7]]}}}],["migrat",{"_index":183,"t":{"274":{"position":[[19,9]]},"394":{"position":[[0,10]]},"396":{"position":[[13,9]]},"398":{"position":[[6,10]]},"400":{"position":[[5,9]]},"404":{"position":[[5,9]]},"406":{"position":[[13,9]]}}}],["mock",{"_index":161,"t":{"223":{"position":[[0,7]]},"225":{"position":[[0,7]]},"227":{"position":[[0,7]]},"229":{"position":[[0,7]]},"231":{"position":[[0,7]]},"339":{"position":[[0,7]]},"359":{"position":[[39,4]]},"361":{"position":[[39,4]]}}}],["model",{"_index":267,"t":{"452":{"position":[[0,5]]}}}],["modul",{"_index":144,"t":{"186":{"position":[[18,6]]},"304":{"position":[[0,7]]},"540":{"position":[[0,7]]},"542":{"position":[[4,7]]}}}],["mongodb",{"_index":231,"t":{"372":{"position":[[6,7]]}}}],["more",{"_index":79,"t":{"93":{"position":[[0,4]]}}}],["multipl",{"_index":109,"t":{"130":{"position":[[22,8]]},"204":{"position":[[27,10]]}}}],["name",{"_index":20,"t":{"14":{"position":[[3,6]]},"132":{"position":[[0,6]]},"202":{"position":[[0,4]]},"206":{"position":[[0,4]]},"259":{"position":[[9,6]]},"313":{"position":[[7,4]]},"324":{"position":[[0,6]]},"548":{"position":[[0,6]]},"562":{"position":[[0,6]]}}}],["neg",{"_index":152,"t":{"196":{"position":[[11,8]]}}}],["nest.j",{"_index":168,"t":{"240":{"position":[[0,7]]}}}],["nestj",{"_index":172,"t":{"247":{"position":[[29,6]]},"249":{"position":[[7,6]]}}}],["new",{"_index":242,"t":{"396":{"position":[[9,3]]},"525":{"position":[[7,3]]}}}],["next",{"_index":54,"t":{"48":{"position":[[7,5]]}}}],["note",{"_index":245,"t":{"402":{"position":[[0,5]]}}}],["npm",{"_index":229,"t":{"369":{"position":[[0,3]]}}}],["open",{"_index":250,"t":{"422":{"position":[[13,7]]},"431":{"position":[[0,7]]}}}],["openapi",{"_index":177,"t":{"261":{"position":[[0,7]]}}}],["openid",{"_index":217,"t":{"359":{"position":[[6,6]]},"361":{"position":[[6,6]]}}}],["order",{"_index":307,"t":{"560":{"position":[[0,5]]}}}],["outlin",{"_index":18,"t":{"12":{"position":[[41,8]]}}}],["over",{"_index":103,"t":{"127":{"position":[[12,4]]},"130":{"position":[[17,4]]}}}],["page",{"_index":62,"t":{"62":{"position":[[24,4]]},"64":{"position":[[27,4]]},"162":{"position":[[20,5]]}}}],["paramet",{"_index":9,"t":{"8":{"position":[[9,10]]},"10":{"position":[[9,10]]},"552":{"position":[[43,10]]}}}],["pass",{"_index":303,"t":{"552":{"position":[[15,7]]}}}],["perform",{"_index":246,"t":{"411":{"position":[[0,11]]}}}],["permiss",{"_index":131,"t":{"162":{"position":[[5,11]]}}}],["pick",{"_index":145,"t":{"188":{"position":[[7,4]]}}}],["pinia",{"_index":164,"t":{"229":{"position":[[8,5]]}}}],["point",{"_index":272,"t":{"460":{"position":[[4,6]]}}}],["posit",{"_index":150,"t":{"196":{"position":[[0,8]]}}}],["post",{"_index":57,"t":{"52":{"position":[[18,4]]}}}],["practic",{"_index":29,"t":{"20":{"position":[[19,9]]},"408":{"position":[[5,9]]}}}],["pre",{"_index":30,"t":{"24":{"position":[[3,3]]}}}],["principl",{"_index":294,"t":{"530":{"position":[[0,10]]}}}],["project",{"_index":45,"t":{"36":{"position":[[0,7]]}}}],["promis",{"_index":208,"t":{"333":{"position":[[0,8]]}}}],["properti",{"_index":190,"t":{"290":{"position":[[31,8]]}}}],["provid",{"_index":220,"t":{"359":{"position":[[30,8]]},"361":{"position":[[30,8]]}}}],["provis",{"_index":225,"t":{"361":{"position":[[78,12]]}}}],["pull",{"_index":118,"t":{"147":{"position":[[0,4]]}}}],["react",{"_index":61,"t":{"62":{"position":[[18,5]]},"83":{"position":[[8,5]]}}}],["reciev",{"_index":187,"t":{"286":{"position":[[0,9]]}}}],["recommend",{"_index":238,"t":{"387":{"position":[[0,11]]}}}],["redi",{"_index":284,"t":{"504":{"position":[[16,5]]}}}],["refer",{"_index":215,"t":{"353":{"position":[[0,10]]}}}],["regener",{"_index":129,"t":{"158":{"position":[[0,12]]}}}],["relationship",{"_index":268,"t":{"452":{"position":[[6,13]]}}}],["remov",{"_index":265,"t":{"444":{"position":[[8,7]]}}}],["renew",{"_index":264,"t":{"442":{"position":[[8,7]]}}}],["repo",{"_index":212,"t":{"343":{"position":[[0,4]]}}}],["repositori",{"_index":33,"t":{"26":{"position":[[15,10]]},"538":{"position":[[0,10]]}}}],["request",{"_index":119,"t":{"147":{"position":[[5,8]]}}}],["requisit",{"_index":31,"t":{"24":{"position":[[7,10]]}}}],["resolv",{"_index":139,"t":{"171":{"position":[[0,9]]}}}],["respons",{"_index":169,"t":{"242":{"position":[[0,9]]}}}],["result",{"_index":304,"t":{"552":{"position":[[32,7]]}}}],["return",{"_index":206,"t":{"331":{"position":[[21,6]]},"550":{"position":[[13,7]]},"554":{"position":[[9,6]]}}}],["reus",{"_index":263,"t":{"440":{"position":[[8,5]]}}}],["rocketchat",{"_index":232,"t":{"374":{"position":[[6,10]]}}}],["roommemberent",{"_index":269,"t":{"454":{"position":[[0,16]]}}}],["rule",{"_index":99,"t":{"122":{"position":[[0,5]]}}}],["run",{"_index":43,"t":{"32":{"position":[[3,7]]},"472":{"position":[[0,7]]},"523":{"position":[[3,3]]}}}],["s)css",{"_index":95,"t":{"116":{"position":[[14,6]]}}}],["s3",{"_index":285,"t":{"504":{"position":[[26,2]]}}}],["sampl",{"_index":204,"t":{"330":{"position":[[8,7]]}}}],["scenario",{"_index":17,"t":{"12":{"position":[[32,8]]},"506":{"position":[[8,9]]}}}],["script",{"_index":237,"t":{"383":{"position":[[7,7]]}}}],["seed",{"_index":227,"t":{"365":{"position":[[0,7]]},"367":{"position":[[9,4]]}}}],["send",{"_index":186,"t":{"284":{"position":[[0,7]]},"492":{"position":[[0,7]]}}}],["sentenc",{"_index":156,"t":{"206":{"position":[[21,8]]}}}],["server",{"_index":114,"t":{"137":{"position":[[10,6]]},"378":{"position":[[23,6]]},"394":{"position":[[15,6]]},"472":{"position":[[21,6]]},"513":{"position":[[7,6]]},"532":{"position":[[0,6]]}}}],["servic",{"_index":171,"t":{"247":{"position":[[16,7]]},"462":{"position":[[0,7]]}}}],["session",{"_index":257,"t":{"429":{"position":[[0,7]]},"437":{"position":[[0,7]]},"438":{"position":[[9,7]]},"440":{"position":[[0,7]]},"442":{"position":[[0,7]]},"444":{"position":[[0,7]]}}}],["set",{"_index":34,"t":{"28":{"position":[[3,7]]},"385":{"position":[[0,8]]},"429":{"position":[[28,7]]}}}],["setup",{"_index":112,"t":{"135":{"position":[[12,5]]},"143":{"position":[[7,5]]},"210":{"position":[[0,5]]},"359":{"position":[[0,5]]},"361":{"position":[[0,5]]},"402":{"position":[[12,5]]}}}],["sidebar",{"_index":60,"t":{"58":{"position":[[14,7]]}}}],["site",{"_index":65,"t":{"68":{"position":[[11,4]]}}}],["slot",{"_index":104,"t":{"128":{"position":[[6,5]]}}}],["solut",{"_index":80,"t":{"93":{"position":[[5,9]]}}}],["special",{"_index":11,"t":{"10":{"position":[[24,7]]}}}],["specif",{"_index":178,"t":{"261":{"position":[[8,13]]}}}],["start",{"_index":113,"t":{"137":{"position":[[0,5]]},"372":{"position":[[0,5]]},"374":{"position":[[0,5]]}}}],["state",{"_index":291,"t":{"520":{"position":[[0,5]]}}}],["step",{"_index":15,"t":{"12":{"position":[[11,4]]},"18":{"position":[[11,4]]},"274":{"position":[[14,4]]}}}],["storag",{"_index":286,"t":{"504":{"position":[[29,8]]}}}],["store",{"_index":163,"t":{"227":{"position":[[13,5]]},"229":{"position":[[14,6]]}}}],["string",{"_index":101,"t":{"125":{"position":[[14,6]]}}}],["structur",{"_index":5,"t":{"4":{"position":[[9,9]]},"204":{"position":[[0,9]]},"328":{"position":[[5,9]]},"513":{"position":[[19,9]]}}}],["submodul",{"_index":196,"t":{"304":{"position":[[12,10]]}}}],["surfac",{"_index":96,"t":{"118":{"position":[[4,8]]}}}],["sync",{"_index":274,"t":{"477":{"position":[[0,4]]}}}],["synchron",{"_index":277,"t":{"481":{"position":[[4,15]]}}}],["tag",{"_index":50,"t":{"42":{"position":[[0,3]]},"44":{"position":[[0,3]]}}}],["task",{"_index":147,"t":{"188":{"position":[[36,4]]}}}],["technolog",{"_index":290,"t":{"518":{"position":[[9,12]]}}}],["templat",{"_index":4,"t":{"4":{"position":[[0,8]]}}}],["test",{"_index":12,"t":{"10":{"position":[[32,4]]},"28":{"position":[[44,7]]},"32":{"position":[[19,5]]},"139":{"position":[[5,5]]},"194":{"position":[[5,5],[25,5]]},"196":{"position":[[20,5]]},"198":{"position":[[8,4]]},"202":{"position":[[10,5]]},"204":{"position":[[15,5]]},"206":{"position":[[9,4]]},"212":{"position":[[0,7]]},"215":{"position":[[0,7]]},"221":{"position":[[0,7]]},"233":{"position":[[28,4]]},"235":{"position":[[11,5]]},"321":{"position":[[8,4]]},"322":{"position":[[5,5]]},"328":{"position":[[0,4]]},"330":{"position":[[0,7]]},"333":{"position":[[24,5]]},"335":{"position":[[20,5]]},"337":{"position":[[0,7]]},"341":{"position":[[5,5],[26,5]]},"343":{"position":[[5,5]]},"345":{"position":[[8,5]]},"347":{"position":[[9,5]]},"349":{"position":[[11,5]]},"351":{"position":[[4,5]]},"363":{"position":[[0,4]]},"404":{"position":[[0,4]]}}}],["testid",{"_index":88,"t":{"105":{"position":[[5,7]]},"208":{"position":[[5,7]]}}}],["text",{"_index":75,"t":{"87":{"position":[[0,4]]}}}],["throw",{"_index":136,"t":{"166":{"position":[[18,8]]}}}],["timout",{"_index":209,"t":{"333":{"position":[[13,7]]}}}],["tldraw",{"_index":283,"t":{"504":{"position":[[4,6]]},"513":{"position":[[0,6]]},"523":{"position":[[7,6]]}}}],["topic",{"_index":266,"t":{"448":{"position":[[7,6]]}}}],["tree",{"_index":110,"t":{"132":{"position":[[44,5]]}}}],["ts",{"_index":89,"t":{"107":{"position":[[0,2]]}}}],["tsp",{"_index":276,"t":{"481":{"position":[[0,3]]}}}],["tspsyncstrategi",{"_index":273,"t":{"469":{"position":[[0,15]]}}}],["type",{"_index":128,"t":{"156":{"position":[[29,5]]},"184":{"position":[[0,5]]},"188":{"position":[[24,4]]},"554":{"position":[[16,4]]}}}],["typescript",{"_index":154,"t":{"200":{"position":[[4,10]]}}}],["ubuntu/wsl",{"_index":77,"t":{"89":{"position":[[13,12]]}}}],["ui",{"_index":107,"t":{"128":{"position":[[32,2]]}}}],["undo",{"_index":244,"t":{"400":{"position":[[0,4]]}}}],["unit",{"_index":115,"t":{"139":{"position":[[0,4]]},"194":{"position":[[0,4]]},"341":{"position":[[0,4]]}}}],["up",{"_index":35,"t":{"28":{"position":[[11,2]]}}}],["updat",{"_index":228,"t":{"367":{"position":[[0,8]]}}}],["updates/stor",{"_index":278,"t":{"492":{"position":[[8,15]]}}}],["upload",{"_index":281,"t":{"497":{"position":[[6,6]]}}}],["url",{"_index":40,"t":{"28":{"position":[[73,4]]}}}],["us",{"_index":8,"t":{"8":{"position":[[3,5]]},"10":{"position":[[3,5]]},"116":{"position":[[0,3]]},"128":{"position":[[0,5]]},"156":{"position":[[0,5]]},"160":{"position":[[0,5]]},"198":{"position":[[0,3]]},"200":{"position":[[0,3]]},"204":{"position":[[21,5]]},"274":{"position":[[8,3]]},"347":{"position":[[0,3]]},"504":{"position":[[11,4]]},"564":{"position":[[7,3]]},"566":{"position":[[3,3]]}}}],["usag",{"_index":93,"t":{"113":{"position":[[0,5]]},"464":{"position":[[0,5]]},"479":{"position":[[0,5]]},"489":{"position":[[0,5]]}}}],["user",{"_index":38,"t":{"28":{"position":[[52,4]]},"162":{"position":[[0,4]]}}}],["util",{"_index":153,"t":{"198":{"position":[[13,5]]},"337":{"position":[[8,9]]}}}],["valid",{"_index":175,"t":{"255":{"position":[[0,10]]}}}],["valu",{"_index":207,"t":{"331":{"position":[[28,6]]}}}],["variabl",{"_index":37,"t":{"28":{"position":[[26,9]]}}}],["vs",{"_index":149,"t":{"194":{"position":[[11,3]]},"341":{"position":[[11,2]]}}}],["vue",{"_index":87,"t":{"101":{"position":[[12,3]]},"173":{"position":[[41,3]]},"198":{"position":[[4,3]]}}}],["vuetifi",{"_index":86,"t":{"101":{"position":[[0,7]]}}}],["vuex",{"_index":162,"t":{"227":{"position":[[8,4]]}}}],["w3c",{"_index":81,"t":{"99":{"position":[[0,3]]}}}],["wai",{"_index":85,"t":{"99":{"position":[[33,5]]}}}],["web",{"_index":82,"t":{"99":{"position":[[4,3]]}}}],["what'",{"_index":53,"t":{"48":{"position":[[0,6]]}}}],["whiteboard",{"_index":292,"t":{"525":{"position":[[11,11]]}}}],["window",{"_index":201,"t":{"317":{"position":[[0,7]]}}}],["work",{"_index":120,"t":{"150":{"position":[[0,7]]}}}],["write",{"_index":1,"t":{"3":{"position":[[3,7]]},"12":{"position":[[3,7]]},"16":{"position":[[3,7]]},"18":{"position":[[3,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":2,"t":"This guide provides coding conventions and best practices for writing feature files, naming folders, files, methods, and step definitions. Following these conventions will ensure consistency and maintainability across the test framework.","s":"Code Conventions","u":"/docs/e2e-system-tests/CodeConventions","h":"","p":1},{"i":5,"t":"Use the following template when creating a feature file: Feature: Scenario: Given When Then ","s":"Template Structure","u":"/docs/e2e-system-tests/CodeConventions","h":"#template-structure","p":1},{"i":7,"t":"\"When\" statements describe actions or interactions with the application. \"Then\" statements describe the expected results of those actions. Multiple \"When\" statements can precede a single \"Then\" statement: When When Then It is acceptable to have multiple \"Then\" statements: When Then Then Avoid using \"And\" statements. Use only \"When\" and \"Then\" for clarity.","s":"Guidelines","u":"/docs/e2e-system-tests/CodeConventions","h":"#guidelines","p":1},{"i":9,"t":"Leveraging Example Tables with Scenario Outline When you need to run the same scenario with different sets of test data, use Scenario Outline along with Example Tables. This approach makes your test cases more efficient and maintainable. Scenario Outline allows you to define steps with placeholders () that are substituted with values from the Examples table. Example Usage Feature: Create and delete a class As a teacher, I want to create and delete classes so that I can manage my courses effectively. Scenario Outline: Teacher creates and deletes a class Given a user with the role \"\" When the user creates a class named \"\" Then the class \"\" should be visible in the list When the user deletes the class \"\" Then the class \"\" should no longer exist Examples: | role | className | | teacher | Math101 | | teacher | Science202 | | admin | History300 | Explanation The Scenario Outline defines the test steps using parameters like and . The Examples table provides multiple sets of data for each parameter. Each row in theExamples table will generate a separate test case with the specified values. Benefits of Using Scenario Outlines Reusability: Allows you to reuse the same test steps for multiple sets of test data. Maintainability: Reduces code duplication and makes it easier to update test data. Clarity: Keeps the .feature file organized and easy to read.","s":"2. Using Parameters in Feature Files","u":"/docs/e2e-system-tests/CodeConventions","h":"#2-using-parameters-in-feature-files","p":1},{"i":11,"t":"In addition to Scenario Outlines, you can directly use parameters within feature files to specify dynamic values, such as task titles, or other context-specific data. Example: Feature: User login Scenario: Valid user login Given a user with the username \"testUser\" and password \"Password123\" When the user logs in Then the user should be redirected to the dashboard In this case, parameters are directly embedded within the scenario steps without an Examples table.","s":"3. Using Parameters for Special Test Data","u":"/docs/e2e-system-tests/CodeConventions","h":"#3-using-parameters-for-special-test-data","p":1},{"i":13,"t":"Step Definitions Example Ensure that your step definitions are designed to handle the dynamic parameters from your feature files: Given('a user with the role {string}', role => { cy.loginAsRole(role) }) When('the user creates a class named {string}', className => { cy.createClass(className) }) Then('the class {string} should be visible in the list', className => { cy.verifyClassExists(className) }) Then('the class {string} should no longer exist', className => { cy.deleteClass(className) cy.verifyClassDeleted(className) }) Explanation The {string} syntax captures the parameter from the feature file. Each step is linked to the corresponding scenario, making your tests highly modular and reusable.","s":"4. Writing Step Definitions for Scenario Outlines","u":"/docs/e2e-system-tests/CodeConventions","h":"#4-writing-step-definitions-for-scenario-outlines","p":1},{"i":15,"t":"Folder Names Use snake_case for longer folder names. Example: common_logins Feature File Names Syntax: verb + noun Example: createCourse.feature Page Object File Names Syntax: page + noun Example: pageCourse.js, pageCommonCourse.js Class Names Inside Page Objects Syntax: FirstWord_SecondWord Example: Course, Course_Common Method Names Inside Classes Syntax: verb + noun (CamelCase) Example: fillCourseCreationForm() Variable Names Syntax: verb + noun or noun + verb (CamelCase) Example: userEmail, loginButton Data-testid Naming Convention Test IDs are used to select elements on the web page, including buttons, input fields, and sections. Syntax: firstword-secondword-thirdword Example: content-card-task-menu-edit-icon Usage: Assign test IDs to elements using: ","s":"5. Naming Conventions","u":"/docs/e2e-system-tests/CodeConventions","h":"#5-naming-conventions","p":1},{"i":17,"t":"Guidelines Assign test data IDs to a static variable with a # prefix so it indicate as a private variable within the class. Break down complex interactions into smaller methods within the class for better modularity. Refer to examples in the current end to end repository for guidance.","s":"6. Writing Classes and Methods","u":"/docs/e2e-system-tests/CodeConventions","h":"#6-writing-classes-and-methods","p":1},{"i":19,"t":"Step Definition Folder Structure Create a step definition file within cypress/support/step_definitions/ based on the module name (e.g., rooms, courses, teams). The naming convention for step definition files is based on the module name, followed by .spec.js. Example: editCourseSteps.spec.js commonCourseSteps.spec.js (for shared steps across multiple scenarios) Guidelines Follow the same sequence as the feature file for consistency. Create one common step definition file that can be reused across tests within the same module or across modules. For module-specific step definitions, comment out any common steps and include a reference to their location. Example 1: Dedicated Module Step Definitions // editCourseSteps.spec.js Given('a user is logged in', () => { ... }); When('the user edits the course details', () => { ... }); Then('the course should be updated', () => { ... }); Example 2: Referencing Common Step Definitions // commonCourseSteps.spec.js Given('a user is logged in', () => { ... }); // editCourseSteps.js // Commented out common steps with reference // Refer to: commonCourseSteps.spec.js When('the user edits the course details', () => { ... }); Then('the course should be updated', () => { ... });","s":"7. Writing Step Definitions","u":"/docs/e2e-system-tests/CodeConventions","h":"#7-writing-step-definitions","p":1},{"i":21,"t":"Use consistent folder and file names as per the naming conventions above. Keep the user journey sequence the same in both .feature files and step definition files to enhance readability.","s":"8. Additional Best Practices","u":"/docs/e2e-system-tests/CodeConventions","h":"#8-additional-best-practices","p":1},{"i":23,"t":"This section provides instructions for setting up the Cypress-Cucumber test environment to ensure a smooth onboarding process.","s":"Getting Started","u":"/docs/e2e-system-tests/GettingStarted","h":"","p":22},{"i":25,"t":"Before getting started, ensure the following tools are installed: Node.js: Download Node v18 Git: Download Git Browser: (Recommended: Microsoft Edge) Download Edge Browser IDE: Choose any IDE (Recommended: VS Code) Optional Tools: GitHub Desktop App Recommended VS Code Extensions: Cucumber (Gherkin) Full Support EditorConfig Prettier","s":"1. Pre-requisites","u":"/docs/e2e-system-tests/GettingStarted","h":"#1-pre-requisites","p":22},{"i":27,"t":"To get the project files locally, follow these steps: git clone cd Make sure you have access to the repository using your organization's credentials.","s":"2. Cloning the Repository","u":"/docs/e2e-system-tests/GettingStarted","h":"#2-cloning-the-repository","p":22},{"i":29,"t":"Setting Up Environment Variables for Dev Environment/Cluster: Duplicate the file devTemplate.env.json and rename the duplicated file to local.env.json inside the env_variables folder. Include the required development namespace URLs for BRB/DBC/NBC. Test user data on development clusters are created using the school API. To retrieve the API keys for all three namespaces, navigate to 1Password (1PW). Contact QA team for the necessary 1Password links. Setting Up Environment Variables for Staging Environment/Cluster: Duplicate the file stagingTemplate.env.json and rename the duplicated file to staging.env.json in the env_variables folder. Include the required staging namespace URLs for BRB/DBC/NBC. Test data on the staging environment are fetched from the seed data on the server. Add the environment-specific credentials to staging.env.json from 1Password (1PW). Ensure all instances are included, as 1Password contains different vaults for each namespace with testing credentials. Contact QA team for the necessary 1Password links.","s":"3. Setting Up Environment Variables for the Testing User Credentials and URLs","u":"/docs/e2e-system-tests/GettingStarted","h":"#3-setting-up-environment-variables-for-the-testing-user-credentials-and-urls","p":22},{"i":31,"t":"Use the following command to install all necessary project dependencies: npm ci","s":"4. Installing Dependencies","u":"/docs/e2e-system-tests/GettingStarted","h":"#4-installing-dependencies","p":22},{"i":33,"t":"Once the setup is complete, you can run the tests: To run all tests in headless mode: npm run cy:headless:stable:local To run tests interactively in the Cypress UI: npm run cy:gui:stable:regression:staging:local For more details on additional configurations and test options, refer to the Executing Tests Guide section in README.","s":"5. Running Cypress Tests","u":"/docs/e2e-system-tests/GettingStarted","h":"#5-running-cypress-tests","p":22},{"i":35,"t":"Understanding the project directory layout will help you navigate and manage the Cypress-Cucumber E2E test framework effectively. This section provides a detailed breakdown of the folder structure and the purpose of each component.","s":"Project Structure","u":"/docs/e2e-system-tests/ProjectStructure","h":"","p":34},{"i":37,"t":"(root) | |---- .github/ | |____ automatic-trigger.yml # GitHub Actions workflow for automatic triggers | |____ manual-trigger.yml # GitHub Actions workflow for manual runs | |____ scheduled-trigger.yml # GitHub Actions workflow for scheduled runs | |____ main.yml # GitHub Actions workflow for reusable jobs | |---- .vscode/ # Settings for recommended VS Code extensions | |---- env_variables/ | |____ template.env.json # Template for credentials & environment variables (rename as `local.env.json`) | |---- cypress/ | |___ downloads/ # Downloaded files during tests | |___ fixtures/ # Test data files | |___ e2e/ # Gherkin feature files | |___ screenshots/ # Screenshots taken on test failures | |___ support/ | |___ custom_commands/ # Custom Cypress commands used in tests | |___ pages/ # Page Object methods for better test modularity | |___ step_definitions/ # Step definitions for feature files | |___ commands.js # Custom Cypress commands configuration | |___ e2e.js # Global hooks and configurations | |___ videos/ # Recorded test run videos | |---- docs/ | |___ branch_activation.md | |___ folder_structure.md | |___ executing_tests.md | |___ setup.md | |___ tags.md | |---- env_variables/ | |___ devTemplate.env.json | |___ stagingTemplate.env.json | |---- reports/ # HTML reports and related assets | |---- logs/ # Logs generated during test runs | |---- node_modules/ # Project dependencies | |---- scripts/ | |____ aggregate-json-files.sh # Script to aggregate JSON files in CI | |____ runSchoolApi.js # Script to interact with the School API | |---- .editorconfig # Editor configuration for consistent formatting |---- .gitattributes # Git attributes for line endings and diff |---- .prettierignore # Files and folders ignored by Prettier |---- .prettierrc # Prettier configuration for code formatting |---- .gitignore # Git ignore rules |---- reporter.js # Custom reporter for generating HTML reports |---- cypress.config.json # Cypress configuration settings |---- LICENSE # License file |---- package-lock.json # npm package lock file |---- package.json # Project dependencies and scripts |---- README.md # Project documentation and setup guide","s":"Project Directory Layout","u":"/docs/e2e-system-tests/ProjectStructure","h":"#project-directory-layout","p":34},{"i":39,"t":".github/: Contains CI/CD workflows for automated, manual, and scheduled test executions. .vscode/: Recommended settings for VS Code extensions to maintain consistent coding standards. env_variables/: Holds environment configuration files. Duplicate template.env.json and rename it to local.env.json for local testing. cypress/: The main directory for Cypress tests. fixtures/: Stores reusable test data. e2e/: Contains all Gherkin .feature files. support/: Includes custom commands, page objects, and step definitions. videos/ & screenshots/: Captures test artifacts. docs/: Additional documentation for tags, configurations, and best practices. reports/: Contains HTML reports generated after test runs. scripts/: Helpful scripts for CI/CD and API interactions. .prettierrc & .editorconfig: Configuration files to enforce consistent coding styles. cypress.config.json: Central configuration file for Cypress test settings. reporter.js: Custom script to generate detailed HTML reports.","s":"Explanation of Key Directories and Files","u":"/docs/e2e-system-tests/ProjectStructure","h":"#explanation-of-key-directories-and-files","p":34},{"i":41,"t":"This section explains the tagging system used for Cypress and Cucumber tests. Tags help categorize and selectively run tests based on environments, test stability, or purpose.","s":"Tags","u":"/docs/e2e-system-tests/Tags","h":"","p":40},{"i":43,"t":"@stable_test: Tests that are stable and expected to pass in all environments. @regression_test: Tests run before a release to ensure core functionality. @school_api_test: Tests interacting with the school API. @staging_test: Tests specific to the staging environment. @pr: Tests run during the CI process for Pull Requests. @unstable_test: Tests that may fail intermittently due to environmental factors. @group-A / @group-B: Tags for grouping tests in parallel execution. @schedule_run: Tests tagged for scheduled runs in CI.","s":"Tag Descriptions","u":"/docs/e2e-system-tests/Tags","h":"#tag-descriptions","p":40},{"i":45,"t":"Feature-level tags apply to all scenarios within that feature, unless overridden at the scenario level. Examples: @regression_test & @stable_test @regression_test Feature: Account Management @stable_test Scenario: Edit email as an internal user Given I am logged in as an internal user When I navigate to the account settings Then I should see that my email is editable @unstable_test @unstable_test Feature: Add-ons Management Scenario Outline: Access Add-ons page Given I am logged in as '' When I navigate to the Add-ons page Then I should see the Add-ons interface Examples: | user_role | | admin | | teacher | @school_api_test & @staging_test @regression_test @stable_test Feature: User Management Scenario: Admin manages users Given I am logged in as an admin When I add a new user Then the user should appear in the list @school_api_test Examples: School API | user_role | user_email | | admin | admin@school.com | @staging_test Examples: Staging | user_role | user_email | | teacher | teacher@staging.com | @pr @pr Feature: Critical Paths Scenario: Verify homepage loads Given the application is deployed When I navigate to the homepage Then the homepage should load correctly For detailed information on the full usage of tags, please refer to the full documentation in README.","s":"Tag Hierarchy","u":"/docs/e2e-system-tests/Tags","h":"#tag-hierarchy","p":40},{"i":47,"t":"You have just learned the basics of Docusaurus and made some changes to the initial template. Docusaurus has much more to offer! Anything unclear or buggy in this tutorial? Please report it!","s":"Congratulations!","u":"/docs/How to update the docs/congratulations","h":"","p":46},{"i":49,"t":"Read the official documentation Modify your site configuration with docusaurus.config.js Add navbar and footer items with themeConfig Add a custom Design and Layout Add a search bar Find inspirations in the Docusaurus showcase Get involved in the Docusaurus Community","s":"What's next?","u":"/docs/How to update the docs/congratulations","h":"#whats-next","p":46},{"i":51,"t":"Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...","s":"Create a Blog Post","u":"/docs/How to update the docs/create-a-blog-post","h":"","p":50},{"i":53,"t":"Create a file at blog/2021-02-28-greetings.md: blog/2021-02-28-greetings.md --- slug: greetings title: Greetings! authors: - name: Joel Marcey title: Co-creator of Docusaurus 1 url: https://github.com/JoelMarcey image_url: https://github.com/JoelMarcey.png - name: Sébastien Lorber title: Docusaurus maintainer url: https://sebastienlorber.com image_url: https://github.com/slorber.png tags: [greetings] --- Congratulations, you have made your first post! Feel free to play around and edit this post as much you like. A new blog post is now available at http://localhost:3000/blog/greetings.","s":"Create your first Post","u":"/docs/How to update the docs/create-a-blog-post","h":"#create-your-first-post","p":50},{"i":55,"t":"Documents are groups of pages connected through: a sidebar previous/next navigation versioning","s":"Create a Document","u":"/docs/How to update the docs/create-a-document","h":"","p":54},{"i":57,"t":"Create a Markdown file at docs/hello.md: docs/hello.md # Hello This is my **first Docusaurus document**! A new document is now available at http://localhost:3000/docs/hello.","s":"Create your first Doc","u":"/docs/How to update the docs/create-a-document","h":"#create-your-first-doc","p":54},{"i":59,"t":"Docusaurus automatically creates a sidebar from the docs folder. Add metadata to customize the sidebar label and position: docs/hello.md --- sidebar_label: 'Hi!' sidebar_position: 3 --- # Hello This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in sidebars.js: sidebars.js module.exports = { tutorialSidebar: [ 'intro', 'hello', { type: 'category', label: 'Tutorial', items: ['tutorial-basics/create-a-document'], }, ], };","s":"Configure the Sidebar","u":"/docs/How to update the docs/create-a-document","h":"#configure-the-sidebar","p":54},{"i":61,"t":"Add Markdown or React files to src/pages to create a standalone page: src/pages/index.js → localhost:3000/ src/pages/foo.md → localhost:3000/foo src/pages/foo/bar.js → localhost:3000/foo/bar","s":"Create a Page","u":"/docs/How to update the docs/create-a-page","h":"","p":60},{"i":63,"t":"Create a file at src/pages/my-react-page.js: src/pages/my-react-page.js import React from 'react'; import Layout from '@theme/Layout'; export default function MyReactPage() { return (

    My React page

    This is a React page

    ); } A new page is now available at http://localhost:3000/my-react-page.","s":"Create your first React Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-react-page","p":60},{"i":65,"t":"Create a file at src/pages/my-markdown-page.md: src/pages/my-markdown-page.md # My Markdown page This is a Markdown page A new page is now available at http://localhost:3000/my-markdown-page.","s":"Create your first Markdown Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-markdown-page","p":60},{"i":67,"t":"Docusaurus is a static-site-generator (also called Jamstack). It builds your site as simple static HTML, JavaScript and CSS files.","s":"Deploy your site","u":"/docs/How to update the docs/deploy-your-site","h":"","p":66},{"i":69,"t":"Build your site for production: npm run build The static files are generated in the build folder.","s":"Build your site","u":"/docs/How to update the docs/deploy-your-site","h":"#build-your-site","p":66},{"i":70,"t":"Test your production build locally: npm run serve The build folder is now served at http://localhost:3000/. You can now deploy the build folder almost anywhere easily, for free or very small cost (read the Deployment Guide).","s":"Deploy your site","u":"/docs/How to update the docs/deploy-your-site","h":"#deploy-your-site-1","p":66},{"i":72,"t":"Docusaurus supports Markdown and a few additional features.","s":"Markdown Features","u":"/docs/How to update the docs/markdown-features","h":"","p":71},{"i":74,"t":"Markdown documents have metadata at the top called Front Matter: my-doc.md --- id: my-doc-id title: My document title description: My document description slug: /my-custom-url --- ## Markdown heading Markdown text with [links](./hello.md)","s":"Front Matter","u":"/docs/How to update the docs/markdown-features","h":"#front-matter","p":71},{"i":76,"t":"Regular Markdown links are supported, using url paths or relative file paths. Let's see how to [Create a page](/create-a-page). Let's see how to [Create a page](./create-a-page.md). Result: Let's see how to Create a page.","s":"Links","u":"/docs/How to update the docs/markdown-features","h":"#links","p":71},{"i":78,"t":"Regular Markdown images are supported. You can use absolute paths to reference images in the static directory (static/img/docusaurus.png): ![Docusaurus logo](/img/docusaurus.png) You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: ![Docusaurus logo](./img/docusaurus.png)","s":"Images","u":"/docs/How to update the docs/markdown-features","h":"#images","p":71},{"i":80,"t":"Markdown code blocks are supported with Syntax highlighting. ```jsx title=\"src/components/HelloDocusaurus.js\" function HelloDocusaurus() { return (

    Hello, Docusaurus!

    ) } ``` src/components/HelloDocusaurus.js function HelloDocusaurus() { return

    Hello, Docusaurus!

    ; }","s":"Code Blocks","u":"/docs/How to update the docs/markdown-features","h":"#code-blocks","p":71},{"i":82,"t":"Docusaurus has a special syntax to create admonitions and callouts: :::tip My tip Use this awesome feature option ::: :::danger Take care This action is dangerous ::: My tip Use this awesome feature option Take care This action is dangerous","s":"Admonitions","u":"/docs/How to update the docs/markdown-features","h":"#admonitions","p":71},{"i":84,"t":"MDX can make your documentation more interactive and allows using any React components inside Markdown: export const Highlight = ({children, color}) => ( { alert(`You clicked the color ${color} with label ${children}`) }}> {children} ); This is Docusaurus green ! This is Facebook blue ! This is Docusaurus green ! This is Facebook blue !","s":"MDX and React Components","u":"/docs/How to update the docs/markdown-features","h":"#mdx-and-react-components","p":71},{"i":86,"t":"You can use following package and command to detect dependency cycles in code. https://www.npmjs.com/package/madge","s":"Detect Dependency Cycles","u":"/docs/Informations/detect-dependency-cycles","h":"","p":85},{"i":88,"t":"npx madge --extensions js,ts --circular .","s":"text export","u":"/docs/Informations/detect-dependency-cycles","h":"#text-export","p":85},{"i":90,"t":"apt-get install graphviz npx madge --extensions js,ts --circular --image graph.svg .","s":"image export (Ubuntu/Wsl)","u":"/docs/Informations/detect-dependency-cycles","h":"#image-export-ubuntuwsl","p":85},{"i":92,"t":"as graphic​ npx madge --image graph_server.svg dist/apps/server/apps/server.app.js npx madge --circular --image graph_server_circular.svg dist/apps/server/apps/server.app.js npx madge --exclude '^(?!.entity).$' --image graph_server_entities.svg dist/apps/server/apps/server.app.js npx madge --circular --exclude '^(?!.entity).$' --image graph_server_circular_entities.svg dist/apps/server/apps/server.app.js as text​ npx madge --json dist/apps/server/apps/server.app.js >> output.json","s":"examples","u":"/docs/Informations/detect-dependency-cycles","h":"#examples","p":85},{"i":94,"t":"https://github.com/jmcdo29/nestjs-spelunker https://sanyamaggarwal.medium.com/automate-circular-dependency-detection-in-your-node-js-project-394ed08f64bf","s":"more solutions","u":"/docs/Informations/detect-dependency-cycles","h":"#more-solutions","p":85},{"i":96,"t":"The schulcloud consists of many repositories.","s":"Schulcloud Documentation","u":"/docs/intro","h":"","p":95},{"i":98,"t":"We want to make sure that our product can be used by anyone. This includes people with disabilities as well as people that have a slow connection, outdated or broken hardware or people that just have an unfavorable environment.","s":"Accessibility (A11y)","u":"/docs/nuxt-client/Accessibility","h":"","p":97},{"i":100,"t":"The WAI develops strategies, standards, resources to make the web accessibile. An introduction to Accessibility can be found here: Introduction to Web Accessibilty The WAI ARIA is the Acessible Rich Internet Applications suite of web standards. It makes Web content and Web applications more accessible with adding attributes to identify features for user interaction and enable e.g. keyboard users to move among regions. A recommended approach using WAI-ARIA roles, states and properties can be found here: ARIA Authoring Practices Guide (APG)","s":"W3C Web Accessibility Initiative (WAI)","u":"/docs/nuxt-client/Accessibility","h":"#w3c-web-accessibility-initiative-wai","p":97},{"i":102,"t":"We want to use Vuetfiy Components in our project. They provide key interaction for all mouse-based-actions and utilize HTML5 semantic elements where applicable. See: Vuetify A11y There is also a Accessibility Chapter in the Best Practices of the Vue Docs: Vue A11y // TODO: link to good tutorial on how to test a11n // REMINDER: If we establish special a11n-components and/or tools - they should be described here","s":"Vuetify and Vue","u":"/docs/nuxt-client/Accessibility","h":"#vuetify-and-vue","p":97},{"i":104,"t":"Code Conventions data-testids ts-ignore-comments composables","s":"Code Conventions","u":"/docs/nuxt-client/CodeConventions","h":"","p":103},{"i":106,"t":"Please use
    in your HTML-code if you want to define a data-testid. do not use uppercase-characters only use one dash - right after data We also recommend to use refs instead of data-testids. But if you do that, you need to be careful when removing them... as they could be used in the component-code AND in tests: VueJs - template refs VueTestUtils - ref Also look here: Frontend Arc Group: Meeting Notes 2022-11-04","s":"data-testids","u":"/docs/nuxt-client/CodeConventions","h":"#data-testids","p":103},{"i":108,"t":"Everybody should try to avoid // @ts-ignore and try his/her best to define the types of variables in TypeScript files. Also look here: Frontend Arc Group: Meeting Notes 2022-10-28","s":"ts-ignore-comments","u":"/docs/nuxt-client/CodeConventions","h":"#ts-ignore-comments","p":103},{"i":110,"t":"Composables are a great way to make our code more reusable and to extract code from components. If you want to write a composable, consider using one of these well documented and well tested ones: VueUse - Collection of Vue Composition Utilities If you write a composable: it should have the extension .composable.ts should be placed in your feature folder (see section \"directory structure\" above), if it is only used inside of your feature should be placed in the global folder / src / composables, if it is used in multiple features","s":"composables","u":"/docs/nuxt-client/CodeConventions","h":"#composables","p":103},{"i":112,"t":"In the Material Design system (the foundation of our component library), colors and color schemes are used to create a visual hierarchy, direct focus, and enhance the user experience. You can find our custom defined theme colors under /src/themes/base-vuetify.options.ts and their overwrites per theme in /src/themes//vuetify.options.ts. You can find the colors provided by Vuetify here.","s":"Colors","u":"/docs/nuxt-client/Colors","h":"","p":111},{"i":115,"t":"All colors defined by Vuetify or in our Vuetify options generate CSS classes you can use. To apply a color variant like lighten-1, add it to the color like grey-lighten-1. Backgrounds have the bg-prefix and texts the text-prefix. Examples​ Using a color from Vuetify's color palette:
    Blue background
    Using a color defined in our vuetify options as text color:

    Text has a red color

    To use a variant of a color, you have to add the name of the variant seperated by hyphens:

    Text has a darken variant of the red color

    ","s":"Color Classes","u":"/docs/nuxt-client/Colors","h":"#color-classes","p":111},{"i":117,"t":"For colors defined in our Vuetify options, Vuetify generates CSS variables. Now, custom properties are an rgb list, so we need to use rgba() to access them. Examples​ .alert { background-color: rgba(var(--v-theme-primary-lighten-1)); color: rgba(var(--v-theme-primary)); } In Vuetify 2, we could only use hex values without the alpha property. With Vuetify 3, it's now possible: .example{ background-color: rgba(var(--v-theme-primary), 0.12); } Colors from Vuetify's colors palette (as of now) do not get generated as CSS variables. You will need to access them with map-get. .alert { background-color: map-get($grey, base); color: map-get($blue, lighten-3); }","s":"Use Colors in (S)CSS","u":"/docs/nuxt-client/Colors","h":"#use-colors-in-scss","p":111},{"i":119,"t":"\"On\" colors are important for making text, icons, and other elements recognizable and readable on various backgrounds. on-surface: Used for text, icons, and other elements that appear on top of a surface. Surfaces can include components like cards, dialogs, and menus. on-background: Used for text, icons, and other elements that appear on the primary background of an application or a component We override the standard on-surface and on-background vuetify colors in our vuetify options and define them for each theme.","s":"\"On-Surface\" and \"On-Background\" Colors","u":"/docs/nuxt-client/Colors","h":"#on-surface-and-on-background-colors","p":111},{"i":121,"t":"You can define more custom colors in our vuetify options like this: ... colors: { info: \"#0a7ac9\", \"icon-btn\": colors.grey.darken3, \"on-surface\": \"#0f3551\", } ...","s":"Definition Of Custom Colors","u":"/docs/nuxt-client/Colors","h":"#definition-of-custom-colors","p":111},{"i":123,"t":"Talk to UX before introducing a new color Do not overwrite vuetify colors Use a semantic name to represent the use case Prefer usage via map-get over new color definition, unless you introduce a new color Either define style in template or in SCSS","s":"Rules","u":"/docs/nuxt-client/Colors","h":"#rules","p":111},{"i":126,"t":"Imagine writing a basic component to add reusable buttons to your app. The first iteration might look like this when using it: The next step might be adding a way to set the button label. Careful! The label-prop is just a string. This will limit your Button to only being able to have text-based Labels in the future. It is a lot less flexible because the power of HTML was removed completely. Compare it to this button: MyButton The label stays within the realm of HTML and we don't lose any HTML capabilities: Two Line
    Button!
    Button with in the label! Both of these examples are (almost) impossible with a prop-based label. Rule: Readable text should be HTML and not a string-prop.","s":"HTML is not a string","u":"/docs/nuxt-client/ComponentGuidelines","h":"#html-is-not-a-string","p":124},{"i":129,"t":"Let's build a simple vertical-menu with two clickable options and add more and more requirements as we go. note Requirements Menu with two clickable options
    • Option 1
    • Option 2
    note Updated Requirements Menu with two clickable options Menu with any number of clickable options To make our menu reusable, one approach might be to add an options prop: This approach has two major problems: First you probably already notice that we demoted the label from HTML to being just a string again. (See: HTML is not a string) Second we abstracted the structure of our menu into an Array. This replaces perfectly good HTML with a datastructure. Take a look at this HTML-based approach: Option 1 Option 2 The original HTML-structure is preserved and only the default elements (ul, li, button) are abstracted in their own components. This leaves a lot of flexibility to interact with the structure (e.g. toggling options with v-if) while still making sure that the rendered output is valid. Additionally, this is much easier to test since we do not have to deal with datastructures. note Updated Requirements Menu with any number of clickable options Menu-Options can be colored Any number of Menu-Dividers can be placed at any position in the menu Adding these new requirements in the HTML approach is very straightforward. We just have to add a prop to each my-menu-option to pick a color. Then we create a new my-menu-divider component. Expanding the datastructure to support colors is easy, we just have to add a color-property. But the divider will be an actual problem. So far the datastructure was created to represent buttons. By adding the divider config object we will lose any uniformity of our config data. This will make it difficult to read, complicated to test und generally annoying to maintain. Compare the two solutions in code: Option 1 Option 2 We can already see the datastructure approach falling apart. For complex menus this will be completely ineligible and difficult to understand. Let's add more requirements to get closer to a real world menu. note New Requirements Menu with any number of clickable options Menu-Options can be colored Any number of Menu-Dividers can be placed at any position in the menu Menu-Options should have a disabled state Menu-Options can be a button or link Menu-Options can be nested dropdowns Option 1 Option 2 Link 1 😅 Rule: Use Slots and small subcomponents to create robust and flexible features. Rule: Do not use datastructures to represent HTML.","s":"Using slots for highly flexible ui components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#using-slots","p":124},{"i":131,"t":"We often have to deal with complex data that we want to show to the user. Take a look at this simplified example: const users: User[] = [ { id: 1, name: 'User 1', email: 'user1@example.com' } { id: 2, name: 'User 2', email: 'user2@example.com' } ] note Requirements Display the User Array in a table
    ID Name Email
    {{user.id}} {{user.name}} {{user.email}}
    This template will quickly get large and hard to understand if we were to add styling, more fields of our user object or even interactions like editing or deleting entries. How can we easily split up the template? Destructuring Data​ A rule of thumb can be to not handle more than one level of your data structure in a single component. The User object consists of three levels: Array Object Property We can use this list to create subcomponents for the table: UserTable the host component where all components come together This will also be the outside Api of our implementation UserTableBody Component responsible for the Array-level of our data UserTableRow Component responsible for the Object-level of our data UserTableHeader Encapsulate Splitting up the table into these sub-components keeps the template short and less complex. It also makes testing much easier since each level is only concerned about a certain part of the complexity in the data. note New Requirements Display the User Array in a table Deleting users should be possible To add the new interaction button we will have to place it at the end of each row. To have a more pronounced structure we will place this button in a new component UserTableActions. This creates a well defined place for adding more actions in the future. We also have to expand UserTableHead by one . Adding this action also reveals the biggest disadvantage of this approach: We have to pass the emit all the way up to UserTable component. Since all children of UserTable are ui-components they cannot access any state or the api. Updated Requirements Display the User Array in a table Deleting users should be possible Delete button should be disabled while an async request is pending Let's ignore state interactions for this example. But to fulfil this requirement we will add a disabled-prop to UserTable so that our outside logic can disable the buttons while requests are pending. But how do we deal with this internally. Option 1 - Passing the prop​ We can pass the disabled value through our whole component tree. That is a completely valid and comparatively easy solution but it can quickly create a lot of boilerplate. Option 2 - provide/inject​ Vue Docs: Prop-Drilling & provide/inject. This can safe some development time since we don't have to deal with all the boilerplate of Option 1 but it can also lead to a mess of injections if not used carefully. Since this table and it's children are already heavily dependant on each other (they serve one shared purpose: Displaying a User-Table) we can use prop-drilling if we keep the injection-key as a private property of the module. Updated Requirements Display the User Array in a table Deleting users should be possible Delete button should be disabled while an async request is pending The Email should be a mailto-Link Remember the three levels of our data: Array > Object > Property. So far we have destructured the Array and Object levels into separate components. The new requirement could be implemented like this: While this template is still easy to understand in this simplified example, if we imagine a table with over 10 columns and a few lines of HTML for each table-cell we can see that this will get messy quite quickly. A great possibility to keep the template clean is to destructure one more level, down to the Property: Note that we did not create components for the id and name properties. Since they are not handled any differently they can just be shown using interpolation. Rule: Create a sub-component for each meaningful level in your data Rule: Use provide/inject of props only in small and defined scopes","s":"Destructure data over multiple components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#destructure-data-in-component-trees","p":124},{"i":133,"t":"Destructuring data over components can lead to many small components and picking meaningful names can become a challenge. To find a name without much effort whenever I am creating components I use a pattern-based approach: Let's analyze the naming in the UserTable example to illustrate the pattern: UserTable The root component of the implementation. Its name consists of the feature identifier UserTable and nothing else. UserTableHead, UserTableBody and UserTableRow The children of the root component are named by the feature identifier and their appriopriate levels in the table: Head, Body and Row. They are still quite general components and do not need a specific description since they are unique to their levels. UserTableCellEmail This is a highly specific component and therefore includes the feature identifier, the level and a specific description to reflect its specific usecase. Following this pattern makes it quite easy to name things while destructuring. It also leads to a well organized folder in the workspace explorer since components on the same level will be listed closely together - e.g. all UserTableCell-components have the same \"prefix\". The most difficult part in my experience is finding a good name for the level-part of the name. I usually try to use names that reflect the component as an HTML-Element: names of the part of a table, list and list-item for list-structures or option when dealing with dropdowns etc.","s":"Naming components in destructured component trees","u":"/docs/nuxt-client/ComponentGuidelines","h":"#naming-in-destructured-trees","p":124},{"i":136,"t":"Note: Please don't use yarn !!! We decided to use npm across all of our repositories. In order to run this client, you need to have the legacy-client and schulcloud-server set up and running. See for documentation on how to do that in the respective repositories.","s":"Development Setup","u":"/docs/nuxt-client/GettingStarted","h":"#development-setup","p":134},{"i":138,"t":"Clone the repository git clone git@github.com:hpi-schul-cloud/nuxt-client.git Install the required dependencies: npm ci Start the development server: npm run serve By default the server will listen on the URL http://localhost:4000","s":"Start the Server","u":"/docs/nuxt-client/GettingStarted","h":"#start-the-server","p":134},{"i":140,"t":"# Run all (unit) tests npm run test","s":"Unit Tests","u":"/docs/nuxt-client/GettingStarted","h":"#unit-tests","p":134},{"i":142,"t":"npm run lint","s":"Lint","u":"/docs/nuxt-client/GettingStarted","h":"#lint","p":134},{"i":144,"t":"We are using Visual Studio Code as our default deveopment-IDE. In /.vscode you can find two templates to setup your IDE: launch.default.json (copy its content and us it in launch.json) settings.default.json (copy its content and us it in settings.json) For a list of recommended Visual Studio Code extensions please refer to extensions.json.","s":"Editor Setup","u":"/docs/nuxt-client/GettingStarted","h":"#editor-setup","p":134},{"i":146,"t":"Each change should be done in a Ticket (no matter how small). We use a Feature Branch model. Start a branch from main and make a PR to main. Branch naming: {{ PROJECT_ABBREVIATION }}-{{ NUMBER }}-word1-word2-word2 e.g.: BC-1234-course-copy We try to keep branch names small. The Ticket Number should be in Uppercase (e.g BC-1234) but the namespace should be in lowercase. It should stay below 64 letters.","s":"Git Conventions","u":"/docs/nuxt-client/GitConventions","h":"","p":145},{"i":148,"t":"Pull Requests must contain a relevant description (template provides useful information, when creating the PR). In case of UI changes also put a screenshot and talk to UX if thats fine like it is. All Pull Requests Criterias (as defined in deployment pipeline) must be green before merge, e.g. 1 approving review, unit tests or QA checkbox in PR template. We merge by squash strategy. The squashed commit subject should start with a ticket number and end with a PR number. Write commit messages in imperative and active. Example: BC-1993 - lesson lernstore and geogebra copy (#3532) In order to make sure developers in the future can find out why changes have been made, we would like some descriptive text here that explains what we did and why. - change some important things - change some other things - refactor some existing things # We dont need to mention tests, changes that didnt make it to main, linter, or other fixups # only leave lines that are relevant changes compared to main # comments like this will not actually show up in the git history Note for working with Windows: We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.","s":"Pull Requests","u":"/docs/nuxt-client/GitConventions","h":"#pull-requests","p":145},{"i":151,"t":"As we work with Material Design Icons (MDI) UX regularly chooses the icons we are going to use. One example to find these icons and how they are represented can be found here. As UX works with Figma you can also easily find the name of the chosen icon directly in Figma with the so called Dev Mode. You can easily access this mode by using the toggle and then see the name of the icon in the left sidebar (see screenshot). Afterwards you can either search for the icon on the page above (by using hyphens between words) or directly import it from the library into your component.","s":"Working with Material Design Icons","u":"/docs/nuxt-client/HintsForWorking","h":"#working-with-material-design-icons","p":149},{"i":153,"t":"Collection of instructions on how to do certain things: Feature Flags Using generated API and it's types User-Permissions on Pages Exception handling inject - fallback throwing an error","s":"How To","u":"/docs/nuxt-client/HowTo","h":"","p":152},{"i":155,"t":"If there is a new functionality that should only be available on certain systems, we introduce new FEATURE-Flags into the SchulCloud-Backend and into the dof-repository, that contains the configuration for all our instances. Our Vue-Frontend requests all FEATURE-flags and provides global access to them by using this code (example): import { envConfigModule } from \"@/store\"; if (envConfigModule.getEnv.FEATURE_COPY_SERVICE_ENABLED) { ... }","s":"Feature Flags","u":"/docs/nuxt-client/HowTo","h":"#feature-flags-","p":152},{"i":157,"t":"We are using a generator script to create classes to access the Schulcloud-Backend-API - V3 (so Legacy-Backend endpoints (aka V1) are not covered). These generated classes and methods internally use axios to request data and use generated types - both for the input to the methods and for the returned types. HINT Please use the generated types in your stores and do not redefine the same types. This way consistency between Server and Api-Access stays stable.","s":"Using generated API and it's types","u":"/docs/nuxt-client/HowTo","h":"#using-generated-api-and-its-types-","p":152},{"i":159,"t":"Only if the server-api or the filestore-api has changed, you need to regenerate them using the following npm-scripts: For generating the files to access the server-api please use: npm run generate-client:server The same is implemented for generating the backend-api to our filestore-backend. For generating the files to access the filestore-api please use: npm run generate-client:filestorage Hint For regenerating the clients you need an up-to-date running backend-server running in your environment.","s":"Regenerating the clients","u":"/docs/nuxt-client/HowTo","h":"#regenerating-the-clients","p":152},{"i":161,"t":"The generated APIs can easily be used. Examples can be seen in any current store-implementation - like here: src/store/share-course.ts: import { ShareTokenApiFactory, ShareTokenApiInterface, ShareTokenBodyParams, ShareTokenBodyParamsParentTypeEnum, ShareTokenResponse, } from \"../serverApi/v3/api\"; ... export default class ShareCourseModule extends VuexModule { ... private get shareApi(): ShareTokenApiInterface { return ShareTokenApiFactory(undefined, \"v3\", $axios); } @Action async createShareUrl( payload: SharePayload ): Promise { const shareTokenPayload: ShareTokenBodyParams = { parentType: ShareTokenBodyParamsParentTypeEnum.Courses, parentId: this.courseId, expiresInDays: payload.hasExpiryDate ? 21 : null, schoolExclusive: payload.isSchoolInternal, }; ... const shareTokenResult = await this.shareApi.shareTokenControllerCreateShareToken( shareTokenPayload ); ... } ... }","s":"Using the generated api","u":"/docs/nuxt-client/HowTo","h":"#using-the-generated-api","p":152},{"i":163,"t":"The permissions are controlled by createPermissionGuard middleware method that receives two parameters. The first parameter should contain an array of the userPermission that is required to reach the page. The second parameter is an optional fallback route. If the second parameter isn't provided and the user has no permission to reach the page, an error page (401) is shown. // src/router/routes.ts // with a fallback route { path: \"/your/route\", component: () => import(\"../pages/your.page.vue\"), name: \"yourRouteName\", beforeEnter: createPermissionGuard([\"ADMIN_VIEW\"], \"/yourFallBackRoute\"), }, // without a fallback, // it shows a '401' file if the user doesn't have permissions { path: \"/your/route\", component: () => import(\"../pages/your.page.vue\"), name: \"yourRouteName\", beforeEnter: createPermissionGuard([\"ADMIN_VIEW\", \"SCHOOL_EDIT\"]), },","s":"User-Permissions on Pages","u":"/docs/nuxt-client/HowTo","h":"#user-permissions-on-pages-","p":152},{"i":165,"t":"useApplicationError is a composable providing a typed factory function for creating application errors. A global error handler for putting application errors takes those and puts them into a store and a global error page will display them. Exceptions should be thrown using them - like this: // src/pages/user-migration/UserMigration.page.vue import { useApplicationError } from \"@/composables/application-error.composable\"; const { createApplicationError } = useApplicationError(); throw createApplicationError(HttpStatusCode.BadRequest); // src/router/guards/permission.guard.ts import { useApplicationError } from \"@/composables/application-error.composable\"; import { applicationErrorModule } from \"@/store\"; const { createApplicationError } = useApplicationError(); applicationErrorModule.setError(createApplicationError(401)); Also look here: Meeting Notes 2022-11-25","s":"Exception handling","u":"/docs/nuxt-client/HowTo","h":"#exception-handling-","p":152},{"i":167,"t":"We want to provide a simple factory function that produces a unique, identifiable error, if an inject fails and we want to avoid adding code to your TypeScript-components only to prevent linter errors. The topic will be implemented with this ticket: Jira - BC-2813. It contains a lot of details on that issue. ... Details should be added here. soon... Also look here: Frontend Arc Group: Meeting Notes 2022-12-02","s":"inject - fallback throwing an error","u":"/docs/nuxt-client/HowTo","h":"#inject---fallback-throwing-an-error-","p":152},{"i":170,"t":"Circular depencies are a common issue when working with barrel-files (index.ts). Let's look at a common dependency pattern: In this example there are two Building-Blocks (e.g. folders that have a barrel file) which depend on each other. Using the SharedComponent in both Building-Blocks will result in a circular dependency. That basically means that the compiler can not resolve the order to load the Building-Blocks which causes an error.","s":"What is a circular dependency?","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#what-is-a-circular-dependency","p":168},{"i":172,"t":"The basic gist is: break the circle and separate the shared dependency in a separate module. In this configuration the compiler can find an order to resolve the building-blocks correctly.","s":"Resolving Circular Dependencies","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#resolving-circular-dependencies","p":168},{"i":174,"t":"I recreated the first example error in Vue. When Vue tries to render ComponentA I see the following Error in the console: This can be quite hard to decipher on a first glance but it contains all the info we need to identify the root cause of the circular dependency. Based on the info from the message we can learn that ComponentB \"closed the circle\" by importing SharedComponent. From there we can trace back to see where SharedComponent is exposed and why it depends on ComponentB. In this case it is because they are both imported in ComponentA. Keep in mind that the circular dependency can involve multiple building-blocks.","s":"How to identify Circular Dependencies in Vue","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#how-to-identify-circular-dependencies-in-vue","p":168},{"i":177,"t":"Files should be consistently named like this: file content style filename comment Components PascalCase YourComponent.vue best practice Pinia stores PascalCase BoardCard.store.ts placed in data- modules Composables CamelCase confirmationDialog.composable.ts current convention Utils CamelCase yourArea.util.ts suggestion: move from yourUtil.ts => your.util.ts other files CamelCase yourFilename.ts test files {{ basename }}.unit.ts e.g. YourComponent.unit.ts, yourArea.util.unit.ts","s":"Filenames","u":"/docs/nuxt-client/ProjectStructure","h":"#filenames","p":175},{"i":179,"t":"Folders are written in KebabCase (e.g. feature-board).","s":"Folders","u":"/docs/nuxt-client/ProjectStructure","h":"#folders","p":175},{"i":181,"t":"The project's code is separated into building blocks.","s":"Building Blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#building-blocks","p":175},{"i":183,"t":"A building-block is a \"container\" were we place most of our applications logic and components. Each building-block is defined by an index.ts (Barrel-File) describing it's exported content (public API of a building-block) and a type. Utilizing linting rules and the index.ts we can ensure that each building-block only exposes files which are meant to be used application-wide. This way we achieve a strong separation of concern across the whole application. Our linting rule is based on the following concept: Enforce Project Boundaries | Nx Note: in above documentation libraries are equivalent to building-blocks and tags represent the types defined below. near future: All newer modules, that already follow our naming convention (see link above), will move from \"components/\" into a central \"modules/\" folder with one subfolder for each type of module (page/, data/, feature/, ui/, util/ ).","s":"What is a building-block?","u":"/docs/nuxt-client/ProjectStructure","h":"#what-is-a-building-block","p":175},{"i":185,"t":"There are different types of building blocks each with a different purpose. type example comment Page modules / page / dashboard Contains a subpage of the application. Orchestrates Feature and UI building-blocks. Feature modules / feature / calendar Complex features with stateful / smart components. Usually specialized to fulfill specific roles in the App. Can also contain presentational components that are specialized for this feature. UI modules / ui / forms Stateless / presentational components which get their data via props and emit events. Usually less specialized. Data modules / data / auth State and API-access. Does not contain any visual components. They are the data-sources of all smart components. Util modules / util / form-validators Contains shared low-level code. Hint: currently the modules are all placed under /components/feature-... etc. and will be refactored to the above with this ticket: BC-5513 Matrix of allowed imports​ Each type is only allowed to import modules of some of the other types. Allowed to Import → It is ↓ page feature data ui util page ✔ ✔ ✔ ✔ feature ✔ ✔ ✔ ✔ data ✔ ✔ ui ✔ ✔ util ✔ Type: Page​ A page building-block represents a subpage of the application. It contains the layout component and orchestrates feature and ui building blocks to create a subpage. It can not be imported into any other type of building-block. It is only imported by the vue-router and should be lazy-loaded if possible. Type: Feature​ A feature building-block contains a set of files that represent a business use case in an application. Most of the components of features are stateful / smart components that interact with data sources. This type also contains most of the UI logic, form validation code, etc. Type: UI​ A ui building-block mainly contains Stateless / presentational components which are used all across the application. They don't have access to stores and do not use features in their templates. All data needed for components in ui building-blocks comes from props. Type: Data​ A data building-block contains stores and api-services. It does not contain any view components. They serve as data-sources for feature and page building blocks. Type: Util​ A utility building-block contains low level code used by many building-blocks. Often there is no framework-specific code and the building-block is simply a collection of types, utilities, pure functions, factories or composables. Placed in folder util","s":"Types of building-blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#types-of-building-blocks","p":175},{"i":187,"t":"Starting a new Feature module​ create a new subfolder inside of modules/feature/-folder and give it a speaking name place any files of different purposes (for ui, data and/or util) inside of it. provide a barrel-file index.ts that defines it's exposed API - the functionality that other modules are allowed to use. This should only export the bare minimum. Another module needs access to the data of your feature​ do not export your data-functionality from your feature refactor your feature-module and extract the data part of it into a new data-module use this new data-module in the original feature and the other module that also needs access Note: if you know upfront, which parts will definitly be used outside your feature, implement the data-module upfront Another module needs access to some of your UI-Components that form your feature​ do not export these components simply from your feature if the components are simple / dumb and do not need data-access: consider extracting them into a separate ui-module (example: ui/preview-image) if the components are coupled with some data-functionality but this does not belong to the core of your feature: consider extracting those parts into a separate feature-module (example: feature/alert-list) Note: if you are sure that a part of your feature, will be reused in the project - consider extracting it from your ticket - before implementation - and implement it first.","s":"Life of a feature module","u":"/docs/nuxt-client/ProjectStructure","h":"#life-of-a-feature-module","p":175},{"i":189,"t":"To render this graph in VS-Code markdown preview install this extension: bierner.markdown-mermaid","s":"How to pick the correct type for my Task","u":"/docs/nuxt-client/ProjectStructure","h":"#how-to-pick-the-correct-type-for-my-task","p":175},{"i":191,"t":"How to write valuable, reliable tests, that are easy to maintain.","s":"Writing Tests","u":"/docs/nuxt-client/WritingTests","h":"","p":190},{"i":193,"t":"Writing good tests that cover all aspects of your code, leads to: confidence: to refactor your code higher code quality: as you review your code and identify problems when writing tests well documented code: as your tests describe how your code works and by that to: developer happiness :-)","s":"Basics","u":"/docs/nuxt-client/WritingTests","h":"#basics","p":190},{"i":195,"t":"Unit-Tests​ Unit-Tests are WhiteBox-Tests. So they may use knowledge of internals of the code. They are well suited for testing e.g. composables and stores. Component-Tests​ Component-Tests are BlackBox-Tests. So they are not allowed to use any knowledge of the internals of the component. They ensure the stability of the public interface of the component (aka its methods, props, events etc.). The enable us to refactor the internals of our components later on.","s":"Unit-Tests vs. Component-Tests","u":"/docs/nuxt-client/WritingTests","h":"#unit-tests-vs-component-tests","p":190},{"i":197,"t":"positive tests test the default cases of your code = how it should work negative tests test error-cases or exception-behaviour you need to write both to ensure your component works correctly think of edge-cases that might break your component e.g. when providing input to the component: numbers: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... dates: none existing dates e.g. 30th February 2023, far away future,... strings: umlauts, url-special-characters (?, &, =, \\/\\/: ), very long strings for names, long strings without linebreaks totally incorrect data: e.g. giving a string instead of a number","s":"Positive & negative Tests","u":"/docs/nuxt-client/WritingTests","h":"#positive--negative-tests","p":190},{"i":199,"t":"For testing our Vue-Components we use the Vue Test Utils. Vue Test Utils is a library that provides methods to help you write tests for your Vue components. It provides methods to mount, shallow mount, and render components, as well as methods to simulate events and find elements in the rendered output. Some functionality it provides: mount(): create a wrapper around the component and instantiate it shallowMount(): create a shallow wrapper of the component being tested with childcomponents being mocked setMethods(): mock function on the component setProps(): set a specific set of props on the component findComponent(): finds a component by it's class, name or ref findAllComponents(): finds all components by it's class, name or ref find() / findAll(): search for html elements using html-selectors deprecated for finding Components use findComponent() or findAllComponents() instead setData(): set specific data on the component trigger() + emit(): test events and the flow of data We think the Vue Test Utils-documentation is a valuable resource for learning how to test Vue-Components and a very good starting point on how to test certain aspects of your component. Please have a look at https://test-utils.vuejs.org/guide","s":"Use Vue-Test-Utils","u":"/docs/nuxt-client/WritingTests","h":"#use-vue-test-utils","p":190},{"i":201,"t":"Use TypeScript for your components and for your unit-tests. This way many errors can be prevented early on, as you can detect them already in your IDE.","s":"Use TypeScript","u":"/docs/nuxt-client/WritingTests","h":"#use-typescript","p":190},{"i":203,"t":"Tests should be named after their Component using .unit.ts as the extension: HelloWorld.vue HelloWorld.unit.ts","s":"Name your tests like your components","u":"/docs/nuxt-client/WritingTests","h":"#name-your-tests-like-your-components","p":190},{"i":205,"t":"Especially in large test-files it is very helpful for the reader to have a tree-like structure grouping the tests. So use describe blocks to group tests that are related to the same aspect of your code/the functionality. describe block that contains the filename in the root-level of the test-file sub-describe-blocks for groups of tests focussing the same aspects of your code Example: describe('@components/share/ImportModal', () => { describe('when action button is clicked', () => { ... }); ... describe(\"when backend returns an error\", () => { }); }); Example taken from here Vue NYC - Component Tests with Vue.js - Matt O'Connell describe('@components/something/AddButton', () => { describe(':props', () => { it(':label - should render a button with the passed-in label text', () => { ... }) }); ... describe(\"@events\", () => { it('@add - should emit an \"add\" event when the button is clicked', () => { ... }) }); }); Hint: maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file","s":"Structure your tests using (multiple) \"describe\"-blocks","u":"/docs/nuxt-client/WritingTests","h":"#structure-your-tests-using-multiple-describe-blocks","p":190},{"i":207,"t":"There is a reason we use the it-alias for writing our code and not the test-method: we want to describe the aspect that is tested in a natural sentence. That's why it is best practice to start your test with: it('should ...'); Example: Bad: it('name changes on button click') ... Good: it('should display the info text', ... ); it('should not render migration start button', ... ); it('should return the translation', ... );","s":"Name the test like a sentence \"it should...\"","u":"/docs/nuxt-client/WritingTests","h":"#name-the-test-like-a-sentence-it-should","p":190},{"i":209,"t":"Data-testids are attributes to HTML-elements that are solely used to enable tests to find and check a certain aspect of that tag (often to check the contained text against some expected value). We decided to unify the way data-testid's should be named in Frontend Arch Group: Meeting 2022-11-04 Please use
    in your HTML-code if you want to define a data-testid. do not use uppercase-characters only use one dash - right after data You can later on check this using: // CopyResultModal.unit.ts expect( wrapper.find('[data-testid=\"copy-result-notifications\"]').text() ).toContain( wrapper.vm.$i18n.t(\"components.molecules.copyResult.fileCopy.error\") ); We also recommend to use refs instead of data-testids. But if you do that you ensure not to remove them once they are in the code... as they can be used in the component-code and for testing: VueJs - template refs VueTestUtils - ref","s":"data-testids","u":"/docs/nuxt-client/WritingTests","h":"#data-testids","p":190},{"i":211,"t":"Separate your setup from your actual tests: If you need a more complex setup to test something - write a scope method called \"setup\" for it. Write it in a reusable and configurable way, in order to reuse most of it in several groups of tests. You will get small and easily readable tests and no redudant setup-code inside your tests that contains small differences that are hard to detect.","s":"Setup-methods","u":"/docs/nuxt-client/WritingTests","h":"#setup-methods","p":190},{"i":214,"t":"Use the trigger()-method to simulate a events Testing Key, Mouse and other DOM events Mouse-Click: VueTestUtils - trigger events Keyboard-Input: VueTestUtils - keyboard example Drag & Drop: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that Event from a child component: VueTestUtils - emitting from child component","s":"Events","u":"/docs/nuxt-client/WritingTests","h":"#events","p":190},{"i":216,"t":"You can test asynchronous behavior by using Vue.nextTick(): await Vue.nextTick(); ... OR by triggering an effect and awaiting this effect to take place: const btnNext = wrapper.find(`[data-testid=\"dialog-next\"]`); await btnNext.trigger(\"click\"); ... see also: VueTestUtils - Testing Asynchronous Behavior","s":"Testing Asynchronous Behavior","u":"/docs/nuxt-client/WritingTests","h":"#testing-asynchronous-behavior","p":190},{"i":218,"t":"await expect(() => copyModule.copy(payload)).rejects.toThrow( `CopyProcess unknown type: ${payload.type}` );","s":"Exceptions","u":"/docs/nuxt-client/WritingTests","h":"#exceptions","p":190},{"i":220,"t":"// UserMigration.page.unit.ts const consoleErrorSpy = jest .spyOn(console, \"error\") .mockImplementation(); ... expect(consoleErrorSpy).toHaveBeenCalledWith( expect.any(ApplicationError) ); consoleErrorSpy.mockRestore();","s":"console.error","u":"/docs/nuxt-client/WritingTests","h":"#consoleerror","p":190},{"i":222,"t":"VueTestUtils - Testing composables","s":"Testing Composables","u":"/docs/nuxt-client/WritingTests","h":"#testing-composables","p":190},{"i":224,"t":"Replaces methods, instances of classes (e.g. stores) with some functionality, that e.g. simply returns a value you want to use in your test. By mocking you can easily simulate certain scenarios like failing requests or certain return values from any \"external\" (as in \"not part of the code i am currently testing\") functionality. Jest provides very helpful methods for that. Examples from our codebase: const mock = jest.fn().mockReturnValue(expectedTranslation); copyModuleMock.copyByShareToken = jest.fn() .mockResolvedValue(copyResults); They can easily be tested like this: expect(copyModuleMock.copyByShareToken).toHaveBeenCalled(); Or more specific like this: expect(addFileMetaDataSpy).toHaveBeenCalledWith( expect.objectContaining({ size: 2 } as FileMetaListResponse) ); See also here: VueTestUtils mount - mocks and stubs are now in global","s":"Mocking","u":"/docs/nuxt-client/WritingTests","h":"#mocking","p":190},{"i":226,"t":"Vue.js - Mocking injections VueTestUtils - provide / inject","s":"Mocking injections","u":"/docs/nuxt-client/WritingTests","h":"#mocking-injections","p":190},{"i":228,"t":"Mocking a vuex-store in a component​ Example file: src/components/administration/AdminMigrationSection.unit.ts import { createModuleMocks } from \"@/utils/mock-store-module\"; import YourModule from \"@/store/YourModule\"; let yourModule: jest.Mocked; schoolsModule = createModuleMocks(YourModule, { yourMethodName: { ... }, ...yourGetters, }) as jest.Mocked; mount(YourComponentToBeTested, { ...createComponentMocks({ ... }), provide: { yourModule, }, }); expect(yourModule.).toHaveBeenCalledWith(...); Testing a store​ import YourModule from \"./your-module\"; const yourModule = new YourModule({}); ... // using `jest.spyOn()` it(\"should call something\", () => { const yourActionNameMock = jest.spyOn(yourModule, \"yourActionName\"); yourModule.yourActionName(); expect(yourActionNameMock).toHaveBeenCalled(); }); // or using a method directly it(\"should set something\", () => { yourModule.setLoading(true); expect(yourModule.getLoading).toBe(true); });","s":"Mocking Vuex-Store","u":"/docs/nuxt-client/WritingTests","h":"#mocking-vuex-store","p":190},{"i":230,"t":"*{{ tbd }} (when Pinia-Stores are enabled for the project)*","s":"Mocking Pinia-Stores","u":"/docs/nuxt-client/WritingTests","h":"#mocking-pinia-stores","p":190},{"i":232,"t":"Sometimes - if a composable is simple and does not create sideeffects - it is okay to use it in the tests and avoid mocking it. That's beneficial as it let's us stick to the BlackBox-Idea: we should not know what the component is using internally. If you need to mock a composable, you can simple do this like in the following example. You only have to ensure to return everything the composable returns... but mocked versions of it. ... jest.spyOn(ourExampleComposable, \"useExample\").mockReturnValue({ // return mocks of what the composable would have returned }); ...","s":"Mocking Composables","u":"/docs/nuxt-client/WritingTests","h":"#mocking-composables","p":190},{"i":234,"t":"If you ever get into trouble to write good tests for your compents or code in general - this might be an indicator, that maybe your code is not structured good enough. Consider: spliting your component into smaller sub-components with a small API extracting functionality into one or mutliple composables using an existing composable (from VueUse or an existing one in the project) using an existing vuetify-component instead of writing it all yourself reshaping the communication workflow (parameters, events, inject/provide, stores, composables) (replacing a Vuex-store with a Pinia-store) For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd)","s":"Components that are hard to test","u":"/docs/nuxt-client/WritingTests","h":"#components-that-are-hard-to-test","p":190},{"i":236,"t":"(aka Integration/Acceptance/System-Tests) End-to-End-Tests are developed in a seperate repository end-to-end-tests Documentation of e2e tests","s":"End-To-End-Tests","u":"/docs/nuxt-client/WritingTests","h":"#end-to-end-tests","p":190},{"i":238,"t":"For monitoring our code-coverage we are using Codacy. The current status can be seen on this Dashboard.","s":"Code-Coverage","u":"/docs/nuxt-client/WritingTests","h":"#code-coverage","p":190},{"i":241,"t":"In nest.js all apis are defined in controllers. Usually the api follows the following syntax: /api/v3/ Each controller is responsible for a specific resource. The controller is responsible for the routing and the validation of the request. The controller calls a service to handle the request. The service is responsible for the business logic. The service calls a repository to access the database. The repository is responsible for the database access.","s":"nest.js","u":"/docs/schulcloud-server/Api","h":"#nestjs","p":239},{"i":243,"t":"When returning a response like this: @ApiOperation({ summary: 'Create a new element on a card.' }) @ApiExtraModels( ExternalToolElementResponse, FileElementResponse, LinkElementResponse, RichTextElementResponse, SubmissionContainerElementResponse ) @ApiResponse({ status: 201, schema: { oneOf: [ { $ref: getSchemaPath(ExternalToolElementResponse) }, { $ref: getSchemaPath(FileElementResponse) }, { $ref: getSchemaPath(LinkElementResponse) }, { $ref: getSchemaPath(RichTextElementResponse) }, { $ref: getSchemaPath(SubmissionContainerElementResponse) }, ], }, }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 403, type: ForbiddenException }) @ApiResponse({ status: 404, type: NotFoundException }) @Post(':cardId/elements') async createElement( @Param() urlParams: CardUrlParams, @Body() bodyParams: CreateContentElementBodyParams, @CurrentUser() currentUser: ICurrentUser ): Promise { const { type, toPosition } = bodyParams; const element = await this.cardUc.createElement(currentUser.userId, urlParams.cardId, type, toPosition); const response = ContentElementResponseFactory.mapToResponse(element); return response; } We want to use decorators to explain the intent of the response. The @ApiOperation decorator is used to define the summary. The @ApiResponse decorator is used to define the response. The @ApiExtraModels decorator is used to define the response models. The final response should either be an javascript Object or an array. We do not return primitives like string or boolean. Swagger will automatically generate the response schema from the response object.","s":"Responses","u":"/docs/schulcloud-server/Api","h":"#responses","p":239},{"i":245,"t":"Make sure to have the following software installed node 18 git clone git@github.com:hpi-schul-cloud/schulcloud-client.git Install the packages cd schulcloud-client npm ci npm run build The last step is to start the dev server with. npm run watch If you want to change the instance you need to set an env variable declare -x SC_THEME=\"n21\" node node_modules/gulp/bin/gulp.js clear-cache && node node_modules/gulp/bin/gulp.js npm run watch","s":"Getting started","u":"/docs/schulcloud-client/Getting started","h":"","p":244},{"i":248,"t":"The FeathersModule provides functionality to access legacy code. In order to introduce strong typing, it is necessary to write an adapter service for the feathers service you want to access. Place this adapter within your module, and use the FeathersServiceProvider to access the service you need // inside your module, import the FeathersModule @Module({ imports: [FeathersModule], providers: [MyFeathersServiceAdapter], }) export class MyModule {} // inside of your service, inject the FeathersServiceProvider @Injectable() export class MyFeathersServiceAdapter { constructor(private feathersServiceProvider: FeathersServiceProvider) {} async get(): Promise { const service = this.feathersServiceProvider.getService(`path`); const result = await service.get(...) return result; }","s":"Access Feathers Service from NestJS","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-feathers-service-from-nestjs","p":246},{"i":250,"t":"To access a NestJS service from a legacy Feathers service you need to make the NestJS service known to the Feathers service-collection in main.ts. This possibility should not be used for new features in Feathers, but it can help if you want to refactor a Feathers service to NestJs although other Feathers services depend on it. // main.ts async function bootstrap() { // (...) feathersExpress.services['nest-rocket-chat'] = nestApp.get(RocketChatService); // (...) } Afterwards you can access it the same way as you access other Feathers services with app.service('/nest-rocket-chat');","s":"Access NestJS injectable from Feathers","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-nestjs-injectable-from-feathers","p":246},{"i":252,"t":"A modules api layer is defined within of controllers. The main responsibilities of a controller is to define the REST API interface as openAPI specification and map DTO's to match the logic layers interfaces. @ApiOperation({ summary: 'some descriptive information that will show up in the API documentation' }) @ApiResponse({ status: 200, type: BoardResponse }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 403, type: ForbiddenException }) @ApiResponse({ status: 404, type: NotFoundException }) @Post() async create(@CurrentUser() currentUser: ICurrentUser, @Body() params: CreateNewsParams): Promise { const news = await this.newsUc.create( currentUser.userId, currentUser.schoolId, NewsMapper.mapCreateNewsToDomain(params) ); const dto = NewsMapper.mapToResponse(news); return dto; }","s":"Controller","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"","p":251},{"i":254,"t":"For authentication, use guards like JwtAuthGuard. It can be applied to a whole controller or a single controller method only. Then, the authenticated user can be injected using the @CurrentUser() decorator.","s":"JWT-Authentication","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#jwt-authentication","p":251},{"i":256,"t":"Global settings of the core-module ensure request/response validation against the api definition. Simple input types might additionally use a custom pipe while for complex types injected as query/body are validated by default when parsed as DTO class.","s":"Validation","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#validation","p":251},{"i":258,"t":"All data that leaves or enters the system has to be defined and typed using DTOs. export class CreateNewsParams { @IsString() @SanitizeHtml() @ApiProperty({ description: 'Title of the News entity', }) title!: string; // ... }","s":"DTOs","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dtos","p":251},{"i":260,"t":"Complex input DTOs are defined like [create-news].params.ts (class-name: CreateNewsParams). When DTO's are shared between multiple modules, locate them in the layer-related shared folder. Security: When exporting data, internal entities must be mapped to a response DTO class named like [news].response.dto. The mapping ensures which data of internal entities are exported.","s":"DTO File naming","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dto-file-naming","p":251},{"i":262,"t":"Defining the request/response DTOs in a controller will define the openAPI specification automatically. Additional validation rules and openAPI definitions can be added using decorators. For simplification, openAPI decorators should define a type and if a property is required, while additional decorators can be used from class-validator to validate content.","s":"openAPI specification","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#openapi-specification","p":251},{"i":264,"t":"You should define a mapper to easily create dtos from the uc responses, and the datatypes expected by ucs from params.","s":"Mapping","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#mapping","p":251},{"i":266,"t":"The Configuration Management consists of three layers working together. Application Layer: Defines the Variables the application needs as well es their type, and validates their values on application start. Configmap: Provides the Configuration as environment variables, making them available to the application. dof-configuration: defines configuration values for various instances and deployment stages. Each of those layers is described in more detail below.","s":"Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"","p":265},{"i":268,"t":"WORK IN PROGRESS we are still working to refine how the configuration should work within the application. Specifically, we still want to: remove the hpi-common library which currently takes care of defining a configuration schema and validating the values streamline each modules definition of necessary configuration values in NestJs consider how to implement runtime configuration and configuration changes improve the workflow of introducing (and removing!) feature flags TODO: document how config files in nest work TODO: document how the config schema works","s":"Application Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#application-configuration","p":265},{"i":270,"t":"Each of our code repositories contains the ansible files necessary for deploying the application(s) within. The configmap can be found under ansible/roles/:application-name:/templates/:application-name-configmap.yml.j2. apiVersion: v1 kind: ConfigMap metadata: name: application-name-configmap namespace: {{ NAMESPACE }} labels: app: application-label data: SOME_CONFIGURATION_KEY: \"{{ SOME_CONFIGURATION_KEY }}\" SOME_RENAMED_CONFIGURATION_KEY: \"{{ SOME_ORIGINAL_CONFIGURATION_KEY }}\" HARDCODED_VALUE: \"value\" COMPOSED_VALUE: \"https://{{ DOMAIN }}/some/thing\" under ansible/roles/:application-name:/defaults we define default values to be used when no values are provided from the environment.","s":"Config Maps","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#config-maps","p":265},{"i":272,"t":"The actual configuration values used for our deployments can be found in the dof_app_deploy repository under ansible/group_vars. Here, you will find various yml files containing configuration values. Kubernetes will apply each of these files in a defined order, beginning with the folder all, then applying the files in the folder corresponding to the deployment stage (eg. development, reference, production), and finally the files in the folder corresponding to the instance (eg. brb, dbc, thr, nbc.) Values in files that are applied later will overwrite earlier values. All of these values are gathered as kubernetes facts, and can theoretically be used by all config maps, ensuring the configurations of different applications are consistent with each other. Do note that a value must be mapped in a config map to be available to a given application.","s":"dof Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#dof-configuration","p":265},{"i":275,"t":"When we are replacing code which is used in other modules we should use a 2 step migration. This is meant to prevent merge conflicts and to make it easier to review the changes. Also it makes it easier to find the code which needs to be changed in the other modules. Please note that this is not always possible, but should be used when possible. Step 1: Add new alternative code Step 2: Mark the old code with \"@deprecated\" add a hint in the comments to use the new code. Step 3: Inform team Step 4: Remove deprecated code (when is hard to say, once all dependencies are removed)","s":"When to use 2 step migration","u":"/docs/schulcloud-server/Coding-Guidelines/deprection-workflow","h":"#when-to-use-2-step-migration","p":273},{"i":277,"t":"If you need to validate a domain object, please write an independent class, so that the domain object itself, its repo and services can reuse it. Eric Evans suggests using the specification pattern. A specification fulfills the following interface: public interface Specification { boolean isSatisfiedBy(T t); } A specification checks if a domain object fulfills the conditions of the specification. A specification can simply specify that a domain object is valid. E.g. a Task has an owner and a description. A specification can specify more complex and specialized conditions. E.g. Task where every student assigned to the task's course has handed in a submission. The specification pattern in its full extend describes how to use logic operators to combine multiple specifications into combined specifications as well. Please don't build this as long as you don't need it. YAGNI. More about full specification pattern","s":"Domain Object Validation","u":"/docs/schulcloud-server/Coding-Guidelines/domain-object-validation","h":"","p":276},{"i":279,"t":"Internal Events are used as a mechanism for Dependency Inversion. If you are implementing an operation in a module that needs to trigger an operation in another module, that is simple if you can simply import a service. However, if that other module already has a dependency on your module, that would lead to a dependency cycle. In this case, you need to inverse one of the dependencies via events. The main thing you need to think about, is which module should know about which module(s). This is the dependency, and it only ever can point into one direction. As a general rule of thumb, the module that is more specific, or is changing more frequently, or is less central to the functionality of the system, should have the dependency on the other. In the following example, the course module has a dependency on the user module, but NOT vice versa.","s":"Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"","p":278},{"i":281,"t":"consider the following folder structure - users - api - domain - services - events - user-deleted.event.ts - repo - courses - api - domain - services - handlers - user-deleted.handler.ts - repo each of the modules needs to import the CqrsModule // users.module.ts import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; @Module({ imports: [CqrsModule], providers: [/* some things here */], exports: [/* some things here */], }) export class GroupModule {}","s":"How to implement Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#how-to-implement-event-handling","p":278},{"i":283,"t":"The event is in the end simply a class, containing any data required to handle the event // users/domain/events/user-deleted.event.ts export class UserDeletedEvent { id: EntityId constructor(id: EntityId) { this.id = id } } Make sure to make your event public in the index file of your module","s":"Defining an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#defining-an-event","p":278},{"i":285,"t":"// users/domain/services/service.ts import { EventBus } from '@nestjs/cqrs'; import { UserDeletedEvent } from '../events'; @Injectable() export class Service { constructor(private readonly eventBus: EventBus) {} public async delete(userId: EntityId): Promise { doStuffForDeletion() await this.eventBus.publish(new UserDeletedEvent(userId)); } }","s":"Sending an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#sending-an-event","p":278},{"i":287,"t":"// courses/domain/handler/user-deleted.handler.ts import { UserDeletedEvent } from '@modules/users'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { SomeService } from '../services' @Injectable() @EventsHandler(UserDeletedEvent) export class GroupDeletedHandlerService implements IEventHandler { constructor(private readonly someService: SomeService) {} public async handle(event: GroupDeletedEvent): Promise { await someService.doSomeStuff() } } Note that the handler should not contain any logic, but only the orchestration of what needs to be done.","s":"Recieving an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#recieving-an-event","p":278},{"i":289,"t":"We separate our business exceptions from technical exceptions. While for technical exceptions, we use the predefined HTTPExceptions from NestJS, business exceptions inherit from abstract BusinessException. By default, implementations of BusinessException must define code: 500 type: \"CUSTOM_ERROR_TYPE\", title: \"Custom Error Type\", message: \"Human readable details\", // additional: optionalData There is a GlobalErrorFilter provided to handle exceptions, which cares about the response format of exceptions and logging. It overrides the default NestJS APP_FILTER in the core/error-module. In client applications, for technical errors, evaluate the http-error-code, then for business exceptions, the type can be used as identifier and additional data can be evaluated. For business errors we use 409/conflict as default to clearly have all business errors with one error code identified. Sample: For API validation errors, 400/Bad Request will be extended with validationError: ValidationError[{ field: string, error: string }] and a custom type API_VALIDATION_ERROR. Pipes can be used as input validation. To get errors reported in the correct format, they can define a custom exception factory when they should produce api validation error or other exceptions, handled by clients.","s":"Exception Handling","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"","p":288},{"i":291,"t":"If you catch an error and throw a new one, put the original error in the cause property of the new error. See example: try { someMethod(); } catch(error) { throw new ForbiddenException('some message', { cause: error }); }","s":"Chaining errors with the cause property","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#chaining-errors-with-the-cause-property","p":288},{"i":293,"t":"If you want the error log to contain more information than just the exception message, use or create an exception which implements the Loggable interface. Don't put data directly in the exception message! A loggable exception should extend the respective Built-in HTTP exception from NestJS. For the name just put in \"Loggable\" before the word \"Exception\", e.g. \"BadRequestLoggableException\". Except for logging a loggable exception behaves like any other exception, specifically the error response is not affected by this. See example below. export class UnauthorizedLoggableException extends UnauthorizedException implements Loggable { constructor(private readonly username: string, private readonly systemId?: string) { super(); } getLogMessage(): ErrorLogMessage { const message = { type: 'UNAUTHORIZED_EXCEPTION', stack: this.stack, data: { userName: this.username, systemId: this.systemId, }, }; return message; } } export class YourService { public sampleServiceMethod(username, systemId) { throw new UnauthorizedLoggableException(username, systemId); } }","s":"Loggable exceptions","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#loggable-exceptions","p":288},{"i":295,"t":"For logging use the Logger, exported by the logger module. It encapsulates a Winston logger. Its injection scope is transient, so you can set a context when you inject it. For better privacy protection and searchability of logs, the logger cannot log arbitrary strings but only so called loggables. If you want to log something you have to use or create a loggable that implements the Loggable interface. The message should be fixed in each loggable. If you want to log further data, put in the data field of the LogMessage, like in the example below. export class YourLoggable implements Loggable { constructor(private readonly userId: EntityId) {} getLogMessage(): LogMessage { return { message: 'I am a log message.', data: { userId: this.userId, }, }; } } import { Logger } from '@src/core/logger'; export class YourUc { constructor(private logger: Logger) { this.logger.setContext(YourUc.name); } public sampleUcMethod(user) { this.logger.log(new YourLoggable(userId: user.id)); } } This produces a logging output like [NestWinston] Info - 2023-05-31 15:20:30.888 [YourUc] { message: 'I am a log message.', data: { userId: '0000d231816abba584714c9e' }}","s":"Logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"","p":294},{"i":297,"t":"The logger exposes the methods log, warn, debug and verbose. It does not expose an error method because we don't want errors to be logged manually. All errors are logged in the exception filter.","s":"Log levels and error logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#log-levels-and-error-logging","p":294},{"i":299,"t":"While transitioning to the new logger for loggables, the old logger for strings is still available as LegacyLogger.","s":"Legacy logger","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#legacy-logger","p":294},{"i":301,"t":"When defining entities with MikroORM (Version 5), the following should be considered: The property decorator requires explicit assignment of the type to the property and may not work correctly when working with type inference or assigning union types to a property. In these cases, the metadata may not be set correctly, which can lead to exceptions, for example, when using the em.assign() or em.aggregate() functions. Therefore, the following is not sufficient: @Property() termsAccepted = false; @Property() createdAt = new Date(); The following works: @Property() termsAccepted: boolean = false; @Property() createdAt: Date = new Date(); The better way is to provide the type through the decorator: @Property({ type: 'boolean' }) termsAccepted = false; @Property({ type: Date }) createdAt = new Date(); Errors can also occur when specifying multiple types (union types): @Poperty({ nullable: true }) dueDate: Date | null; To set the metadata correctly, do the following: @Property({ type: Date, nullable: true }) dueDate: Date | null; If type inference is not used, specifying the type through the property decorator is not necessary: @Property() name: string;","s":"Defining Entities","u":"/docs/schulcloud-server/Coding-Guidelines/micro-orm","h":"","p":300},{"i":303,"t":"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.","s":"Implementation and usage of modules, submodule and barrel files in our project","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"","p":302},{"i":305,"t":"In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the export keyword. To import a module, you use the import keyword followed by the module name. Here's an example: import { ModuleName } from '@modules/module-name'; Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts): // @modules/module-name/index.ts export { SubmoduleServiceName } from './submodule-name/service.ts';","s":"Modules and Submodules","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#modules-and-submodules","p":302},{"i":307,"t":"Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules. If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again. Here's an example of a barrel file: // @modules/module-name/index.ts export { PublicService } from './services/public-service.ts'; export { ServiceInterfaceA, InterfaceB } from './interfaces'; export { InterfaceC } from './submodule-name/interfaces'; !!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like export * from './services' in the barrel file. And here's how you can import from the barrel: // @modules/other-module-name/service.ts import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';","s":"Barrel Files","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#barrel-files","p":302},{"i":309,"t":"Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs. Here are some strategies to handle circular dependencies: Refactor Your Code: The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle. // @modules/moduleC/service.ts import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA'; import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB'; Use Interfaces: If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle. // @modules/moduleC/service.ts import { type PublicService } from '@modules/moduleA'; import { type PublicService } from '@modules/moduleB'; Use Events: If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it. https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling https://docs.nestjs.com/recipes/cqrs Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible.","s":"Handling Circular Dependencies","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#handling-circular-dependencies","p":302},{"i":311,"t":"The repository is responsible to provide domain objects for the domain layer. Typically, it does so by accessing a database. Since the domain layer should be isolated (not have knowledge of the outer layers), the domain layer should have an interface definition for each repository it wants to access. This naturally means that the interface can only mention domain objects, without any knowledge about database entities. // somewhere in the domain layer... export interface SchoolRepo { getSchoolById(schoolId: EntityId): Promise; } The repository itself can now implement this interface, in this example using MikroOrm to get data from a database @Injectable() export class SchoolMikroOrmRepo implements SchoolRepo { constructor(private readonly em: EntityManager) {} public async getSchoolById(schoolId: EntityId): Promise { const entity = await this.em.findOneOrFail( SchoolEntity, { id: schoolId }, ); const school = SchoolEntityMapper.mapToDo(entity); return school; } } Note that the internal entity manager uses a different datatype, the SchoolEntity, to represent schooldata. This type includes information that is specific to mikroorm, and should not be mixed into the domain object definition. A mapper is used to explicitly map the entity to the domain object, and vice versa.","s":"Repositories","u":"/docs/schulcloud-server/Coding-Guidelines/repositories","h":"","p":310},{"i":314,"t":"Each change should be done in a ticket (no matter how small) The ticket does not need to be refined for very small things Might be relevant for reporting later Folder (feature/..) should no longer be used Stay below 64 letters Do not simply use ticket title, usually we need a shorter description :-) Ticket number needs to be uppercase (BC-1234) Related to matching with Jira Careful: namespace is lowercase BC-XXXX-kebab-case-short-description","s":"Branch name conventions","u":"/docs/schulcloud-server/Development/git","h":"#branch-name-conventions","p":312},{"i":316,"t":"Squashed commit subject should start with a ticket number, and end with a PR number Clean body (contains all commits by default) Only leave changes relevant for main Remove commits likes 'fix for linter', 'add tests', 'fix review comments' See example below Write commit messages in imperative and active Good: \"make the code better\" Bad: \"made the code better\", \"makes the code better\" Feel free to write actual text BC-1993 - lesson lernstore and geogebra copy (#3532) In order to make sure developers in the future can find out why changes have been made, we would like some descriptive text here that explains what we did and why. - change some important things - change some other things - refactor some existing things # I dont need to mention tests, changes that didnt make it to main, linter, or other fixups # only leave lines that are relevant changes compared to main # comments like this will not actually show up in the git history","s":"Commit message conventions","u":"/docs/schulcloud-server/Development/git","h":"#commit-message-conventions","p":312},{"i":318,"t":"We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.","s":"Windows","u":"/docs/schulcloud-server/Development/git","h":"#windows","p":312},{"i":320,"t":"Automated testing is the essential part of the software development process. It improves the code quality and ensure that the code operates correctly especially after refactoring.","s":"Testing","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"","p":319},{"i":323,"t":"The tests should be as simple to read and understand as possible. They should be effortless to write and change, in order to not slow down development. Wherever possible: avoid complex logic cover only one case per test only use clearly named and widely used helper functions stick to blackbox testing: think about the unit from the outside, not its inner workings. its okay to duplicate code for each test","s":"Lean Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#lean-tests","p":319},{"i":325,"t":"When a test fails, the name of the test is the first hint to the developer (or any other person) to what went wrong where. (along with the \"describe\" blocks the test is in). Thus, your describe structure and testcase names should be designed to enable a person unfamiliar with the code to identify the problem as fast as possible. It should tell him: what component is being tested under what condition the expected outcome To facilitate this, your tests should be wrapped in at least two describe levels. // Name of the unit under test describe(\"Course Service\", (() => { // method that is called describe('createCourse', () => { // a \"when...\" sentence describe(\"When a student tries to create a course\", (() => { const setup = () => { // testsetup for the situation that was described } // a \"should...\" sentence it(\"should return course\", async () => { ... }); }); }); });","s":"Naming Convention","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#naming-convention","p":319},{"i":327,"t":"Each test should be able to run alone, as well as together with any other tests. To ensure this, it is important that the test does not depend on any preexisting data. Each test should generate the data it needs, and ensure that its data is deleted afterwards. (this is usually done via mocha's \"afterEach\" function. When you create objects with fields that have to be globally unique, like the account username, you must ensure the name you choose is unique. This can be done by including a timestamp. Never use seeddata.","s":"Isolation","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#isolation","p":319},{"i":329,"t":"Your test should be structured in three seperate areas, each distinguished by at least an empty line: Arrange - set up your testdata Act - call the function you want to test Assert - check the result this is known as the AAA-pattern. The tests for a unit should cover as much scenarios as possible. Parameters and the combination of parameters can often take numerous values. Therefore it largely differs from case to case what a sufficient amount of scenarios would be. Parameter values that contradict the typescript type definition should be ignored as a test case. The test coverage report already enforces scenarios that test every possible if/else result in the code. But still some scenarios are not covered by the report and must be tested: All error scenarios: That means one describe block for every call that can reject. We use different levels of describe blocks to structure the tests in a way, that the tested scenarios could easily be recognized. The outer describe would be the function call itself. Every scenario is added as another describe inside the outer describe. All of the data and mock preparation should happen in a setup function. Every describe scenario only contains one setup function and is called in every test. No further data or mock preparation should be added to the test. Often there will be only one test in every describe scenario, this is perfectly fine with our desired structure. describe('[method]', () => { describe('when [senario description that is prepared in setup]', () => { const setup = () => { // prepare the data and mocks for this scenario }; it('...', () => { const { } = setup(); }); it('...', () => { const { } = setup(); }); }); describe('when [senario description that is prepared in setup]', () => { const setup = () => { // prepare the data and mocks for this scenario }; it('...', () => { const { } = setup(); }); }); });","s":"Test Structure","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#test-structure","p":319},{"i":332,"t":"When assigning a value to an expect, separate the function call from the expectation to simplify debugging. This later helps when you not know about the return value type or if it's an promise or not. This is good style not only for tests. // doSomethingCrazy : retValue it('bad sample', () => { expect(doSomethingCrazy(x,y,z)).to... }) it('good sample', () => { const result = doSomethingCrazy(x,y,z) expect(result).to... // here we can simply debug })","s":"Handling of function return values","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#handling-of-function-return-values","p":319},{"i":334,"t":"When using asynchronous functions and/opr promises, results must be awaited within of an async test function instead of using promise chains. While for expecting error conditions it might be helpful to use catch for extracting a value from an expected error, in every case avoid writing long promise chains. Instead of using done callback, use async test functions. Use await instead of (long) promise chains never manually set a timeout // doSomethingCrazy : Promise it('bad async sample', async function (done) => { return doSomethingCrazy(x,y,z).then(result=>{ expect(result).to... done() // expected done }).catch(()=>{ logger.info(`Could not ... ${error}`); done() // unexpected done, test will always succeed which is wrong }) }, 10000 /* timeout in ms */) it('good async sample', async () => { // no timeout set const result = await doSomethingCrazy(x,y,z) expect(result).to... }) Timeouts must not be used, when async handling is correctly defined!","s":"Promises and Timouts in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#promises-and-timouts-in-tests","p":319},{"i":336,"t":"When expecting an error, you might take values from an error, test for the error type thrown and must care of promises. // doSomethingCrazy : Promise it('bad async sample expecting an error', () => { expect(doSomethingCrazy(x,y,z)).to... }) it('good async sample expecting an error value', async () => { const code = await doSomethingCrazy(x,y,z).catch(err => err.code) expect(code).to... }) it('good sample expecting an error type from a sync function', () => { expect(() => doSomethingCrazySync(wrong, param)).toThrow(BadRequestException); }) it('good sample expecting an error type from an async function', async () => { await expect(doSomethingCrazySync(wrong, param)).rejects.toThrow(BadRequestException); })","s":"Expecting errors in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#expecting-errors-in-tests","p":319},{"i":338,"t":"NestJS: provides default tooling (such as test runner that builds an isolated module/application loader) provides integration with Jest and Supertest out of the box makes the Nest dependency injection system available in the testing environment for mocking components The @nestjs/testing.Test class provides an execution context that mocks the full Nest runtime, but gives hooks that can help to manage class instances, including mocking and overriding. The method Test.createTestingModule() takes module metadata as argument it returns TestingModule instance. The TestingModule instance provides method compile() which bootstraps a module with its dependencies. Every provider can be overwritten with custom provider implementation for testing purposes. beforeAll(async () => { const moduleRef = await Test.createTestingModule({ controllers: [SampleController], providers: [SampleService], }).compile(); sampleService = moduleRef.get(SampleService); sampleController = moduleRef.get(CatsController); });","s":"Testing Utilities","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#testing-utilities","p":319},{"i":340,"t":"Using the utilities provided by NestJs, we can easily inject mocks into our testing module. The mocks themselves, we create using a library by @golevelup. You can create a mock using createMock(). As result you will recieved a DeepMocked let fut: FeatureUnderTest; let mockService: DeepMocked; beforeAll(async () => { const module = await Test.createTestingModule({ providers: [ FeatureUnderTest, { provide: MockService, useValue: createMock(), }, ], }).compile(); fut = module.get(FeatureUnderTest); mockService = module.get(MockService); }); afterAll(async () => { await module.close(); }); afterEach(() => { jest.resetAllMocks(); }) The resulting mock has all the functions of the original Class, replaced with jest spies. This gives you code completion and type safety, combined with all the features of spies. createTestingModule should only be calld in beforeAll and not in beforeEach to keep the setup and teardown for each test as simple as possible. Therefore module.close should only be called in afterAll and not in afterEach. To generally reset specific mock implementation after each test jest.resetAllMocks can be used in afterEach. jest.restoreAllMocks should not be used, because in some cases it will not properly restore mocks created by ts-jest. describe('somefunction', () => { describe('when service returns user', () => { const setup = () => { const resultUser = userFactory.buildWithId(); mockService.getUser.mockReturnValueOnce(resultUser); return { resultUser }; }; it('should call service', async () => { setup(); await fut.somefunction(); expect(mockService.getUser).toHaveBeenCalled(); }); it('should return user passed by service', async () => { const { resultUser } = setup(); const result = await fut.somefunction(); expect(result).toEqual(resultUser); }); }); }); For creating specific mock implementations the helper functions which only mock the implementation once, must be used (e.g. mockReturnValueOnce). With that approach more control over mocked functions can be achieved. If you want to mock a method that is not part of a dependency you can mock it with jest.spyOn. We strongly recommend the use of jest.spyOn and not jest.fn, because jest.spyOn can be restored a lot easier.","s":"Mocking","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mocking","p":319},{"i":342,"t":"In Unit Tests we access directly only the component which is currently testing. Any dependencies should be mocked or are replaced with default testing implementation. Especially the database access and database calls should be mocked. In contrast to unit tests the integration tests use access to the database and execute real queries using repositories.","s":"Unit Tests vs Integration Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#unit-tests-vs-integration-tests","p":319},{"i":344,"t":"For the data access layer, integration tests can be used to check the repositories base functionality against a database. For Queries care DRY principle, they should be tested very carefully. Use a in-memory database for testing to allow parallel test execution and have isolated execution of tests. A test must define the before and after state of the data set clearly and cleanup the database after execution to the before state. Instead of using predefined data sets, all preconditions should be defined in code through fixtures. Our repository layer uses mikro-orm/EntityManager to execute the queries. By testing repositories we want to verify the correct behaviour of the repository functions. It includes verifying expected database state after executed repository function. Therefore, the *.repo.integration.spec.js should be used. The basic structure of the repo integration test: Preconditions (beforeAll)​ Create Nest JS testing module: 1.1 with MongoMemoryDatabaseModule defining entities which are used in tests. This will wrap MikroOrmModule.forRoot() with running a MongoDB in memory. 1.2 provide the repo which should be tested Get repo, orm and entityManager from testing module import { MongoMemoryDatabaseModule } from '@src/modules/database'; let repo: NewsRepo; let em: EntityManager; let testModule: TestingModule; beforeAll(async () => { testModule: TestingModule = await Test.createTestingModule({ (1) imports: [ MongoMemoryDatabaseModule.forRoot({ (1.1) entities: [News, CourseNews, ...], }), ], providers: [NewsRepo], (1.2) }).compile(); repo = testModule.get(NewsRepo); (2) orm = testModule.get(MikroORM); em = testModule.get(EntityManager); }) Post conditions (afterAll), Teardown​ After all tests are executed close the app and orm to release the resources by closing the test module. afterAll(async () => { await testModule.close(); }); When Jest reports open handles that not have been closed, ensure all Promises are awaited and all application parts started are correctly closed. Entity Factories​ To fill the in-memory-db we use factories. They are located in \\apps\\server\\src\\shared\\testing\\factory. If you create a new one, please add it to the index.ts in that folder. Accessing the in-memory-db​ While debugging the tests, the URL to the in-memory-db can be found in the EntityManager instance of your repo in em.config.options.clientUrl. Copy paste this URL to your DB Tool e.g. MongoDB Compass. You will find a database called 'test' with the data you created for your test.","s":"Repo Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#repo-tests","p":319},{"i":346,"t":"Mapping tests are Unit Tests which verify the correct mapping between entities and Dto objects. These tests should not have any external dependencies to other layers like database or use cases.","s":"Mapping Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mapping-tests","p":319},{"i":348,"t":"Since a usecase only contains orchestration, its tests should be decoupled from the components it depends on. We thus use unittests to verify the orchestration where necessary All Dependencies should be mocked. Use Spies to verify necessary steps, such as authorisation checks. to be documented","s":"Use Case Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#use-case-tests","p":319},{"i":350,"t":"Controllers do not contain any logic, but exclusively information to map and validate between dataformats used on the network, and those used internally, as well as documentation of the api. Most of these things can not be covered by unit tests. Therefore we do not write specific unittests for them, and only cover them with api tests.","s":"Controller Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#controller-tests","p":319},{"i":352,"t":"The API tests are plumbing or integration tests. Their job is to make sure all components that interact to fulfill a specific api endpoint are wired up correctly, and fulfil the expectation set up in the documentation. API tests should be located in the folder controller/api-test of each module. They should call the endpoint like a external entity would, treating it like a blackbox. It should try all parameters available on the API, users with different roles, as well as relevant error cases. During the API test, all components that are part of the server, or controlled by the server, should be available. This includes an in-memory database. Any external services or servers that are outside our control should be mocked away via their respective adapters.","s":"API Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#api-tests","p":319},{"i":354,"t":"This guide is inspired by javascript-testing-best-practices by goldbergyoni","s":"References","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#references","p":319},{"i":356,"t":"ErWIn-IDM, namely Keycloak, will be the future Identity Management System (IDM) for the dBildungscloud. Keycloak provides OpenID Connect, SAML 2.0 and other identity related functionalities like SSO out of the box. It can also act as identity broker or aggregate identities from third party services which can be an active directory or LDAP.","s":"ErWIn-IDM (Keycloak)","u":"/docs/schulcloud-server/Development/keycloak","h":"","p":355},{"i":358,"t":"To run Keycloak locally for development purpose use the following Bash or PowerShell command. You can log into Keycloak here http://localhost:8080. If you don't want to block your terminal, you can add the -d option to start the container in the background. Execute these commands in the repository root or the data seeding will fail, and you can not log into Keycloak with any user. Bash: docker run \\ --name erwinidm \\ -p 8080:8080 \\ -p 8443:8443 \\ -v \"$PWD/backup/idm/keycloak:/tmp/realms\" \\ ghcr.io/hpi-schul-cloud/erwin-idm/dev:latest \\ \"&& /opt/keycloak/bin/kc.sh import --dir /tmp/realms\" PowerShell: docker run ` --name erwinidm ` -p 8080:8080 ` -p 8443:8443 ` -v \"$PWD/backup/idm/keycloak:/tmp/realms\" ` ghcr.io/hpi-schul-cloud/erwin-idm/dev:latest ` \"&& /opt/keycloak/bin/kc.sh import --dir /tmp/realms\"","s":"Docker","u":"/docs/schulcloud-server/Development/keycloak","h":"#docker","p":355},{"i":360,"t":"To add ErWIn-IDM identity broker feature via OpenID Connect (OIDC) Identity Provider (IdP) mock follow the steps below. Execute these commands in the repository root. Set env vars (or in your .env file) 'OIDCMOCK__BASE_URL' to http://\\. To make it work with the nuxt client set the env var HOST=http://localhost:4000 re-trigger npm run setup:db and npm run setup:idm to reset and apply seed data. start the 'oidc-server-mock' as follows: docker run \\ --name oidc-server-mock \\ -p 4011:80 \\ -e ASPNETCORE_ENVIRONMENT='Development' \\ -e SERVER_OPTIONS_PATH='/tmp/config/server-config.json' \\ -e USERS_CONFIGURATION_PATH='/tmp/config/users-config.json' \\ -e CLIENTS_CONFIGURATION_PATH='/tmp/config/clients-config.json' \\ -v \"$PWD/backup/idm/oidcmock:/tmp/config\" \\ ghcr.io/soluto/oidc-server-mock:0.6.0 PowerShell: docker run ` --name oidc-server-mock ` -p 4011:80 ` -e ASPNETCORE_ENVIRONMENT='Development' ` -e SERVER_OPTIONS_PATH='/tmp/config/server-config.json' ` -e USERS_CONFIGURATION_PATH='/tmp/config/users-config.json' ` -e CLIENTS_CONFIGURATION_PATH='/tmp/config/clients-config.json' ` -v \"$PWD/backup/idm/oidcmock:/tmp/config\" ` ghcr.io/soluto/oidc-server-mock:0.6.0","s":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering","p":355},{"i":362,"t":"The broker feature can be setup in conjunction with LDAP provisioning for local testing purpose. Therefore, run the sc-openldap-single container: docker run \\ --name sc-openldap-single \\ -p 389:389 \\ ghcr.io/hpi-schul-cloud/sc-openldap-single:latest docker run ` --name sc-openldap-single ` -p 389:389 ` ghcr.io/hpi-schul-cloud/sc-openldap-single:latest The LDAP provisioning is trigger as follows: curl -X POST \\ 'http://localhost:3030/api/v1/sync?target=ldap' \\ --header 'Accept: */*' \\ --header 'X-API-KEY: example' Invoke-RestMethod ` -Uri 'http://localhost:3030/api/v1/sync?target=ldap' ` -Method Post ` -Headers @{ \"Accept\" = \"*/*\"; \"X-API-KEY\" = \"example\" } See '/tmp/config/users-config.json' for the users details.","s":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering with LDAP provisioning","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering-with-ldap-provisioning","p":355},{"i":364,"t":"You may test your local setup executing 'keycloak-identity-management.integration.spec.ts': npx jest apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts","s":"Test local environment","u":"/docs/schulcloud-server/Development/keycloak","h":"#test-local-environment","p":355},{"i":366,"t":"During container startup Keycloak will always create the Master realm with the admin user. After startup, we use the Keycloak-CLI to import the dBildungscloud realm, which contains some seed users, groups and permissions for development and testing. In the table below you can see the username and password combinations for the Keycloak login. Username Password Description keycloak keycloak The overall Keycloak administrator with all permissions. dbildungscloud dBildungscloud The dBildungscloud realm specific administrator.","s":"Seeding Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#seeding-data","p":355},{"i":368,"t":"Run Keycloak and make the desired changes Use docker container exec -it keycloak bash to start a bash in the container Use the Keycloak-CLI to export all Keycloak data with /opt/keycloak/bin/kc.sh export --dir /tmp/realms Save your changes with a commit If you start your container with a command from the docker section, your changes will be directly applied to the starting Keycloak container IMPORTANT: During the export process there will be some errors, that's because the export process will be done on the same port as the Keycloak server. This leads to Keycloak failing to start the server in import/export mode. Due to the transition from WildFly to Quarkus as application server there is currently no documentation on this topic. In order to re-apply the seeding data for a running keycloak container, you may run following commands (to be executed in the repository root): docker cp ./backup/idm/keycloak keycloak:/tmp/realms docker exec erwinidm /opt/keycloak/bin/kc.sh import --dir /tmp/realms","s":"Updating Seed Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#updating-seed-data","p":355},{"i":370,"t":"A list of available NPM commands regarding Keycloak / IDM. Command Description setup:idm:seed Seeds users for development and testing purpose into the IDM setup:idm:configure Configures identity and authentication providers and other details in the IDM","s":"NPM Commands","u":"/docs/schulcloud-server/Development/keycloak","h":"#npm-commands","p":355},{"i":373,"t":"It makes sense for Rocket.Chat to launch its own mongodb in Docker. Reason for this is Rocket.Chat requires Mongodb as replicaSet setup. docker run --name rocket-chat-mongodb -m=256m -p27030:27017 -d docker.io/mongo --replSet rs0 --oplogSize 10 Start mongoDB console and execute rs.initiate({\"_id\" : \"rs0\", \"members\" : [{\"_id\" : 0, \"host\" : \"localhost:27017\"}]})","s":"Start Mongodb","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-mongodb","p":371},{"i":375,"t":"(check the latest settings https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/roles/rocketchat/templates/configmap.yml.j2#L9) Please not that the displayed //172.29.173.128 is the IP address of the mongoDB docker container. You can get the ip over the command: docker inspect rocket-chat-mongodb | grep \"IPAddress\" (dependent on our system) docker run\\ -e CREATE_TOKENS_FOR_USERS=true \\ -e MONGO_URL=mongodb://172.29.173.128:27030/rocketchat \\ -e ADMIN_PASS=huhu \\ -e API_Enable_Rate_Limiter_Limit_Calls_Default=255 \\ -e Accounts_iframe_enabled=true \\ -e Accounts_iframe_url=http://localhost:4000/rocketChat/Iframe \\ -e Accounts_Iframe_api_url=http://localhost:4000/rocketChat/authGet \\ -e Accounts_AllowRealNameChange=false \\ -e Accounts_AllowUsernameChange=false \\ -e Accounts_AllowEmailChange=false \\ -e Accounts_AllowAnonymousRead=false \\ -e Accounts_Send_Email_When_Activating=false \\ -e Accounts_Send_Email_When_Deactivating=false \\ -e Accounts_UseDefaultBlockedDomainsList=false \\ -e Analytics_features_messages=false \\ -e Analytics_features_rooms=false \\ -e Analytics_features_users=false \\ -e Statistics_reporting=false \\ -e API_Enable_CORS=true \\ -e Discussion_enabled=false \\ -e FileUpload_Enabled=false \\ -e UI_Use_Real_Name=true \\ -e Threads_enabled=false \\ -e Accounts_SetDefaultAvatar=false \\ -e Iframe_Restrict_Access=false \\ -e Accounts_Iframe_api_method=GET \\ -e OVERWRITE_SETTING_Show_Setup_Wizard='completed' \\ -p 3000:3000 docker.io/rocketchat/rocket.chat:4.7.2","s":"Start rocketChat","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-rocketchat","p":371},{"i":377,"t":"You must also configure you server and legacy client application. Use the .env file in top of the project folders.","s":"ENVS","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#envs","p":371},{"i":379,"t":"ROCKETCHAT_SERVICE_ENABLED=true ROCKET_CHAT_URI=\"http://localhost:3000\" ROCKET_CHAT_ADMIN_USER=admin ROCKET_CHAT_ADMIN_PASSWORD=huhu","s":"dBildungscloud Backend Server","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-backend-server","p":371},{"i":381,"t":"ROCKETCHAT_SERVICE_ENABLED=true ROCKET_CHAT_URI=\"http://localhost:3000\"","s":"dBildungscloud Legacy Client","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-legacy-client","p":371},{"i":384,"t":"In the file ./vscode/launch.default.json you find following actions: Attach to NestJS will allow to attach VSCode debugger to an already running application Deubg NestJS via NPM will start the application and attach the debugger","s":"Launch scripts","u":"/docs/schulcloud-server/Development/vs-code","h":"#launch-scripts","p":382},{"i":386,"t":"In the file ./vscode/settings.default.json find suggested settings.","s":"Settings","u":"/docs/schulcloud-server/Development/vs-code","h":"#settings","p":382},{"i":388,"t":"See ./vscode/extensions.json for recommendations.","s":"Recommended extensions","u":"/docs/schulcloud-server/Development/vs-code","h":"#recommended-extensions","p":382},{"i":390,"t":"Jest is used to care of unit- and end2end tests on all *.spec.ts files. Allows to just see failing tests in Problems tab. and get small icons like ✔️ or a cross beside it-definitions inside of test files.","s":"Jest","u":"/docs/schulcloud-server/Development/vs-code","h":"#jest","p":382},{"i":392,"t":"Make sure to have the following software installed node 18 MongoDb 4.2+ Redis RabbitMQ git clone git@github.com:hpi-schul-cloud/schulcloud-server.git Install the packages cd schulcloud-server npm ci Start MongoDb, Redis & RabbitMQ. Then seed the database npm run setup The last step is to start the dev server with. npm run nest:start:dev","s":"Getting started","u":"/docs/schulcloud-server/Getting started","h":"","p":391},{"i":395,"t":"Migration mean to change the database structure by updating a model and/or updating user data to the target model. The documentation about migration concept and tool on npm can be found at MikroORM documentation. Please be aware of current mikro-orm version. Here, we explain only how we create migrations currently. We use the cli commands provided by MikroORM.","s":"Migrations for server database","u":"/docs/schulcloud-server/Migrations","h":"#migrations-for-server-database","p":393},{"i":397,"t":"npx mikro-orm migration:create will create a new migration file in ./apps/server/src/migrations/mikro-orm folder. please note that argument --name=SOME_MIGRATION_NAME is supported in 5.9 only do log all database changes (before and after state of documents or at least all modified document id's)","s":"Create a new migration","u":"/docs/schulcloud-server/Migrations","h":"#create-a-new-migration","p":393},{"i":399,"t":"To run migraitons you can use one of the commands below: npx mikro-orm migration:up will apply all migrations that are not applied yet npm run migration:up will run the compiled js file. This command is safer, and used in production. you can also apply any specific migration. Look at the documentation for more details.","s":"Apply migrations","u":"/docs/schulcloud-server/Migrations","h":"#apply-migrations","p":393},{"i":401,"t":"npx mikro-orm migration:down will undo the last migration that has been applied.","s":"Undo migration","u":"/docs/schulcloud-server/Migrations","h":"#undo-migration","p":393},{"i":403,"t":"migrations are stored in database migrations collection there is no status for a migration. Once the migration has been applied, a record is added in the database. the command will compare migrations files to database records to know which migrations to apply migration configuration is in ./apps/server/src/mikro-orm.config.ts file. MikroORM uses the config file to connect to the database and to find the migrations folder.","s":"Notes about setup","u":"/docs/schulcloud-server/Migrations","h":"#notes-about-setup","p":393},{"i":405,"t":"to check migrations to be executed: in local development you can use npx mikro-orm migration:pending command npm run migration:pending to run the compiled js file. The second command is safer, and used in CI and should be used in K8 clusters. The CI job ./.github/workflows/migrations.yml will check that the migrations are already included in the seed data. If not, it will fail. This is to ensure that the seed data is always up to date with the migrations.","s":"Test migration","u":"/docs/schulcloud-server/Migrations","h":"#test-migration","p":393},{"i":407,"t":"npm run setup:db:seed - to update the whole database or use npm run nest:start:console -- database --help to see how to import/export single collection npx mikro-orm migration:up - to apply the migration update the migrations collection npm run backup (copies the collections to folder backup/DATE, move the changed files of this folder to backup/setup and commit the updated files) or use npm run nest:start:console -- database export --collection --override to override seed data directly. The updated collections, modified by your migration added in backup/setup folder Commit the changes: git add . git commit -m \"migration: \" git push test that the migration was applied npx mikro-orm migrations:pending (or better use npm run migration:pending) should return nothing","s":"Committing a migration","u":"/docs/schulcloud-server/Migrations","h":"#committing-a-migration","p":393},{"i":410,"t":"consider if a migration is the right tool for this job recurring tasks are better placed in a script/console test the migration well if a migration should be deleted, do not delete the migration file itself, but remove the content of the up and down method and log a skip message consider if the migration can be written to be idempotent (can be executed multiple times without problems)","s":"General","u":"/docs/schulcloud-server/Migrations","h":"#general","p":393},{"i":412,"t":"think about the size of the collection. On production, it can be that the collection has a lot of data, and your migration might take a long time or can lead to time-out errors. use methods already provided by mongo for bulk operations (e.g. updateMany, deleteMany etc.) but think about handle items separately with extra logging and separate error handling load the data chunkwise and process them asynchronously query the data with skip and limit (example migration) or use async iterators with mongoose queries (blog post, example migration) migrations should in general not be executed in parallel (on multiple pods) Use for loops with synchronous execution and logging. Avoid subtasks awaiting Promise.all() Beside loading data in chunks or cursors, performance must not be an issue.","s":"Performance","u":"/docs/schulcloud-server/Migrations","h":"#performance","p":393},{"i":414,"t":"if a migration throws an error, the subsequent migrations are not executed as well catch errors if the errors are acceptable for the next release, so it will not become a release blocker log start and end of a migration (so that we know which migration currently is running) log intermediate results for long-running migrations (so that we know if it is still running) use log level alert or above, so the output can be seen on production logging might be difficult to fix, instead it might be helpful to keep before-states in a separate collection","s":"Error Handling/Logging","u":"/docs/schulcloud-server/Migrations","h":"#error-handlinglogging","p":393},{"i":416,"t":"use env MIKRO_ORM_CLI_VERBOSE=1 can add mikro-orm debug info in config file debug: true will log all queries logger: console.log will log all queries can add debug breakpoints in migration files run with typical debug options for your IDE (e.g. in Webstorm create a Run/Debug npm command and run it with debug options)","s":"Debugging","u":"/docs/schulcloud-server/Migrations","h":"#debugging","p":393},{"i":418,"t":"using entity manager​ According to documentation, the entity manager should not be used in migrations. Instead, the migration should use the mongoClient to access the database. Outdated Migrations​ By their nature, migrations become outdated as the database model changes. You are never expected to update migrations due to any changes in the code that are made. If needed, for example because the migration shows errors due to a code (model) change, migrations can be deleted, since they will still be accessible in the git history.","s":"Caveats","u":"/docs/schulcloud-server/Migrations","h":"#caveats","p":393},{"i":421,"t":"FEATURE_ETHERPAD_ENABLED - Used to enable etherpad feature in feathers backend FEATURE_COLUMN_BOARD_COLLABORATIVE_TEXT_EDITOR_ENABLED - Enables etherpad feature on column boards ETHERPAD__COOKIE_EXPIRES_SECONDS - Time in seconds after which a session expires ETHERPAD__COOKIE_RELEASE_THRESHOLD - Time in seconds after which a session is not returned to the user ETHERPAD__API_KEY - Api key used for authentication of schulcloud server requests ETHERPAD__URI - Uri of etherpad api for all calls like create, delete etc. Used as base path for api client in nest and feathers backend. ETHERPAD__PAD_URI - URI to etherpad client api. Used in backend in collaborative text editor and lesson to build return url. Used in legacy client to build url. ETHERPAD_OLD_PAD_URI - Used in feathers backend to restrict access to old etherpad urls to lesson context. Only defined in default.schema.json and not in dof-app-deploy ETHERPAD__PAD_PATH - Path to etherpad client api. Used in legacy client to set path on cookie. ETHERPAD__NEW_DOMAIN - Etherpad Domain. Used in legacy client to validate url ETHERPAD__OLD_DOMAIN - Old Etherpad Domain. Used in legacy client to identify old urls in lessons and transform them to urls with new domain.","s":"Configuration","u":"/docs/services/etherpad/How it works","h":"#configuration","p":419},{"i":424,"t":"The user initiates the process by creating an Etherpad element on a column board. Vue then sends a request to the board module in the Schulcloud server for a new Etherpad element. The server responds by returning an Etherpad element which is then displayed on the board.","s":"Creating an Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#creating-an-etherpad-element","p":419},{"i":426,"t":"When the user clicks on the Etherpad element, the Vue client sends a request to the collaborative text editor module in the Schulcloud server for a new Etherpad. The Schulcloud server is capable of creating Etherpads based on parent types. The parent type is further used for handling authorisation of the etherpad. Currently, the only parent type is a column board element.","s":"Interacting with the Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#interacting-with-the-etherpad-element","p":419},{"i":428,"t":"The Schulcloud server first creates a group that clusters several Etherpads together. Each group shares a session and a new group is created for every column board element. This requires sending a request to the Etherpad server. Once the group is created, the server can send a request to create an Etherpad within that group.","s":"Grouping and Creating Etherpads","u":"/docs/services/etherpad/How it works","h":"#grouping-and-creating-etherpads","p":419},{"i":430,"t":"For session creation, the server first needs to request an Etherpad author and then the session for that author. With that session, the server can set a session cookie in the client's browser and return the Etherpad URL to Vue.","s":"Session Creation and Cookie Setting","u":"/docs/services/etherpad/How it works","h":"#session-creation-and-cookie-setting","p":419},{"i":432,"t":"In the final step, Vue opens the Etherpad URL in a new browser tab, enabling the user to interact directly with the Etherpad. It's important to note that the Etherpad client interface displayed to the user is served by the Etherpad server, and as such, it is not a part of our own codebase.","s":"Opening the Etherpad","u":"/docs/services/etherpad/How it works","h":"#opening-the-etherpad","p":419},{"i":434,"t":"For the communication with the Etherpad server, the Schulcloud server uses an adapter. This adapter is responsible for handling all requests to the Etherpad server and ensuring that the correct data is sent and received. This adapter uses a client that is generated by open api generator. Client generation can be triggered with generate-client:etherpad and should be executed after update of etherpad server.","s":"Etherpad Adapter","u":"/docs/services/etherpad/How it works","h":"#etherpad-adapter","p":419},{"i":436,"t":"The authentication process in our system works via a token. This token is sent with each request as a parameter to ensure secure communication. The token is defined by the ETHERPAD_API_KEY environment variable. This variable holds the key used for authentication, ensuring that only authorized requests are processed. In the Schulcloud server, this API key is passed to the etherpad adapter on its initialization in the collaborative text editor module.","s":"Authentication","u":"/docs/services/etherpad/How it works","h":"#authentication","p":419},{"i":439,"t":"Each interaction with an Etherpad element initiates the creation of a new session. The lifespan of this session is determined by the ETHERPAD_COOKIE__EXPIRES_SECONDS environment variable. Once this time period elapses, the user loses access to the pad. This loss of access is indicated by the display of a non-translated English message: \"You do not have permission to access this pad.\" However, even after the session expires, users can still view the pad content as long as they do not interact with it.","s":"Etherpad Session Creation and Expiration","u":"/docs/services/etherpad/How it works","h":"#etherpad-session-creation-and-expiration","p":419},{"i":441,"t":"Upon subsequent interactions with the Etherpad element, an existing session for that element may be reused. Whether an old session is reused or a new one is created depends on the environment variable settings. The ETHERPAD_COOKIE_RELEASE_THRESHOLD variable determines the remaining validity period of a session for it to be delivered to the user. In the current production environment, both ETHERPAD_COOKIE_RELEASE_THRESHOLD and ETHERPAD_COOKIE__EXPIRES_SECONDS are set to the same value of 2 hours. This configuration results in the creation of a new session for each interaction with an Etherpad element.","s":"Session Reuse","u":"/docs/services/etherpad/How it works","h":"#session-reuse","p":419},{"i":443,"t":"Currently there is no automatic session renewal upon interaction. Etherpad provides the COOKIE_SESSION_REFRESH_INTERVAL configuration variable, which specifies the time period after which a user's session is automatically renewed in an open tab. However, this variable is currently unset, and the default value of 1 day has no effect because it exceeds the ETHERPAD_COOKIE__EXPIRES_SECONDS value. Therefore, sessions are not automatically renewed.","s":"Session Renewal","u":"/docs/services/etherpad/How it works","h":"#session-renewal","p":419},{"i":445,"t":"When a user logs out of Schulcloud, all sessions are terminated, and the user loses access to the pads upon interaction. However, Etherpad sessions are not automatically removed upon user auto-logout in Schulcloud. As long as ETHERPAD_COOKIE__EXPIRES_SECONDS and JWT_TIMEOUT_SECONDS are set to the same value, all sessions should theoretically become invalid after auto-logout.","s":"Session Removal","u":"/docs/services/etherpad/How it works","h":"#session-removal","p":419},{"i":447,"t":"A cookie named sessionID is stored for each session. These cookies are not programmatically removed after the ETHERPAD_COOKIE__EXPIRES_SECONDS period or upon Schulcloud logout.","s":"Cookie Management","u":"/docs/services/etherpad/How it works","h":"#cookie-management","p":419},{"i":449,"t":"Same session behavior also applies to legacy code. Env vars are used by both implementations. In contrast to the nest code in legacy code a session is created for every course and stored as a cookie for every etherpad.","s":"Legacy Topics","u":"/docs/services/etherpad/How it works","h":"#legacy-topics","p":419},{"i":451,"t":"The Room Member module manages the association between users and rooms, handling permissions and roles within rooms. This module is designed to be injected into the Room module for managing user access and roles within rooms.","s":"Room Member Module","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"","p":450},{"i":453,"t":"Room have one RoomMembers RoomMember have one UserGroup UserGroup have many Users Each User in UserGroup have Role This make room membership easy manage. Can give different roles to users in same room.","s":"Model Relationships","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#model-relationships","p":450},{"i":455,"t":"The core entity of this module is the RoomMemberEntity, which represents a user's membership in a room. @Entity({ tableName: 'room-members' }) export class RoomMemberEntity extends BaseEntityWithTimestamps implements RoomMemberProps { @Property() @Index() roomId!: ObjectId; @OneToOne(() => GroupEntity, { owner: true, orphanRemoval: true }) userGroup!: GroupEntity; @Property({ persist: false }) domainObject: RoomMember | undefined; } The important part is the userGroup property. We store the the members using the Group module. Why? => Because that allows us to assign each user a separate role within the room. e.g. ROOM_EDITOR, ROOM_VIEWER, ... These roles are not related to the user's global role.","s":"RoomMemberEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#roommemberentity","p":450},{"i":457,"t":"The userGroup property uses the GroupEntity from the Group module. This structure allows for multiple users to be associated with a room through a single group. class GroupEntity { id: EntityId; name: string; users: GroupUserEmbeddable[]; // other properties... }","s":"GroupEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupentity","p":450},{"i":459,"t":"Each user in the group is represented by a GroupUserEmbeddable: class GroupUserEmbeddable { user: User; role: Role; } This structure allows for flexible assignment of roles to users within the context of a room.","s":"GroupUserEmbeddable","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupuserembeddable","p":450},{"i":461,"t":"The RoomMemberEntity doesn't directly store user IDs or roles. Instead, it uses a GroupEntity to manage this information. This design allows for easy management of multiple users and roles for a single room. The roomId is stored directly on the RoomMemberEntity for efficient querying of members for a specific room. The domainObject property facilitates the separation between the database entity and the domain object. This is an optimization to not loose the unit of work using mikro-orm. This structure provides a flexible and scalable way to manage room memberships, allowing for complex permission and role scenarios within rooms.","s":"Key Points","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#key-points","p":450},{"i":463,"t":"The RoomMemberService is a service for the RoomMember entity. It provides methods for creating, updating, and deleting RoomMember entities. class RoomMemberService { constructor(private readonly roomMembersRepo: RoomMemberRepo) {} }","s":"Service","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#service","p":450},{"i":465,"t":"The RoomMemberService is designed to be injected into the Room module for managing user access and roles within rooms.","s":"Usage","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#usage","p":450},{"i":467,"t":"There is no API for now. Member specific writes/reads can be implemented by adding an API to the RoomMember module. Like adding/removing users to a room.","s":"API","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#api","p":450},{"i":470,"t":"This strategy handles the sync of schools, students, teachers and classes from TSP and replaces both the legacy TspBaseSyncer and TspSchoolSyncer. It responds to the target \"tsp\". The flow looks like this (some of the logic for syncing is done in the provisioning strategy which is shared with the login):","s":"TspSyncStrategy","u":"/docs/syncronizations/TSP/Architecture","h":"#tspsyncstrategy","p":468},{"i":473,"t":"To run the Etherpad service for the local development and resting purposes you can follow the below steps: Create a new dir that will contain all the needed files that we'll want to use when running the Etherpad service. Create a directory called sc-etherpad and then enter it, in Unix-like systems you can use the following command: `mkdir sc-etherpad && cd sc-etherpad` Create a new file called APIKEY.txt in it, with the following content: 381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd We'll use this file to set a fixed Etherpad's API key on the Etherpad server's start. Create also a file called settings.env with the following content: REQUIRE_SESSION=\"true\" PAD_OPTIONS_SHOW_CHAT=\"true\" DISABLE_IP_LOGGING=\"true\" DEFAULT_PAD_TEXT=\"Schreib etwas!\\n\\nDieses Etherpad wird synchronisiert, während du tippst, so dass alle Betrachter jederzeit den selben Text sehen. So könnt ihr auf einfache Weise gemeinsam an Dokumenten arbeiten.\" DB_TYPE=mongodb DB_URL=mongodb://host.docker.internal:27017/etherpad We'll use this file to provide all the needed environment variables to the Etherpad's server. Please note the last line, that contains the MongoDB connection string: DB_URL=mongodb://host.docker.internal:27017/etherpad Here we're using the host.docker.internal hostname which should make it possible for the Etherpad's container to connect to the host's local network and should work out of the box e.g. on macOS. But please modify it accordingly to your needs and your Docker's network configuration. An alternative configuaration would be to use DB_URL=mongodb://localhost:27017/etherpad and than add --network=\"host\" to the following docker run command. Next, start the Etherpad's container: docker run -d \\ -p 9001:9001 \\ --env-file ./settings.env \\ -v ./APIKEY.txt:/opt/etherpad-lite/APIKEY.txt \\ --name sc-etherpad \\ docker.io/etherpad/etherpad:2.0.0 Please note we're using the docker.io/etherpad/etherpad:2.0.0 image in the command above which might be not the one that is being used anytime in the future when you read this article. To make sure you're using the current version (the one that is currently being used in the SchulCloud platform), please refer to the https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/group_vars/infra/dof_etherpad.yml file. Now we should have the Etherpad service running locally on port 9001, we can verify it's working properly e.g. by fetching the current Etherpad's API version: ~ curl -v http://127.0.0.1:9001/api * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 200 OK < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 26 < ETag: W/\"1a-2HmNLqF3wPt+KQRlEmGwH4O+xu4\" < Date: Fri, 29 Mar 2024 13:27:00 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"currentVersion\":\"1.3.0\"} We can also verify that the API key has been set successfully, let's use the example API call from the Etherpad's documentation ( https://etherpad.org/doc/v2.0.0/#_example_1 ): ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\\?apikey\\=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd\\&name\\=Michael\\&authorMapper\\=7 * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api/1/createAuthorIfNotExistsFor?apikey=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd&name=Michael&authorMapper=7 HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 200 OK < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 66 < ETag: W/\"42-bg92QA1xRFO6QmkNRbNXhfsFBUM\" < Date: Fri, 29 Mar 2024 13:40:05 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"code\":0,\"message\":\"ok\",\"data\":{\"authorID\":\"a.7BgkAuzbHXR1G8Cv\"}} In case of an unsuccessful result (e.g. improperly set or invalid API key) we would receive the following response: ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\\?apikey\\=secret\\&name\\=Michael\\&authorMapper\\=7 * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 401 Unauthorized < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 54 < ETag: W/\"36-dbJd0O+vdNi3zPpwRXE+1EGLTho\" < Date: Fri, 29 Mar 2024 13:39:44 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"code\":4,\"message\":\"no or wrong API Key\",\"data\":null}","s":"Running the Etherpad server","u":"/docs/services/etherpad/Local setup","h":"#running-the-etherpad-server","p":471},{"i":476,"t":"FEATURE_TSP_SYNC_ENABLED - Activates the sync strategy inside the sync console WITH_TSP_SYNC - Activates the cronjob in Kubernetes TSP_API_CLIENT_BASE_URL - Base URL for the TSP API TSP_API_TOKEN_LIFETIME_MS - Lifetime of the access token for the TSP API in milliseconds TSP_SYNC_SCHOOL_LIMIT - The amount of schools the sync handles at once TSP_SYNC_SCHOOL_DAYS_TO_FETCH - The amount of days for which the sync fetches schools from the TSP API TSP_SYNC_DATA_LIMIT - The amount of school data updates the sync handles at once TSP_SYNC_DATA_DAYS_TO_FETCH - The amount of days for which the sync fetches school data from the TSP API FEATURE_TSP_MIGRATION_ENABLED - Activates the migration of TSP users within the sync TSP_SYNC_MIGRATION_LIMIT - The amount of users the sync migrates at once","s":"Configuration","u":"/docs/syncronizations/TSP/How it works","h":"#configuration","p":474},{"i":478,"t":"This is a console application that allows you to start the synchronization process for different sources.","s":"Sync console","u":"/docs/syncronizations/TSP/How it works","h":"#sync-console","p":474},{"i":480,"t":"To start the synchronization process, run the following command: npm run nest:start:console sync run Where is the name of the system you want to start the synchronization for. The currently available systems are: tsp - Synchronize Thüringer Schulportal. If the target is not provided, the synchronization will not start and the available targets will be displayed in an error message. { message: 'Either synchronization is not activated or the target entered is invalid', data: { enteredTarget: 'tsp', availableTargets: { TSP: 'tsp' }} }","s":"Usage","u":"/docs/syncronizations/TSP/How it works","h":"#usage","p":474},{"i":482,"t":"The TSP synchronization is controlled with the feature flag FEATURE_TSP_SYNC_ENABLED.","s":"TSP synchronization","u":"/docs/syncronizations/TSP/How it works","h":"#tsp-synchronization","p":474},{"i":484,"t":"The terms Redis and Valkey are used here synonymously and should describe the used in-memory-database.","s":"How it works","u":"/docs/tldraw-server/How it works","h":"","p":483},{"i":486,"t":"AUTHORIZATION_API_HOST - host address of the authorization endpoint (schuldcloud-server) FEATURE_TLDRAW_ENABLED - flag determining if tldraw is enabled LOGGER_LOG_LEVEL - logging level LOGGER_EXIT_ON_ERROR - flag whether an error will cause the application to stop METRICS_COLLECT_DEFAULT - flag whether the default metrics shall be collected REDIS_CLUSTER_ENABLED - flag whether a redis cluster or used or not REDIS_URL - redis connection string REDIS_SENTINEL_SERVICE_NAME - name of the redis sentinel service REDIS_PREFIX - prefix to be used with redis database REDIS_SENTINEL_NAME - name of the redis sentinel REDIS_SENTINEL_PASSWORD - password for the redis sentinel S3_ACCESS_KEY - access key for S3 storage S3_BUCKET - name of the S3 bucket S3_ENDPOINT - URL of the S3 service S3_PORT - port number for the S3 service S3_SECRET_KEY - secret key for S3 storage S3_SSL - flag to enable or disable SSL for S3 storage TLDRAW_ASSETS_ENABLED - enables uploading assets to tldraw board TLDRAW_ASSETS_MAX_SIZE_BYTES - maximum asset size in bytes TLDRAW_ASSETS_ALLOWED_MIME_TYPES_LIST - list of allowed assets MIME types TLDRAW_WEBSOCKET_PATH - path for the tldraw websocket connection TLDRAW_WEBSOCKET_URL - URL for the tldraw websocket connection WORKER_MIN_MESSAGE_LIFETIME - minimal lifetime of a update message consumed by the worker WORKER_TASK_DEBOUNCE - minimum idle time (in milliseconds) of the pending task messages to be claimed WORKER_TRY_CLAIM_COUNT - the maximum number of task messages to claim X_API_ALLOWED_KEYS - list of allowed xAPI keys In order to have deletion functionality fully working locally you have to fill those feature flags, e.g.: tldraw-server : X_API_ALLOWED_KEYS=\"7ccd4e11-c6f6-48b0-81eb-abcdef123456\" schulcloud-server : TLDRAW_ADMIN_API_CLIENT_API_KEY=\"7ccd4e11-c6f6-48b0-81eb-abcdef123456\" TLDRAW_ADMIN_API_CLIENT_BASE_URL=\"http://localhost:3349\"","s":"Configuration","u":"/docs/tldraw-server/How it works","h":"#configuration","p":483},{"i":488,"t":"The Tldraw board can be created by the user on the courses ColumnBoard. It has a representation in ColumnBoard as DrawingElement inside a card (BoardNode in db). After creating representation as DrawingElement we can enter actual Tldraw SPA client on click. User enters ColumnBoard and creates Representation of whiteboard (tldraw) in Card. Data is saved and feedback with proper creation is given - user can see Representation and can enter whiteboard. By entering whiteboard user is redirected to SPA tldraw-client. Tldraw-client is starting WS connection with tldraw-server. Tldraw-server first checks if user has permission to this resource (by checking if user has a permission to Representation of whiteboard - BoardNode). Id of Representation is same as drawingName, which is visible in tldraw-client url. If user has permission tldraw-server is allowing to remain connected and getting drawing data from S3 storage. If there is no drawing data available, the tldraw-server will create a new document automatically.","s":"Create","u":"/docs/tldraw-server/How it works","h":"#create","p":483},{"i":491,"t":"User joins tldraw board. Tldraw-client connects to one of the tldraw-server pods and tries to establish websocket connection. Tldraw-server calls schulcloud-server via HTTP requests to check user permissions. If everything is fine, the websocket connection is established. Tldraw-server gets stored tldraw board data from S3 storage and sends it via websocket to connected user. Tldraw-server starts subscribing to Redis PUBSUB channel corresponding to tldraw board name to listen to changes from other pods.","s":"Connection","u":"/docs/tldraw-server/How it works","h":"#connection","p":483},{"i":493,"t":"Tldraw-client sends user's drawing changes to the tldraw-server via websocket connection. Tldraw-server stores the board update in the valkey db - basically creates a diff between what's already stored and what's being updated. Tldraw-server pushes the update to the boards Redis channel so that connected clients on different pods have synchronized board data. Other pods subscribing to Redis channel send updates to their connected clients via websocket whenever they see a new message on Redis channel. Finally the worker will run and persist the current state of the drawing data by applying all currently available updates from valkey on top of the stored drawing data and update the S3 storage accordingly.","s":"Sending updates/storing data","u":"/docs/tldraw-server/How it works","h":"#sending-updatesstoring-data","p":483},{"i":495,"t":"User from schulcloud app in ColumnBoard deletes whiteboard (tldraw) instance form Card. Schulcloud-server is removing representation data in schulcloud-database - BoardNodes collection. Schulcloud-server is calling tldraw-server to delete all data that has given id. Tldraw-server sends a delete action via websocket to inform connected clients about deletion. Clients redirect away from Tldraw-board to ensure that no new messages are added to valkey database. Finally the worker will run, clear all updates and data from the valkey db and delete the drawing data from the S3 storage.","s":"Delete","u":"/docs/tldraw-server/How it works","h":"#delete","p":483},{"i":498,"t":"Images/GIFs can be uploaded into tldraw whiteboard by every user with access to the board. We use SVS FileStorageService to physically store uploaded assets while tldraw only holds URL to a resource. The files are uploaded by calling schulcloud-api's fileController upload endpoint. This is possible because tldraw is represented as a boardnode called drawing-element. Mongo id of this drawing-element is a roomId used in URL param when connecting to a specific board.","s":"files upload","u":"/docs/tldraw-server/How it works","h":"#files-upload","p":483},{"i":500,"t":"The deletion of files is handled directly by the tldraw-client itself. On deletion in the UI, the client sends a delete request to the file storage. While awaiting the answer from file storage the editing of the Tldraw-board is blocked to prevent race conditions to the file storage.","s":"files deletion","u":"/docs/tldraw-server/How it works","h":"#files-deletion","p":483},{"i":503,"t":"In tldraw-server, Yjs, Redis and S3 storage are integrated to support collaborative drawing features, data synchronization, and persistent storage in a scalable and efficient manner. The combination of Yjs (used for collaborative editing), Redis (for real-time data synchronization), and S3 (for file storage) enables the platform to handle complex collaborative interactions and large-scale, persistent storage of drawing data.","s":"Introduction","u":"/docs/tldraw-server/Technical details","h":"#introduction","p":501},{"i":505,"t":"Real-time Collaboration with Yjs: Yjs is the backbone of real-time collaboration in tldraw. It provides a CRDT-based (Conflict-free Replicated Data Type) framework for handling shared documents where changes made by one user are automatically and consistently synchronized across all other users in the same session. In tldraw, the drawing canvas or document is represented as a Yjs document. Users can draw shapes, lines, text, or modify the canvas in real time. The Yjs document tracks all changes in the form of small, incremental edits. These edits could be changes to the position of objects, the creation of new objects (e.g., shapes or lines), or modification of existing elements. . Redis for Real-Time Data Synchronization: Redis is used to store and synchronize these Yjs documents across multiple users in real time. Redis, being a fast in-memory key-value store, provides low-latency updates that are crucial for real-time collaboration. It is used for: Broadcasting updates: When one user makes a change, Yjs sends that change to the Redis server, which then distributes the change to all other connected users. Data persistence: Changes are stored in Redis and can be fetched by other users at any time to maintain consistency. The use of Redis Pub/Sub allows different instances of the tldraw application to subscribe to channels. When one user makes a change, the Redis system publishes the change, and other users (who are subscribed to that document) get updated immediately. S3 Storage for Persistent and Large-Scale File Storage: While Redis ensures real-time synchronization and collaboration, S3 (Amazon Simple Storage Service) is used for persistent storage of larger files or data that need to be saved across sessions. S3 is highly scalable and can store large amounts of data. For tldraw, S3 is primarily used for: Storing canvases and drawings: While Redis handles real-time data synchronization and storage of changes, the worker service is responsible for periodically persisting the state of the collaborative canvas to S3 storage. S3 as a file store: Unlike Redis, which is an in-memory store designed for fast access and transient data, S3 is optimized for storing larger data in a persistent manner. This makes it suitable for storing media files, large canvas snapshots, and other assets that don't need to be constantly updated in real-time. Integration Between Redis and S3: Redis and S3 serve different but complementary purposes: Redis handles real-time synchronization of drawing changes and interactions, ensuring that users see each other's edits in near real-time. S3 handles long-term storage and backup of the drawings or canvases themselves. For example, when a user closes the app or saves their session, the drawing data is saved to S3. The worker service will save snapshots of the collaborative document or canvas to S3 periodically, ensuring that the state of the canvas is preserved even if a user disconnects or the server restarts. How tldraw's Workflow Would Look Using Redis and S3: Step 1: Collaborative Drawing Session Users interact with the tldraw canvas, making real-time changes (drawing shapes, text, etc.). The changes are immediately propagated through Yjs and Redis. Redis stores these changes in memory and broadcasts them to other users. Each user's drawing operations are synchronized, so everyone sees the same live canvas. Step 2: Saving the Canvas On a regular basis the worker service will send a snapshot of the canvas (or the entire Yjs document) to S3. This can include data about the shapes, their positions, colors, and any other relevant canvas data. The snapshot can be stored as a JSON object, an image, or another format, depending on how tldraw chooses to serialize the data. Step 3: Retrieving Saved Data When a user returns to the drawing session or opens the application at a later time, the application queries S3 for the last saved canvas snapshot. Once retrieved, the snapshot is loaded back into the application, allowing users to continue editing from where they left off. Redis can be used in the background to ensure that any new changes made by users are synchronized in real time while they are working on the canvas. Scalability and Fault Tolerance: Redis Scaling: As we are using Valkey, by default there will only be one primary Valkey instance and we will have two replica instances ready to jump in once the primary instance fails. But these two replica sets make sure, that Valkey is always available. S3 Scaling: S3 is designed to scale automatically and handle large amounts of storage without performance degradation. This makes it ideal for storing large or numerous drawing assets, like high-resolution images or full snapshots of large canvases. Tldraw-server Scaling: So far the number of pods the tldraw-server is deployed on is fixed and no load based scaling is applied so far.","s":"How tldraw Uses Redis and S3 Storage:","u":"/docs/tldraw-server/Technical details","h":"#how-tldraw-uses-redis-and-s3-storage","p":501},{"i":507,"t":"User A and User B start a collaborative session in tldraw, and they can see each other's updates in real time (thanks to Redis). After some time, the worker service saves the drawing to S3, and now the drawing is stored in S3 as a persistent snapshot. User B, who was not connected when the session ended, can later load the canvas from S3, where the most recent version is stored. Meanwhile, as new users join the session, Redis continues to handle the real-time synchronization of the drawing, ensuring smooth interaction.","s":"Example Scenario:","u":"/docs/tldraw-server/Technical details","h":"#example-scenario","p":501},{"i":509,"t":"In tldraw, Redis and S3 are integrated to deliver a collaborative and scalable experience. Redis ensures real-time synchronization of drawing changes among multiple users, while S3 provides persistent and scalable storage for canvas data. This combination allows tldraw to offer seamless collaboration, persistent storage, and fault-tolerant handling of large-scale data.","s":"Conclusion:","u":"/docs/tldraw-server/Technical details","h":"#conclusion","p":501},{"i":512,"t":"Tldraw is deployed as a separate application from schoulcloud-server and consists of the following deployments : tldraw-server-deployment - deployment for tldraw-server's instances. tldraw-worker-deployment - deployment for worker's instances. tldraw-client-deployment - deployment for tldraw-client's instances.","s":"Deployments","u":"/docs/tldraw-server/Technical details","h":"#deployments","p":501},{"i":514,"t":"tldraw-config.controller.ts - controller that exposes tldraw server configuration to be used by the tldraw client. tldraw-document.controller.ts - controller that expose HTTP deletion method outside the tldraw-server application. tldraw-document.service.ts - service used by TldrawDocumentController. redis.service.ts - encapsulates the logic for creating and managing Redis instances, supporting both standalone and sentinel configurations, and integrates seamlessly with the NestJS framework. ioredis.adapter.ts - encapsulates the logic for interacting with Redis, including defining custom commands and subscribing to channels. It leverages the ioredis library and integrates with the application's configuration and logging systems to provide a robust and flexible Redis adapter. api.service.ts - API service for Redis. ws.service.ts - Responsibe for Redis communication. metrics.service.ts - service resonsible for storing application-level metrics. worker.service.ts - responsible for persisting the current state of changed tldraw documents into the file storage. On the backend side we are also using Yjs library to store tldraw board in memory and to calculate diffs after the board is changed.","s":"Tldraw-server code structure","u":"/docs/tldraw-server/Technical details","h":"#tldraw-server-code-structure","p":501},{"i":517,"t":"stores/setup.ts – this file provides a real-time collaboration environment for a drawing application using the WebSocket and Yjs libraries. hooks/useMultiplayerState.ts – custom hook for managing multiplayer state. App.tsx – main application component integrating Tldraw and multiplayer state.","s":"Key Files","u":"/docs/tldraw-server/Technical details","h":"#key-files","p":501},{"i":519,"t":"The frontend of the project is built using React and leverages various libraries and tools for enhanced functionality. Here is an overview of the key frontend technologies: React: A JavaScript library for building user interfaces. Yjs: A real-time collaboration framework for synchronizing shared state. Tldraw: A library for drawing functionalities in the application. We use the old version of tldraw: https://github.com/tldraw/tldraw-v1, after the tldraw team releases the official update of the new version, we will work on the new version and integrate it with the needs of our users.","s":"Frontend Technologies","u":"/docs/tldraw-server/Technical details","h":"#frontend-technologies","p":501},{"i":521,"t":"Yjs is integrated into the project for real-time collaboration. The central state (shapes, bindings, assets) is managed using Yjs maps. store.ts handles the configuration of Yjs, WebSocket connections, and provides centralized maps for shapes, bindings, and assets useMultiplayerState.ts -This hook manages the multiplayer state, including loading rooms, handling file system operations, and updating Yjs maps: Mounting and handling changes in Tldraw App. Presence management and user updates. File system operations like opening and saving projects. useTldrawUiSanitizer.ts​ This hook is designed to observe changes in the DOM, specifically targeting certain buttons and a horizontal rule (< hr>), and hides them if they match a specific ID pattern. We hide this elements and left just only Language and Keyboard shortcuts. Event Handling​ onMount: Handles mounting of the Tldraw app. onChangePage: Manages page changes and updates Yjs maps. onUndo and onRedo: Handle undo and redo operations. onChangePresence: Manages presence changes in the collaborative environment. onAssetCreate: This function is triggered when a user attempts to upload an asset (like an image or a file). Useful links​ https://tldraw.dev/ - documentation for the new version of tldraw https://old.tldraw.com/ - tldraw live application https://github.com/tldraw/tldraw-v1 - tldraw v1 repo https://github.com/yjs/y-redis - code from this package was used to add Redis as a persistence to tldraw https://discord.com/invite/SBBEVCA4PG discord channel with open questions and answers https://grafana.dbildungscloud.dev/d/b6b28b2b-3129-4772-8102-e32981d2c2e3/devops-tldraw-metrics?orgId=1&refresh=1m&var-source=sc-dev-dbc&var-env=main&var-env=tldraw-debugging - grafana v https://grafana.dbildungscloud.org/d/b6b28b2b-3129-4772-8102-e32981d2c2e0/devops-tldraw?orgId=1&from=now-6h&to=now&refresh=1m - grafana metrics https://github.com/nimeshnayaju/yjs-tldraw - yjs with tldraw POC https://github.com/yjs/y-websocket/tree/master/bin - yjs/y-websocket repo https://github.com/erdtool/yjs-scalable-ws-backend/tree/main - Yjs scalable WS backend with redis example https://teamchat.dbildungscloud.de/channel/G9hJWv92zXEESKK3X - rocketchat discussion \"tldraw syncronisation for release again\" https://teamchat.dbildungscloud.de/group/SagK4sCyujhu6yZr8 - rocketchat discussion \"Tldraw deployment\"s","s":"State Managment","u":"/docs/tldraw-server/Technical details","h":"#state-managment","p":501},{"i":524,"t":"Run redis i.e. in a docker container, it will work on localhost:6379 by default which is what the REDIS_URI env var is set to, for example on wsl: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-windows/ . In the tldraw-server make a copy of .env.default and rename it to .env, in order to use the default configuration. To run npm run nest:start:dev (schulcloud-server) npm run nest:start:files-storage:dev (schulcloud-server with s3, if you want to upload files) npm run start:server:dev (tldraw-server) npm run start:worker:dev (tldraw-server) npm run dev (schulcloud-client) npm run servce (nuxt-client) npm run dev (tldraw-client)","s":"To run tldraw locally:","u":"/docs/tldraw-server/Local setup","h":"#to-run-tldraw-locally","p":522},{"i":526,"t":"Go to a course. Go to 'Column board'. Create a new card and a new 'Whiteboard' element within it, then click it. A new browser tab with URL like: http://localhost:4000/tldraw?roomName=65c37329b2f97cc714d31c00 will open. Change the port part from 4000 to 3046, which is the default port of tldraw-client app. You should see a working tldraw whiteboard now.","s":"Create new whiteboard:","u":"/docs/tldraw-server/Local setup","h":"#create-new-whiteboard","p":522},{"i":529,"t":"Our architecture aims to achieve the following goals: Maintainability it should be easy as possible to make changes that do not change the behaviour of the system (refactoring) it should be easy to exchange entire components of the system, without impact on other components. Extendability it should be easy to add new functionality to the system Agility it should be easy to react to changing requirements during our development process Change Security it should be easy to determine the correctness of the system after making any changes","s":"Goals","u":"/docs/schulcloud-server/architecture","h":"#goals","p":527},{"i":531,"t":"In order to achieve these goals, we try to follow the principles detailed below. These principles apply to all layers of our software, from lines of code and methods to modules and architectural layers. Single Responsibility / Seperation of Concerns each piece of code should have a single layer of abstraction/detail each piece of code should have a single reason to change Open/Closed Principle design to be open to extension, but closed to modification Liskov Substitution the specific input may be more generic than its interface the specific output may be more specialized than its interface Interface Segregation multiple small interfaces are preferred over big interfaces Dependency Inversion Principle always depend on interfaces, not implementations higher level parts should not depend on lower level parts. Keep It Simple (KISS) any piece of code should be simple and readable any logic should be broken down to be trivial beware of overenginiering and premature optimisation You Aint Gonna Need It (YAGNI) keep decisions open for as long as possible build only what you need to build, stay flexible for future requirements Do Not Repeat Yourself (DRY) do not solve the same responsability or concern in multiple places beware of things that look similar, but are not. for example, things that change for different reasons should not be combined, even if their code looks the same","s":"Principles","u":"/docs/schulcloud-server/architecture","h":"#principles","p":527},{"i":533,"t":"We generally distinguish three different layers in our server architecture: The API Layer, the Repository Layer, and the Domain Layer. Note that based on the Dependency Inversion Principle, the Domain Layer does not have any dependencies. Instead, both the API and Repository Layer depend on its abstractions.","s":"Server Layer Architecture","u":"/docs/schulcloud-server/architecture","h":"#server-layer-architecture","p":527},{"i":535,"t":"The Domain Layer contains the business logic of the application. As mentioned above, it is not allowed to know about anything outside the domain layer itself. Any operation within the system is defined by a usecase (UC). It describes how an external actor, for example a user, can interact with the system. Each usecase defines what needs to be done to authorize it, and what needs to be done to fulfill it. To this end, it orchestrates services. A service is a public part of a domain module, that provides an interface for logic. It might be a simple class doing simple calculations, an interface to a complex hierarchy of classes within a module, or anything in between. The domain layer might also define other classes, types, and interfaces to be used internally by its services, as well as the interface definitions for the repository layer. That way, the domain does not have to depend on the repositories, and the repositories have to depend on the domain instead (dependency inversion) TODO: the exact way of implementing the interfaces between repositories and domain layer is still in active discussion and development within the architecture chapter","s":"Domain Layer","u":"/docs/schulcloud-server/architecture","h":"#domain-layer","p":527},{"i":537,"t":"The API Layer is responsible for providing the API that is exposed outside the system, and to map the various incoming requests into domain DTOs. The params.dto and response.dto are used to automatically generate the API Documentation based on openAPI. The params.dto also contains information that is used for input validation. The controller is responsible for sanitizing and authenticating incoming requests, and to map to and from the format that the domain usecase implementations expect. To this end, mappers are being used.","s":"API Layer","u":"/docs/schulcloud-server/architecture","h":"#api-layer","p":527},{"i":539,"t":"The Repository Layer is responsible for outgoing requests to external services. The most prominent example is accessing the database, but the same principles apply for sending emails or other interactions with external systems. In order to access these external systems without knowing them, the domain layer may define interfaces that describe how it would like to use external services in its own domain language. The repositories implement these interfaces, recieving and returning exclusively objects or dtos defined in the domain. The datamodel itself is defined through Entities, that have to be mapped into domain objects before they can be returned to the domain layer. We use MikroORM to create, persist and load the entities and their references among each other.","s":"Repository Layer","u":"/docs/schulcloud-server/architecture","h":"#repository-layer","p":527},{"i":541,"t":"The codebase is broken into modules, each dealing with a part of the businesslogic, or seperated technical concerns. These modules define what code is available where, and ensure a clean dependency graph. All Code written should be part of exactly one module. Each module contains any services, typedefinitions, interfaces, repositories, mappers, and other files it needs internally to function. When something is needed in more than one module, it needs to be explicitly exported by the module, to be part of its public interface. It can then be imported by other modules. Services are exported published via the dependency injection mechanism provided by Nestjs. @Module({ providers: [InternalRepo, InternalService, PublicService], exports: [PublicService], }) export class ExampleModule {} @Module({ imports: [ExampleModule] providers: [SomeOtherService], }) export class OtherModule {} Notice that in the above example, the PublicService can be used anywhere within the OtherModule, including in the SomeOtherService, whereas the InternalRepo and InternalService can not. Things that cant be injectables, like types and interfaces, are exported via the index file at the root of the module. Code that needs to be shared across many modules can either be put into their own seperate module, if there is a clearly defined seperate concern covered by it, or into the shared module if not.","s":"Modules","u":"/docs/schulcloud-server/architecture","h":"#modules","p":527},{"i":543,"t":"The controllers and the corresponding usecases, along with the api tests for these routes, are seperated into api modules @Module({ imports: [ExampleModule] providers: [ExampleUc], controllers: [ExampleController], }) export class ExampleApiModule {} This allows us to include the domain modules in different server deployments, without each of them having all api definitions. This also means that no usecase can ever be imported, as only services are ever exported, enforcing a seperation of concerns between logic and orchestration.","s":"Api Modules","u":"/docs/schulcloud-server/architecture","h":"#api-modules","p":527},{"i":545,"t":"The application is split into different modules that implement different parts of our domain. The exact split of modules is still work in progress, or left open as implementation detail. Some important considerations are: things with high cohesion and coupling should be in the same module things with low coupling should be in seperate modules the modules define an explicit public interface of usecases and types they expose to other modules no module should ever try to access a class of a different module that is not explicitly exported no injectable should ever be defined in more than one module a module should only export services to be used by other modules. a module that other modules might need to import, especially in another mikroservice, should not contain controllers.","s":"Horizontal Architecture","u":"/docs/schulcloud-server/architecture","h":"#horizontal-architecture","p":527},{"i":549,"t":"The name of a function should clearly communicate what it does. There should be no need to read the implementation of a function to understand what it does. There are a few keywords that we use with specific meaning: \"is...\"​ isTask(), isPublished(), isAuthenticated(), isValid() A function with the prefix \"is...\" is checking wether the input belongs to a certain (sub)class, or fulfils a specific criteria. The function should return a boolean, and have no sideeffects. \"check...\"​ checkPermission(), checkInputIsValid() A function with the prefix \"check...\" is checking the condition described in its name, throwing an error if it does not apply. \"has...\"​ hasPermission(), similar to \"is...\", the prefix \"has...\" means that the function is checking a condition, and returns a boolean. It does NOT throw an error.","s":"Naming functions","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-functions","p":546},{"i":551,"t":"avoid directly returning the result of some computation. Instead, use a variable to give the result of the computation a name. Exceptions can be made when the result of the computation is already clear from the function name, and the function is sufficiently simple to not occlude its meaning. public doSomething(): FileRecordParams[] { // ... more logic here const fileRecordParams = fileRecords.map((fileRecord) => Mapper.toParams(fileRecord)); // hint: this empty line can be increase the readability return fileRecordParams; } public getName(): String { return this.name; } public getInfo(): IInfo { // ... more logic here return { name, parentId, parentType }; // but if the return include many keys, please put it first to a const }","s":"Avoid direct returns of computations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-direct-returns-of-computations","p":546},{"i":553,"t":"function badExample(): void { doSomething(this.extractFromParams(params), this.createNewConfiguration()); } function goodExample(): void { const neededParams = this.extractFromParams(params); const configuration = this.createNewConfiguration(); doSomething(neededParams, configuration); }","s":"avoid directly passing function results as parameters","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-directly-passing-function-results-as-parameters","p":546},{"i":555,"t":"public doSomething(): FileRecords[] { //... return fileRecords }","s":"explicit return type","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#explicit-return-type","p":546},{"i":558,"t":"In most cases, it should not matter to the reader/external code wether something is an interface or an implementation. Only prefix the \"I\" when necessary, or when its specifically important to the external code to know that its an interface, for example when external code is required to implement the interface. interface CourseRepo { getById(id): Course // ... } class InMemoryCourseRepo implements CourseRepo { getById(id): Course { return new Course() } }","s":"Avoid the \"I\" for interfaces","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-the-i-for-interfaces","p":546},{"i":561,"t":"Classes are declared in the following order: properties constructor methods Example: export class Course { // 1. properties name: string; // more properties... // 2. constructor constructor(props: { name: string }) { // ... } // 3. methods public getShortTitle(): string { // ... } // more methods... }","s":"Order of declarations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#order-of-declarations","p":546},{"i":563,"t":"Classes should be named in CamelCase. They should have a Suffix with the kind of Object they represent, and from the beginning to the end go from specific to general. CourseController CourseCreateBodyParam CourseCreateQueryParam CourseCreateResponse CourseDtoMapper CourseUc CourseAuthorisationDto CourseDo CourseService CourseRepo CourseEntity CourseEntityMapper","s":"Naming classes","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-classes","p":546},{"i":565,"t":"You should always try to write code in a way that does not require further explanation to understand. Use proper names for functions and variables, and extract code and partial results into functions or variables in order to name them. If you feel like a function needs a JsDoc, treat that as a codesmell, and try to rewrite the code in a way that is more self-explanatory.","s":"Do NOT use JsDoc","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-not-use-jsdoc","p":546},{"i":567,"t":"empty lines help to structure code. Use them wherever you want to seperate parts of a function from each other (and think about further extracting functions). Common uses include: before return statement before and after an if/else statement between test sections (arrange, act, assert) between \"it()\" statements in tests","s":"Do use empty lines","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-use-empty-lines","p":546}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/2",[0,4.785,1,2.313,2,2.257,3,5.948,4,4.617,5,4.785,6,3.576,7,2.741,8,2.5,9,2.285,10,2.951,11,3.146,12,3.576,13,3.813,14,2.863,15,3.146,16,3.902,17,4.468,18,1.803,19,4.977]],["t/5",[7,3.236,8,1.853,14,2.638,20,0.855,21,3.881,22,1.984,23,4.841,24,2.633,25,6.395,26,2.456,27,1.271,28,2.805,29,5.35,30,4.973,31,4.792,32,3.994,33,5.35,34,5.275,35,5.35,36,4.409,37,5.765,38,2.899,39,2.761,40,3.513,41,5.765]],["t/7",[20,1.078,30,5.935,38,2.825,39,2.691,40,3.423,42,9.028,43,4.53,44,5.308,45,4.469,46,4.427,47,6.232,48,4.012,49,4.642,50,4.891,51,4.911,52,3.892,53,5.618]],["t/9",[1,1.015,2,0.99,7,1.898,8,0.882,9,1.002,12,3.067,17,3.094,18,2.127,20,0.796,22,2.098,24,1.914,27,1.44,28,1.336,34,5.272,36,2.099,46,2.64,53,2.745,54,2.547,55,2.514,56,5.515,57,6.544,58,1.138,59,1.202,60,2.427,61,1.569,62,2.456,63,2.226,64,2.547,65,2.183,66,1.953,67,2.427,68,1.404,69,2.547,70,2.427,71,1.846,72,3.045,73,3.961,74,2.745,75,2.476,76,2.281,77,4.104,78,3.145,79,5.656,80,1.636,81,1.754,82,2.399,83,4.104,84,7.051,85,2.399,86,1.902,87,2.399,88,2.026,89,3.045,90,3.045,91,2.547,92,3.045,93,2.547,94,1.683,95,2.547,96,3.045,97,1.404,98,1.636,99,2.281,100,3.045,101,2.026,102,2.183,103,3.045,104,2.399,105,2.099,106,1.601,107,1.96,108,2.547,109,2.026,110,2.099]],["t/11",[7,2.97,8,1.626,12,2.891,20,0.751,24,3.037,34,5.461,36,3.868,55,2.417,56,4.204,57,4.694,63,1.847,67,2.834,73,5.003,75,2.891,99,4.204,111,4.023,112,4.443,113,3.656,114,5.058,115,4.422,116,4.204,117,4.023,118,4.204,119,2.385,120,6.291,121,3.082,122,4.422,123,5.611,124,4.694,125,5.611,126,2.951,127,4.694,128,4.694,129,5.611,130,3.315]],["t/13",[7,2.656,8,1.949,9,1.57,12,4.014,13,3.695,15,2.162,18,1.239,22,1.48,24,2.162,27,1.585,34,3.071,55,1.533,66,1.939,73,4.475,78,2.992,83,4.082,84,7.023,85,3.759,86,2.979,87,3.759,88,3.174,93,3.991,94,1.671,101,3.174,114,4.3,131,2.895,132,2.319,133,4.3,134,4.691,135,4.771,136,4.3,137,4.771,138,6.063,139,4.771,140,4.771,141,4.771,142,3.574,143,4.3,144,3.174,145,3.759,146,3.991,147,3.991]],["t/15",[3,2.722,7,1.559,8,1.699,9,3.036,10,2.493,11,1.789,18,1.523,20,0.936,27,1.284,55,2.885,63,1.93,76,2.958,78,2.252,81,3.378,87,3.111,116,2.958,142,6.497,148,3.948,149,3.948,150,7.745,151,8.276,152,3.948,153,4.073,154,3.297,155,3.948,156,3.948,157,3.559,158,3.948,159,3.948,160,4.906,161,3.948,162,2.169,163,3.948,164,3.948,165,3.111,166,3.151,167,3.302,168,3.084,169,3.559,170,1.956,171,4.043,172,2.332,173,2.958,174,2.626,175,3.948,176,3.948,177,3.948,178,2.542,179,2.831,180,3.302,181,2.626,182,2.831,183,2.722,184,3.948,185,3.948,186,3.948]],["t/17",[11,2.899,18,1.661,27,0.992,38,2.899,55,2.055,63,2.105,78,3.147,113,3.984,147,5.35,162,4.501,166,3.436,183,4.409,187,5.765,188,5.039,189,4.586,190,5.35,191,5.039,192,5.039,193,4.586,194,3.779,195,5.765,196,4.409,197,4.117,198,3.23,199,4.719,200,3.168,201,6.395]],["t/19",[3,2.229,7,1.277,8,2.024,9,2.037,10,1.374,12,4.689,13,4.602,14,2.078,16,1.818,18,0.84,22,1.563,24,2.246,27,1.65,34,2.081,46,1.776,49,1.862,50,1.962,55,1.989,60,2.544,81,4.362,102,2.318,106,2.649,113,2.449,119,1.374,126,2.649,133,4.541,136,4.541,138,4.541,170,1.602,181,3.351,187,2.914,197,3.984,202,1.7,203,3.233,204,2.706,205,2.92,206,1.49,207,2.019,208,2.548,209,3.233,210,5.038,211,6.189,212,2.019,213,2.914,214,1.465,215,5.506,216,3.473,217,3.351,218,2.548,219,3.233,220,2.832,221,3.233,222,3.233]],["t/21",[3,4.785,7,2.741,8,2.72,9,2.84,10,2.951,12,3.576,13,3.813,16,3.902,20,0.928,24,2.231,60,3.505,107,4.468,213,6.257,223,5.806,224,4.468,225,6.941,226,4.212,227,5.806,228,4.785]],["t/23",[1,2.528,15,3.439,18,1.971,62,3.13,174,5.048,229,6.84,230,4.483,231,5.441,232,5.979,233,3.99,234,6.84,235,7.588,236,4.739]],["t/25",[2,2.51,14,2.407,15,2.645,166,4.147,232,4.598,237,3.541,238,4.881,239,2.69,240,5.321,241,4.023,242,5.835,243,7.797,244,4.598,245,5.835,246,4.685,247,6.082,248,5.25,249,5.835,250,6.958,251,4.598,252,6.082,253,3.361,254,4.881,255,5.835,256,3.644,257,3.644,258,4.881,259,4.372,260,3.541,261,4.881,262,5.26]],["t/27",[8,2.07,10,3.037,12,3.68,14,2.947,20,0.956,66,2.903,200,4.711,246,4.336,263,3.924,264,4.461,265,5.629,266,3.838,267,5.629,268,4.221,269,2.947,270,7.144,271,5.976]],["t/29",[8,2.268,10,2.534,15,1.829,18,1.841,20,0.54,22,1.252,24,1.297,61,2.079,62,2.458,63,2.333,94,1.413,104,6.168,119,1.715,144,3.965,157,2.449,162,3.274,170,3.511,208,4.696,230,3.521,233,4.117,266,3.202,271,4.986,272,2.893,273,5.96,274,3.637,275,4.696,276,3.375,277,4.986,278,3.351,279,3.01,280,6.168,281,5.96,282,3.179,283,2.782,284,2.387,285,3.637,286,2.384,287,3.023,288,3.179,289,8.351,290,5.96,291,5.96,292,5.373,293,3.837,294,5.31,295,3.637,296,5.96,297,3.179,298,2.598,299,1.64,300,1.926,301,2.384,302,1.64,303,4.035]],["t/31",[14,3.265,20,1.059,241,5.457,263,4.348,293,5.096,304,4.163,305,3.473,306,3.779,307,5.457]],["t/33",[0,4.371,18,2.553,38,2.874,59,3.882,68,2.922,111,4.546,174,4.218,197,4.082,220,3.564,231,4.546,253,3.652,306,3.89,308,4.082,309,3.848,310,4.75,311,6.34,312,5.303,313,6.34,314,4.371,315,6.34,316,3.141,317,3.406,318,5.715]],["t/35",[1,2.313,10,2.951,18,1.803,19,4.977,80,3.729,82,5.469,94,2.43,174,4.617,202,3.65,220,3.902,231,4.977,232,5.469,263,3.813,288,5.469,319,4.785,320,4.785,321,5.469,322,3.902,323,5.469,324,6.941,325,4.212,326,2.665]],["t/37",[0,1.317,2,1.064,7,1.292,8,2.037,10,0.812,11,0.866,12,0.984,13,1.049,16,1.074,18,1.732,20,0.255,21,1.159,27,1.676,30,3.085,38,0.866,59,2.008,62,1.349,63,0.629,97,1.508,101,1.27,126,1.72,147,1.597,153,1.72,154,1.074,162,1.049,196,1.317,199,1.1,231,3.645,233,1.004,243,2.949,246,1.985,248,1.159,252,1.505,254,4.782,257,1.193,258,1.597,260,1.159,261,1.597,262,2.949,263,2.357,271,1.597,274,1.721,275,1.505,276,1.597,277,2.736,283,1.317,284,0.765,295,1.721,304,1.72,305,1.435,306,0.912,307,1.317,309,1.159,316,2.832,323,1.505,327,1.27,328,6.667,329,2.106,330,4.291,331,3.809,332,1.229,333,2.578,334,2.736,335,1.91,336,1.505,337,1.505,338,1.721,339,8.933,340,2.346,341,1.597,342,2.578,343,1.721,344,1.91,345,1.91,346,2.472,347,1.91,348,1.91,349,1.91,350,1.193,351,1.505,352,2.949,353,1.721,354,1.369,355,1.91,356,1.91,357,1.91,358,1.91,359,1.91,360,3.505,361,2.043,362,1.317,363,1.431,364,1.91,365,4.006,366,2.949,367,2.736,368,1.91,369,1.91,370,1.505,371,2.578,372,1.91,373,1.597,374,1.1,375,1.597,376,1.91,377,2.451,378,1.721,379,1.91,380,1.27,381,1.721,382,1.721,383,3.271,384,2.451,385,1.91,386,1.91,387,1.91,388,1.91,389,0.788]],["t/39",[2,2.276,4,2.626,5,2.722,7,1.559,8,2.244,12,2.034,13,2.169,16,3.297,17,2.542,18,2.332,27,0.91,38,1.789,59,1.559,62,2.419,63,1.299,97,2.703,101,2.626,104,3.111,111,2.831,143,3.559,153,2.076,154,2.219,170,1.956,220,2.219,231,5.017,233,2.076,248,2.396,252,3.111,254,3.302,257,2.465,258,3.302,260,2.396,261,3.302,264,2.465,275,3.111,276,3.302,277,3.302,284,1.581,302,2.844,304,2.076,316,3.836,317,2.121,320,2.722,322,2.219,323,3.111,331,2.958,333,3.111,334,3.302,337,3.111,338,3.559,341,3.302,342,3.111,346,3.378,352,3.559,354,2.831,360,4.824,361,3.662,365,5.514,378,3.559,381,3.559,382,3.559,389,1.628,390,5.865,391,3.559,392,3.302,393,3.302,394,2.542,395,1.851,396,3.948,397,2.626,398,3.111,399,2.831,400,3.111]],["t/41",[18,2.47,20,0.956,59,2.821,167,5.976,174,4.753,204,3.838,231,5.122,232,5.629,233,3.757,322,4.016,325,4.336,397,5.843,401,5.353,402,3.608,403,7.144,404,6.44]],["t/43",[15,2.414,18,2.592,26,2.045,27,0.826,38,2.414,40,2.925,59,3.262,119,2.264,233,3.818,236,3.326,237,3.232,283,3.672,284,2.133,294,3.99,307,5.005,317,2.861,334,4.455,340,3.819,397,4.83,405,4.801,406,4.801,407,3.99,408,4.801,409,3.99,410,4.197,411,4.801,412,4.801,413,3.99,414,4.801,415,2.69,416,4.801,417,3.429,418,5.326,419,4.197,420,5.326,421,5.326,422,4.757,423,4.455,424,4.455,425,5.326]],["t/45",[7,3.168,24,2.578,27,1.57,34,5.165,36,4.817,39,1.396,55,2.246,57,2.705,62,1.334,76,2.423,79,4.214,80,3.754,86,2.019,91,5.845,113,1.572,126,3.255,153,2.649,181,3.351,197,2.081,214,3.167,220,1.818,259,3.775,269,1.334,283,2.229,284,1.295,288,4.877,294,2.423,300,3.616,318,2.914,389,1.334,397,3.351,405,5.579,408,5.579,411,4.541,412,4.541,413,3.775,416,4.541,426,3.057,427,1.737,428,2.705,429,3.233,430,4.541,431,4.214,432,3.057,433,2.283,434,6.988,435,1.666,436,1.162,437,2.914,438,5.038,439,3.233,440,3.233,441,3.233,442,2.318,443,2.423,444,6.189,445,3.351,446,2.019,447,2.151,448,2.019,449,1.602]],["t/47",[21,4.422,68,3.359,360,5.023,449,3.609,450,6.095,451,5.224,452,5.396,453,5.023,454,3.248,455,5.741,456,5.224,457,6.568,458,6.568,459,7.286,460,7.286,461,6.095]],["t/49",[110,4.61,131,4.059,300,4.407,316,3.313,321,5.27,346,3.852,389,2.758,452,5.116,462,6.028,463,5.011,464,5.594,465,6.687,466,6.687,467,6.687,468,5.27,469,6.687,470,5.594,471,6.687,472,3.517,473,6.028,474,6.687,475,5.594,476,4.61]],["t/51",[22,2.354,94,2.657,153,4.792,397,5.048,402,3.832,452,4.605,477,7.622,478,5.441,479,5.979,480,7.588,481,7.588]],["t/53",[8,1.521,9,2.366,17,3.379,22,1.629,49,3.023,117,5.878,181,3.492,266,3.862,397,3.492,436,1.886,452,4.363,453,3.619,456,3.764,477,4.391,478,5.878,482,7.189,483,6.48,484,6.48,485,7.189,486,4.732,487,8.198,488,3.764,489,5.249,490,5.249,491,5.249,492,5.249,493,5.249,494,7.189,495,5.249,496,5.249,497,5.249,498,5.249,499,5.249,500,5.249,501,3.023,502,4.391,503,4.136,504,5.249,505,4.732,506,3.101,507,2.82,508,5.249]],["t/55",[153,4.163,288,6.238,389,3.265,422,4.559,509,4.348,510,5.457,511,6.622,512,7.916,513,5.266]],["t/57",[8,2.154,22,2.306,27,1.153,389,3.712,436,2.671,452,4.512,501,4.282,506,4.392,507,3.994,514,4.642,515,8.112,516,6.701,517,7.434]],["t/59",[10,2.385,22,2.333,27,1.567,300,2.679,329,3.612,346,3.232,354,4.023,389,3.102,452,4.564,461,6.291,468,4.422,501,3.232,511,7.096,515,5.058,516,6.779,518,4.204,519,5.393,520,4.422,521,5.611,522,5.611,523,5.611,524,4.204,525,3.082,526,4.204,527,7.521,528,5.611,529,5.611,530,5.611,531,2.281,532,5.611,533,5.611]],["t/61",[8,2.111,22,2.26,27,1.489,153,3.832,300,3.478,514,4.55,534,5.459,535,7.286,536,6.568,537,7.286,538,7.286,539,7.286,540,7.286,541,7.286,542,7.286]],["t/63",[8,1.746,22,1.87,26,2.314,27,1.446,153,4.147,321,6.925,436,2.165,506,3.561,507,3.238,534,7.577,543,7.108,544,7.885,545,3.252,546,6.027,547,2.778,548,3.17,549,6.027,550,2.523,551,6.027,552,6.027,553,6.027,554,6.027,555,5.433]],["t/65",[8,2.05,22,2.195,27,1.098,153,5.2,436,2.542,506,4.18,507,3.801,514,6.343,543,7.87,555,6.377,556,7.87]],["t/67",[8,2.176,97,3.462,188,7.135,361,4.69,452,4.558,464,7.574,557,3.793,558,7.51,559,4.035,560,4.326,561,5.918,562,6.282]],["t/69",[8,2.245,10,3.294,59,3.06,97,3.572,188,6.106,306,3.699,464,6.482,559,5.293,563,5.155]],["t/70",[0,4.653,10,3.604,18,1.753,59,2.665,110,4.653,264,4.215,306,3.222,446,5.295,503,5.318,506,5.009,559,4.98,563,4.49,564,6.079,565,6.749,566,6.084,567,4.345,568,4.49,569,4.096,570,6.749]],["t/72",[7,3.231,111,5.866,260,4.965,452,4.965,514,5.109,571,6.844]],["t/74",[23,4.98,27,1.038,117,6.044,166,4.529,266,3.593,346,3.852,354,4.795,389,3.808,486,6.028,514,5.765,518,5.011,557,3.377,572,5.27,573,6.687,574,5.27,575,6.687,576,5.594,577,3.673,578,6.687]],["t/76",[8,1.955,20,0.903,22,2.876,44,3.794,144,4.49,153,4.459,260,4.096,266,3.626,433,4.202,442,6.079,514,4.215,556,6.084,579,5.645,580,6.084,581,6.391,582,6.749,583,6.749]],["t/78",[8,2.387,20,1.214,188,5.084,197,5.305,198,3.258,260,3.916,320,4.448,442,4.626,452,5.001,514,5.146,579,5.397,580,5.816,584,6.595,585,6.452,586,6.452,587,6.452,588,3.466,589,6.452,590,6.452,591,4.626,592,6.452]],["t/80",[2,2.175,26,3.237,27,1.607,142,5.011,260,4.059,514,4.176,550,3.529,593,3.951,594,6.028,595,6.687,596,6.687,597,8.43,598,8.43,599,8.43,600,6.687]],["t/82",[7,3.291,20,1.115,22,2.037,27,1.293,30,5.058,142,4.921,253,4.8,452,3.986,601,4.709,602,6.567,603,6.567,604,9.155,605,8.334,606,9.155,607,5.204,608,5.365]],["t/84",[20,0.687,27,1.606,38,2.329,66,2.089,68,2.369,70,2.595,157,3.119,326,1.973,389,2.12,399,3.685,452,3.119,514,3.209,519,3.685,534,3.85,547,2.369,594,7.308,609,5.139,610,2.453,611,6.782,612,5.901,613,7.084,614,5.139,615,5.139,616,5.139,617,5.139,618,4.299,619,5.139,620,4.632,621,5.139,622,5.139,623,5.139,624,3.543,625,5.139,626,5.139,627,5.139,628,5.139,629,4.632,630,5.139,631,4.632]],["t/86",[2,2.575,14,3.265,20,1.059,304,4.163,305,3.473,384,5.931,632,6.238,633,6.622,634,7.916]],["t/88",[27,1.269,257,5.109,635,5.267,636,6.844,637,7.375,638,5.64]],["t/90",[27,1.202,241,5.342,257,4.839,584,5.342,635,4.988,636,6.482,637,6.985,638,5.342,639,7.749,640,7.749,641,7.749]],["t/92",[27,0.967,367,5.213,577,3.423,584,6.51,635,6.295,636,8.179,638,5.556,642,6.232,643,6.232,644,9.778,645,6.232,646,8.059,647,4.642,648,6.232,649,6.232,650,6.232]],["t/94",[244,6.171,263,4.302,305,3.435,632,6.171,638,5.399,651,7.831,652,7.831,653,7.831,654,6.171,655,7.831]],["t/96",[16,4.705,200,4.145,656,4.145,657,5.769]],["t/98",[20,0.956,28,3.134,66,2.903,170,3.539,233,3.757,268,4.221,509,3.924,563,4.753,588,3.838,658,7.144,659,9.511,660,5.629,661,6.44,662,6.44,663,5.976,664,7.144,665,7.144]],["t/100",[0,3.568,5,3.568,7,2.044,20,0.692,24,2.288,38,2.346,39,3.074,65,3.711,66,2.893,68,2.386,83,3.141,169,8.283,178,3.332,206,2.386,248,3.141,269,2.936,279,2.613,373,4.329,392,5.955,488,3.711,666,8.137,667,3.878,668,3.711,669,5.175,670,7.119,671,5.104,672,3.339,673,5.175,674,8.137,675,5.175,676,5.175,677,5.175,678,4.665,679,3.141,680,3.443,681,3.332,682,4.329,683,3.878,684,5.175,685,2.981,686,3.141,687,5.175]],["t/102",[1,1.914,4,3.821,5,3.959,18,1.492,20,0.768,27,1.185,28,2.519,30,3.486,38,2.603,39,2.48,43,3.229,144,3.821,168,3.02,204,3.086,240,3.959,263,3.155,269,2.369,286,3.393,326,2.933,354,4.118,433,2.603,461,4.804,601,4.118,672,2.694,688,5.743,689,5.177,690,3.821,691,5.743,692,5.177,693,3.821,694,7.638,695,5.177,696,4.513,697,4.804,698,3.697,699,7.638,700,5.743,701,5.177,702,4.804]],["t/104",[2,2.603,3,5.517,63,2.634,165,6.306,216,5.517,377,5.996,703,6.694,704,5.152]],["t/106",[2,2.409,18,1.425,20,1.254,21,3.329,27,1.149,28,2.406,55,1.763,58,2.05,63,2.956,71,2.106,165,5.836,214,2.486,248,3.329,326,2.106,361,3.425,422,3.159,449,2.717,608,3.531,672,2.573,705,4.588,706,4.945,707,4.322,708,4.588,709,4.945,710,4.588,711,6.608,712,3.159,713,3.329,714,4.945,715,3.782,716,3.084,717,3.933,718,4.588,719,4.11,720,2.826,721,4.11,722,4.588,723,4.945]],["t/108",[4,4.531,8,1.973,27,1.057,52,4.254,71,2.616,162,3.742,377,5.104,422,3.923,484,6.14,531,2.768,672,3.195,703,5.698,716,3.829,717,4.884,718,5.698,719,5.104,720,3.509,721,5.104,724,6.812,725,5.038,726,6.812,727,5.368,728,5.698]],["t/110",[2,2.397,6,3.796,7,3.299,10,3.133,18,1.414,20,1.117,27,1.143,28,2.388,46,2.991,66,2.213,68,2.51,101,3.622,157,3.304,174,3.622,202,2.863,214,3.34,224,3.505,257,3.4,320,3.753,326,2.091,350,3.4,389,2.246,433,2.468,588,3.959,690,3.622,696,3.217,704,5.763,729,4.554,730,2.863,731,3.753,732,3.622,733,4.908,734,3.4,735,5.444,736,5.444,737,4.473,738,5.444]],["t/112",[1,2.059,20,0.827,22,1.917,24,1.986,71,2.373,131,3.75,223,5.169,227,5.169,326,2.373,346,3.559,402,3.121,472,4.215,612,6.059,672,2.898,693,4.111,739,5.57,740,6.179,741,3.978,742,6.179,743,5.169,744,5.57,745,5.57,746,6.179,747,5.169,748,6.704,749,4.63,750,6.179,751,6.179,752,5.169,753,6.179]],["t/115",[9,1.57,20,1.132,49,4.487,55,1.533,71,2.583,78,1.832,97,2.199,189,4.823,253,3.874,300,3.211,427,2.563,562,3.991,577,4.9,612,6.444,631,6.063,693,5.183,705,5.627,754,8.461,755,6.063,756,4.771,757,5.04,758,4.771,759,4.3,760,4.3,761,4.771,762,6.066,763,6.726,764,7.627,765,3.174,766,4.3,767,6.726]],["t/117",[20,0.86,27,1.351,49,2.58,50,2.718,55,2.067,58,2.403,71,1.72,75,2.307,86,2.797,97,2.964,130,2.646,162,3.533,204,2.406,253,2.58,269,2.653,346,2.58,506,4.446,524,4.819,525,2.46,562,5.38,612,6.151,686,3.903,693,5.47,748,6.294,755,5.797,757,5.638,759,4.037,760,4.037,768,4.479,769,4.479,770,5.797,771,7.524,772,5.187,773,6.294,774,4.479,775,4.479,776,2.797,777,4.479,778,4.567,779,4.479,780,4.479]],["t/119",[20,1.016,39,2.461,66,2.316,71,2.188,94,1.995,168,4.496,170,2.823,179,4.086,180,4.767,182,6.13,228,3.929,253,3.282,326,2.918,392,4.767,437,6.85,545,2.35,572,4.49,577,4.696,612,4.892,693,5.055,748,4.767,757,6.833,773,4.767,781,5.699,782,4.27,783,8.22,784,5.699,785,4.49]],["t/121",[27,1.545,68,3.325,71,2.77,182,5.173,253,4.155,346,4.155,612,5.689,693,4.799,783,6.503,786,5.405,787,7.214,788,7.214,789,7.214,790,7.214]],["t/123",[9,2.105,13,3.513,20,1.096,21,3.881,67,3.23,71,2.456,76,4.792,237,3.881,399,4.586,428,5.35,436,3.248,612,6.138,692,5.765,693,4.255,752,5.35,778,3.881,791,5.765,792,5.35,793,6.457,794,3.881,795,5.765,796,3.684,797,4.586,798,6.395]],["t/126",[6,2.336,12,2.336,20,0.607,27,0.703,55,1.457,62,1.87,101,3.016,113,2.204,134,3.412,171,6.826,182,3.251,204,3.486,226,2.752,228,3.126,256,2.832,300,2.165,310,3.397,326,1.741,361,5.164,374,2.612,380,3.016,451,3.251,501,2.612,519,6.274,577,3.564,608,2.919,679,2.752,713,2.752,716,2.549,730,2.385,799,4.087,800,4.087,801,4.534,802,3.573,803,4.534,804,5.432,805,4.087,806,3.016,807,2.919,808,3.251,809,3.573,810,3.251,811,4.087,812,3.251,813,4.534,814,3.397,815,4.087,816,2.832,817,3.573,818,4.087,819,4.534,820,4.534,821,4.534,822,4.534,823,4.534,824,4.534,825,4.534]],["t/129",[2,0.462,7,0.561,9,0.468,17,0.915,18,0.656,20,0.338,21,3.187,22,1.058,27,1.442,30,2.51,38,0.644,49,1.455,50,2.071,63,0.468,65,4.591,66,1.387,68,1.573,76,1.064,94,0.497,97,0.655,101,0.945,105,0.979,106,1.329,109,0.945,110,0.979,121,0.78,134,1.329,144,1.681,154,0.799,168,0.747,171,4.185,180,8.456,194,0.839,202,1.794,204,0.763,206,0.655,214,0.644,253,5.679,260,0.862,268,0.839,278,3.199,300,2.263,310,1.064,319,0.979,326,0.97,361,3.998,380,1.681,433,1.145,436,1.226,456,1.019,501,0.818,519,3.766,520,1.991,531,0.577,548,2.175,559,0.763,560,0.818,568,0.945,569,0.862,581,1.742,607,0.887,612,3.663,660,1.119,679,1.533,685,0.818,686,0.862,698,1.627,716,0.799,720,2.131,737,1.533,764,1.281,772,1.742,794,1.533,804,1.812,806,0.945,808,1.019,810,1.812,812,1.019,817,1.119,826,1.421,827,3.268,828,5.252,829,1.119,830,4.74,831,5.69,832,2.526,833,4.74,834,3.412,835,3.154,836,1.421,837,1.281,838,1.421,839,2.526,840,2.526,841,1.421,842,1.893,843,1.421,844,1.533,845,1.281,846,1.421,847,1.188,848,1.119,849,2.113,850,1.119,851,0.979,852,1.281,853,6.07,854,2.526,855,2.526,856,1.064,857,1.281,858,1.119,859,1.281,860,0.862,861,1.019,862,1.064,863,1.281,864,1.119,865,6.403,866,1.421,867,1.281,868,1.281,869,1.019,870,1.119,871,1.742,872,1.421,873,2.113,874,1.421,875,1.421,876,1.421,877,1.281,878,2.526,879,1.421,880,2.526,881,1.421,882,1.421,883,1.421,884,1.421,885,0.915,886,1.421,887,2.852,888,1.281,889,1.421,890,1.421,891,1.421,892,3.412,893,1.281,894,1.421,895,1.421,896,1.421,897,1.421,898,1.421,899,1.421,900,1.281,901,1.281]],["t/131",[9,0.936,16,0.639,18,0.295,20,0.543,21,4.968,22,1.26,24,2.876,27,1.478,28,0.498,30,2.465,32,0.71,35,0.951,38,1.288,48,0.732,49,2.015,50,1.725,55,0.913,56,5.973,61,0.585,63,1.763,65,0.815,66,0.462,68,1.872,71,0.794,75,0.585,77,2.762,86,0.71,94,1.422,95,2.378,98,0.61,105,0.783,106,1.087,107,1.83,109,1.375,121,0.624,132,1.005,134,0.598,144,0.756,154,2.558,166,1.527,171,3.137,173,0.851,181,0.756,191,0.895,193,0.815,194,1.679,199,0.654,202,1.087,205,0.414,212,0.71,214,1.586,230,1.679,253,1.637,269,0.469,278,2.558,279,0.574,284,0.828,286,0.671,287,1.548,300,1.357,305,0.498,310,0.851,314,0.783,319,1.425,325,0.69,326,2.583,354,0.815,361,0.71,374,0.654,377,0.851,380,1.891,399,0.815,407,2.13,415,1.435,426,3.453,431,2.927,432,0.69,433,0.515,436,1.257,456,0.815,510,0.783,525,1.922,564,0.815,567,0.732,569,0.69,571,0.951,576,0.951,581,0.783,588,0.61,607,0.71,610,0.542,611,1.729,660,3.201,679,1.254,685,1.19,686,2.465,696,0.671,716,0.639,720,1.464,728,0.951,729,0.951,730,0.598,737,1.725,772,1.425,776,0.71,797,0.815,799,1.024,804,4.664,807,0.732,808,0.815,809,0.895,812,0.815,816,0.71,837,1.863,844,0.69,850,4.219,860,0.69,864,2.24,868,1.024,870,0.895,877,1.024,900,1.024,902,0.815,903,1.863,904,1.136,905,1.136,906,2.702,907,4.062,908,6.929,909,2.067,910,2.067,911,2.067,912,3.499,913,4.062,914,4.062,915,2.842,916,2.842,917,0.815,918,0.851,919,0.895,920,1.136,921,1.729,922,2.562,923,1.024,924,3.154,925,0.895,926,1.024,927,0.851,928,1.375,929,0.921,930,1.024,931,1.11,932,1.024,933,1.863,934,0.951,935,1.136,936,1.548,937,1.136,938,1.136,939,1.136,940,1.136,941,3.499,942,1.729,943,0.851,944,0.598,945,1.136,946,1.136,947,1.136,948,5.99,949,2.842,950,2.842,951,1.136,952,1.136,953,1.136,954,1.136,955,1.136,956,0.815,957,1.162,958,2.562,959,0.815,960,0.624,961,0.895,962,1.024,963,2.067,964,2.842,965,2.067,966,1.136,967,0.639,968,0.756,969,1.136,970,1.087,971,1.024,972,1.136,973,1.136,974,1.024,975,2.067,976,1.136,977,0.815,978,2.067,979,1.136,980,0.951,981,0.951,982,1.136,983,1.136,984,1.136,985,1.024,986,1.136,987,1.024,988,0.895]],["t/133",[7,2.799,9,3.018,10,1.412,14,1.37,16,1.867,20,0.688,22,1.03,23,3.719,55,1.067,56,3.854,58,1.241,60,2.598,63,1.093,65,2.381,66,1.35,86,4.427,95,2.778,97,1.531,108,2.778,109,2.209,119,3.014,130,1.962,146,2.778,168,1.747,170,1.645,189,2.381,202,1.747,204,1.784,206,1.531,253,1.913,326,3.521,327,3.422,361,2.074,426,4.654,456,2.381,468,2.617,472,2.705,569,2.016,576,2.778,581,2.29,588,1.784,591,2.381,611,2.778,657,2.29,680,4.188,698,2.138,725,1.962,747,2.778,797,2.381,860,2.016,864,2.617,867,2.994,873,2.778,888,2.994,922,4.637,924,5.675,927,2.488,929,1.481,930,2.994,932,2.994,933,2.994,936,2.488,944,3.311,968,3.422,980,4.303,987,2.994,989,2.617,990,3.321,991,3.321,992,2.994,993,4.717,994,3.321,995,3.321,996,3.321,997,2.994,998,3.321,999,2.778,1000,3.321,1001,2.209,1002,5.144,1003,2.381,1004,2.016,1005,3.321,1006,3.321,1007,3.321,1008,2.381,1009,2.29]],["t/136",[20,1.134,27,1.047,58,2.522,59,3.348,62,2.784,200,4.2,230,3.987,299,2.743,306,3.222,389,2.784,433,3.059,449,3.343,656,3.343,720,3.477,816,4.215,1010,6.749,1011,6.084,1012,3.626,1013,4.282,1014,4.49,1015,5.645]],["t/138",[59,2.69,200,3.374,239,3.14,241,4.696,246,4.134,265,6.72,266,3.66,278,3.829,279,3.44,299,3.466,305,2.988,306,4.071,307,4.696,548,3.582,564,4.884,1016,5.698,1017,4.696,1018,6.812,1019,6.14,1020,5.698,1021,6.812]],["t/140",[18,2.458,27,1.255,59,3.738,306,3.863,1022,4.78]],["t/142",[59,3.343,306,4.041,1023,7.631]],["t/144",[2,2.68,20,0.863,21,3.916,86,4.029,166,4.427,178,5.305,197,4.153,248,3.916,257,4.029,309,3.916,337,5.084,449,3.196,472,3.393,548,3.393,743,6.893,827,4.448,1024,8.24,1025,6.452,1026,6.452,1027,5.681,1028,6.452,1029,6.452,1030,6.452,1031,6.452]],["t/146",[7,2.182,9,2.45,20,0.739,27,1.397,66,2.246,81,3.183,94,1.935,107,3.558,206,3.432,239,2.548,280,4.355,394,4.793,413,4.141,454,2.464,569,4.518,574,4.355,707,4.355,725,3.265,814,4.141,835,4.952,1027,3.81,1032,3.677,1033,5.132,1034,9.007,1035,4.355,1036,5.527,1037,5.527,1038,7.444,1039,5.338,1040,6.71,1041,4.982,1042,3.265,1043,4.982,1044,4.982]],["t/148",[1,1.148,6,1.775,18,1.375,20,0.461,21,3.212,22,1.069,23,3.127,27,1,49,1.985,55,1.108,58,1.288,62,1.421,66,2.151,67,1.74,71,1.323,88,2.293,172,2.036,199,3.048,206,1.589,216,2.376,217,2.293,230,2.036,237,2.092,239,1.589,246,3.91,248,2.092,268,2.036,279,1.74,292,3.106,300,1.645,302,1.401,314,2.376,342,2.716,350,2.152,374,3.048,394,3.407,401,2.582,413,4.828,414,4.771,415,2.673,446,2.152,448,2.152,449,1.707,453,2.376,454,3.67,472,1.812,545,1.421,577,1.893,629,3.106,667,2.582,672,1.616,720,1.775,791,3.106,792,2.883,807,2.219,812,2.471,835,3.521,858,2.716,869,2.471,871,2.376,902,2.471,1004,3.91,1012,1.851,1022,2.036,1027,2.376,1033,2.376,1039,2.471,1045,3.965,1046,2.376,1047,3.446,1048,2.883,1049,3.106,1050,3.446,1051,4.771,1052,3.446,1053,2.716,1054,3.446,1055,4.771,1056,4.17,1057,3.106,1058,1.985,1059,3.106,1060,2.471,1061,3.106,1062,2.883,1063,3.106,1064,3.106,1065,3.106,1066,2.152,1067,3.106,1068,2.716,1069,3.106,1070,2.883,1071,3.106,1072,2.883,1073,2.622,1074,4.427,1075,2.883,1076,2.716,1077,3.106]],["t/151",[9,2.366,20,1.097,55,1.687,112,4.247,131,3.186,153,2.761,182,6.839,214,2.379,224,3.379,251,4.136,269,2.165,272,3.764,312,6.013,326,2.016,342,4.136,433,3.258,470,4.391,472,3.78,511,4.391,545,2.165,557,2.651,567,4.628,671,3.764,672,2.462,739,4.732,741,3.379,766,4.732,792,6.013,794,3.186,829,4.136,859,4.732,1073,3.561,1078,5.249,1079,5.249,1080,7.189,1081,5.249,1082,3.933,1083,4.391,1084,3.023,1085,4.732]],["t/153",[7,2.794,20,0.946,24,2.274,32,4.418,97,3.261,132,3.44,153,3.721,229,6.377,284,2.834,531,2.875,734,4.418,776,4.418,970,3.721,1004,4.294,1086,5.301,1087,4.707,1088,4.707,1089,6.377,1090,5.073,1091,3.154]],["t/155",[1,2.042,2,1.993,7,3.148,20,0.82,26,2.353,27,1.509,32,3.827,55,1.969,200,3.035,269,2.527,301,3.62,302,2.49,316,3.035,350,3.827,395,2.874,402,3.095,415,3.095,436,2.864,507,3.292,545,2.527,656,3.035,696,3.62,717,4.394,793,4.829,1086,5.972,1092,4.225,1093,5.126,1094,6.128,1095,6.128]],["t/157",[11,3.374,16,3.107,20,1.205,22,1.715,60,2.791,63,1.819,78,2.859,97,4.152,172,3.265,226,3.354,269,3.07,284,2.982,299,2.246,365,4.355,395,2.592,406,4.982,415,2.791,432,3.354,449,2.738,531,3.661,550,2.314,656,2.738,730,2.906,814,4.141,1014,3.677,1084,3.183,1092,5.132,1096,4.982,1097,4.355,1098,4.623,1099,4.355,1100,3.81,1101,4.982,1102,3.81,1103,5.527]],["t/159",[8,2.135,14,2.246,20,1.117,58,2.754,59,3.535,60,2.749,97,4.312,230,3.217,233,2.863,269,3.04,284,3.747,299,3.395,306,3.988,365,4.29,449,3.65,454,2.427,929,2.427,1013,2.749,1092,5.759,1102,3.753,1104,8.354,1105,7.369,1106,5.444,1107,5.444,1108,4.079]],["t/161",[20,0.603,27,1.654,30,2.735,55,1.448,78,1.731,97,2.077,191,3.551,198,2.276,284,1.805,395,2.114,545,1.859,547,2.077,548,2.37,550,1.887,567,2.901,610,3.084,672,2.114,929,2.009,957,2.533,1096,4.062,1101,4.062,1109,3.77,1110,4.506,1111,4.506,1112,4.506,1113,6.459,1114,6.459,1115,4.506,1116,4.506,1117,4.506,1118,4.506,1119,3.231,1120,4.506,1121,4.506,1122,4.506,1123,4.506,1124,4.506,1125,4.506,1126,4.506,1127,4.062,1128,6.459,1129,4.062,1130,4.506,1131,4.062,1132,4.506,1133,4.506,1134,4.506,1135,4.506,1136,4.062,1137,4.506,1138,4.506,1139,4.506,1140,2.421,1141,4.506]],["t/163",[1,1.549,8,1.347,9,2.174,11,2.107,24,2.123,27,1.578,73,5.564,130,2.747,153,4.04,253,2.678,278,2.614,302,1.89,326,2.537,442,4.736,501,2.678,827,3.205,848,5.205,850,3.664,902,3.334,985,4.191,1087,5.111,1089,6.925,1091,2.073,1142,2.614,1143,4.65,1144,4.65,1145,3.889,1146,4.65,1147,6.605,1148,5.525,1149,4.65,1150,5.954,1151,4.65,1152,6.605,1153,6.605,1154,6.605,1155,6.605,1156,6.605,1157,4.65,1158,4.191,1159,4.65]],["t/165",[1,1.59,20,0.638,22,1.48,26,1.832,27,1.609,39,2.904,45,3.421,153,2.509,350,4.2,395,3.155,531,1.939,545,3.213,607,2.979,610,3.211,672,2.237,704,3.071,716,2.682,719,3.574,720,2.458,721,3.574,722,3.991,906,3.174,1046,4.637,1088,3.174,1090,3.421,1091,3.772,1160,8.921,1161,3.574,1162,3.991,1163,4.3,1164,4.771,1165,4.771,1166,6.726,1167,6.726,1168,6.726,1169,4.771,1170,4.771,1171,4.771,1172,4.771,1173,4.771]],["t/167",[1,1.899,2,1.853,26,2.188,27,0.884,28,3.333,52,3.559,220,4.272,302,2.316,326,2.188,417,3.669,422,3.282,483,5.137,560,3.282,672,3.564,679,4.612,680,3.791,716,3.204,717,4.086,718,4.767,719,4.27,720,2.936,721,4.27,727,4.49,808,4.086,929,2.541,970,2.997,999,4.767,1033,3.929,1039,4.086,1070,4.767,1091,3.388,1161,4.27,1174,4.767,1175,4.49,1176,5.137,1177,5.137,1178,5.699,1179,4.49,1180,5.699,1181,5.699]],["t/170",[8,2.248,10,2.5,20,0.787,44,3.307,55,1.89,94,2.06,206,2.711,215,6.115,226,3.57,305,3.809,445,3.913,451,4.217,559,4.666,581,4.055,593,5.131,638,5.35,716,3.307,827,4.055,993,4.407,1012,3.16,1073,2.914,1091,2.622,1179,4.635,1182,5.882,1183,5.564,1184,4.635,1185,5.302,1186,3.913,1187,4.055,1188,5.302,1189,4.635]],["t/172",[98,4.776,192,5.741,205,2.652,212,4.55,305,3.196,316,3.609,447,4.847,451,5.224,472,3.832,559,3.914,593,4.305,1012,3.914,1187,5.023,1188,6.568,1190,7.286,1191,6.568]],["t/174",[14,2.197,46,2.925,55,1.712,58,1.99,67,2.69,107,3.429,204,2.861,226,3.232,302,2.165,305,3.623,327,3.543,433,3.29,450,4.455,475,4.455,501,4.181,545,2.994,559,2.861,591,3.819,593,3.147,638,5.005,680,3.543,696,4.289,725,3.147,786,5.44,861,3.819,918,3.99,980,4.455,1058,3.067,1091,3.237,1185,6.544,1189,4.197,1191,4.801,1192,5.326,1193,7.26,1194,4.197,1195,5.326,1196,5.326,1197,7.26,1198,5.326,1199,4.801,1200,3.672,1201,5.326]],["t/177",[3,3.959,4,3.821,5,3.959,8,2.65,9,1.891,16,3.229,18,1.492,27,1.185,63,1.891,160,7.179,178,3.697,198,2.9,205,2.09,206,2.647,216,3.959,326,2.206,395,2.694,399,4.118,683,4.303,690,3.821,704,3.697,737,3.486,1202,5.177,1203,7.638,1204,5.743,1205,4.804,1206,5.743,1207,5.743,1208,5.743,1209,4.804,1210,5.743,1211,5.743,1212,5.743,1213,5.743,1214,5.177,1215,5.743,1216,5.743]],["t/179",[7,3.231,10,3.478,206,3.771,1217,6.844,1218,8.181,1219,4.599]],["t/181",[2,2.691,98,4.445,559,4.445,593,4.888,1220,8.274]],["t/183",[3,2.86,7,1.638,8,1.763,9,1.366,10,1.764,14,2.509,15,1.88,20,0.555,27,0.944,31,3.109,39,3.11,43,2.333,63,1.366,71,2.336,94,2.522,98,2.229,144,2.76,153,2.182,178,2.671,204,2.229,205,2.622,214,1.88,224,3.917,263,2.279,284,1.662,302,1.686,314,2.86,326,2.336,380,4.047,389,1.711,397,2.76,398,3.269,400,3.269,433,1.88,531,2.928,547,1.913,559,4.538,593,4.991,683,3.109,690,4.047,720,2.137,730,2.182,737,2.518,741,2.671,776,2.591,794,2.518,807,2.671,844,2.518,943,3.109,960,2.279,961,3.269,1023,5.484,1042,2.451,1183,2.975,1184,4.794,1200,2.86,1221,2.229,1222,3.74,1223,3.74,1224,3.74,1225,3.471,1226,4.149,1227,4.149,1228,4.149,1229,3.74,1230,4.149,1231,3.74]],["t/185",[2,1.728,7,3.329,8,0.592,10,0.868,19,1.465,20,0.711,21,1.24,22,0.634,26,0.784,27,1.534,38,0.926,39,2.295,55,0.656,58,0.763,61,1.785,62,0.843,63,2.649,67,1.032,70,2.278,83,1.24,94,1.213,119,1.473,121,1.903,128,1.709,153,3.624,194,1.207,198,1.032,205,2.508,212,1.276,216,1.408,224,1.315,256,1.276,269,1.429,284,1.388,302,3.271,314,5.39,321,1.61,325,1.24,326,3.091,395,1.625,426,2.103,445,1.359,525,1.122,531,3.353,545,2.454,559,4.529,564,1.465,593,4.98,601,3.235,657,1.408,685,2.599,690,4.823,696,1.207,704,1.315,734,1.276,737,2.103,743,1.709,749,1.531,794,2.103,796,1.177,804,2.484,809,1.61,816,1.276,926,1.841,956,1.465,959,1.465,960,1.122,1008,2.484,1009,2.389,1033,1.408,1039,1.465,1066,1.276,1102,1.408,1161,1.531,1232,4.512,1233,2.596,1234,2.043,1235,4.512,1236,4.512,1237,3.555,1238,3.465,1239,1.207,1240,2.043,1241,4.067,1242,2.898,1243,2.043,1244,2.043,1245,2.043,1246,2.043,1247,2.043,1248,1.61,1249,2.043,1250,0.911,1251,1.841,1252,1.359,1253,2.043]],["t/187",[1,1.122,7,3.699,8,1.506,9,1.108,10,1.431,20,0.849,22,1.045,26,2.439,27,0.522,45,2.414,55,1.671,58,2.669,61,1.735,63,2.966,70,1.7,71,1.293,86,2.103,98,2.793,102,2.414,157,3.155,205,3.41,237,2.043,239,1.552,263,1.85,268,1.989,269,2.945,284,1.349,314,4.378,325,2.043,326,2.742,410,2.653,436,2.565,501,1.939,547,2.927,560,1.939,584,2.321,690,2.24,702,2.816,720,2.678,731,4.923,732,4.224,737,2.043,776,2.103,856,2.523,928,2.24,929,2.831,944,3.755,1033,2.321,1066,2.103,1183,2.414,1184,2.653,1200,2.321,1231,3.035,1237,2.653,1252,2.24,1254,3.367,1255,2.414,1256,3.367,1257,3.367,1258,3.035,1259,3.895,1260,2.168,1261,5.199,1262,3.367,1263,3.367,1264,3.367,1265,2.816,1266,3.035,1267,3.367]],["t/189",[2,2.547,241,5.399,252,6.171,257,4.891,514,4.891,861,5.615,1268,7.059,1269,7.831,1270,7.831,1271,7.831]],["t/191",[6,4.215,17,5.267,18,2.125,109,5.443,1272,7.375,1273,8.181]],["t/193",[2,3.287,6,4.245,18,2.358,27,1.278,43,3.627,279,3.258,389,2.661,588,3.466,680,4.292,698,4.153,842,4.834,968,4.292,1053,5.084,1066,4.029,1073,3.196,1100,4.448,1274,4.834,1275,6.452,1276,5.816,1277,5.816,1278,6.452]],["t/195",[2,1.898,11,2.645,15,2.645,18,2.606,20,1.033,70,2.947,206,2.69,326,3.677,395,2.737,404,5.26,432,5.25,435,3.006,588,3.135,678,5.26,681,3.756,704,3.756,804,4.184,1009,4.023,1022,4.561,1066,3.644,1098,4.881,1221,3.135,1239,3.447,1279,5.835,1280,6.958,1281,4.598,1282,4.023]],["t/197",[1,1.511,2,1.475,6,2.336,9,1.493,15,2.055,18,2.148,27,1.412,40,2.491,58,1.695,63,1.493,67,3.826,88,3.016,130,2.679,134,4.349,172,2.679,192,3.573,206,3.492,226,2.752,250,5.848,266,2.436,326,2.909,447,3.016,520,3.573,548,2.385,568,3.016,601,3.251,708,3.793,712,2.612,807,2.919,835,5.501,870,3.573,1073,3.214,1088,3.016,1091,2.021,1108,4.861,1255,3.251,1283,6.488,1284,3.793,1285,3.251,1286,3.793,1287,4.534,1288,4.534,1289,4.534,1290,4.534,1291,4.534,1292,4.534,1293,4.087,1294,3.793,1295,4.534,1296,4.177,1297,4.534,1298,4.534,1299,4.534]],["t/199",[1,2.166,6,1.801,9,1.761,11,2.946,18,2.366,20,0.87,22,1.66,26,2.055,27,0.83,32,2.183,62,2.681,63,1.761,78,2.055,119,2.275,168,2.814,239,1.611,322,1.965,326,3.729,332,2.25,361,3.341,389,1.442,449,1.732,450,2.924,470,2.924,472,4.128,505,3.151,568,2.325,588,1.878,668,2.506,690,4.324,696,4.892,698,2.25,711,4.216,712,2.013,716,1.965,741,2.25,776,3.341,804,2.506,806,3.559,861,3.836,862,2.619,956,2.506,1239,3.161,1272,3.151,1274,2.619,1285,2.506,1300,5.437,1301,5.35,1302,2.924,1303,5.35,1304,3.496,1305,3.496,1306,3.496,1307,3.161,1308,3.496,1309,3.496,1310,5.35,1311,5.35,1312,3.496,1313,3.496,1314,3.151,1315,3.496,1316,3.151,1317,3.151,1318,3.496,1319,3.496]],["t/201",[18,1.971,20,1.015,166,4.077,326,2.914,632,5.979,657,5.231,727,5.979,730,3.99,844,4.605,1022,4.483,1091,3.383,1175,5.979,1320,7.588]],["t/203",[9,2.634,18,2.079,20,1.07,257,4.997,326,3.073,1214,7.214,1321,8.003,1322,8.003]],["t/205",[2,1.165,8,1.912,18,2.257,20,0.479,26,2.093,27,1.67,30,2.173,32,2.236,43,3.709,55,1.751,58,1.338,60,2.752,171,4.549,202,1.883,206,1.651,300,1.71,302,1.455,322,2.013,326,2.093,327,2.382,343,3.228,362,2.469,407,2.683,422,3.8,426,2.173,472,1.883,519,2.568,550,1.499,568,2.382,577,1.967,593,3.898,624,3.757,672,1.679,696,2.116,731,2.469,861,2.568,917,2.568,942,2.995,956,2.568,962,3.228,1091,1.596,1092,2.469,1102,2.469,1202,3.228,1239,2.116,1274,4.083,1323,2.822,1324,3.581,1325,3.581,1326,3.581,1327,3.581,1328,2.995,1329,3.228,1330,3.581,1331,3.228,1332,3.581,1333,3.581,1334,3.581,1335,3.581,1336,3.581,1337,3.581,1338,3.581,1339,3.228]],["t/207",[2,1.839,4,3.762,5,3.898,6,2.913,11,2.563,18,2.212,20,0.756,27,1.569,28,2.48,43,3.179,55,1.817,171,5.212,239,3.485,454,2.521,550,2.368,577,3.106,624,3.898,698,3.64,786,4.237,861,4.055,906,3.762,1076,4.456,1274,4.237,1340,4.73,1341,5.655,1342,4.73,1343,5.097,1344,4.73,1345,8.195,1346,5.097,1347,5.655,1348,3.106]],["t/209",[2,2.39,9,1.414,15,1.947,18,1.621,20,1.233,21,2.607,27,1.251,28,1.884,32,2.682,40,3.427,44,2.415,55,1.38,63,2.942,71,1.649,75,2.213,165,5.79,168,2.259,214,1.947,248,2.607,302,1.746,308,2.765,326,1.649,361,3.896,373,3.593,397,2.857,422,2.474,449,2.128,472,2.259,577,2.359,681,2.765,705,3.593,706,3.872,707,3.384,708,3.593,709,3.872,710,3.593,711,5.79,712,2.474,713,2.607,714,3.872,715,2.961,717,3.08,719,3.218,721,3.218,722,3.593,723,3.872,730,2.259,1011,3.872,1274,3.218,1282,2.961,1349,4.295,1350,4.46,1351,3.593,1352,4.295,1353,4.295,1354,4.295,1355,4.295,1356,4.295,1357,4.295,1358,4.295,1359,4.295,1360,4.295]],["t/211",[2,1.96,6,4.062,11,2.732,18,2.513,58,2.252,61,3.105,68,2.778,98,3.238,101,4.009,102,4.321,157,3.658,194,3.561,228,4.155,302,2.45,309,5.658,316,2.985,422,3.471,557,3.044,567,3.88,569,4.786,632,4.749,730,3.17,869,4.321,918,4.516,988,4.749,1012,3.238,1361,4.516,1362,5.041,1363,6.027]],["t/214",[11,2.709,18,1.553,20,0.8,27,0.927,55,1.921,172,3.532,206,2.755,286,3.532,326,3.012,332,5.635,624,4.121,682,6.56,689,7.07,715,6.035,956,5.624,1239,5.851,1302,5,1350,3.628,1364,5.388,1365,5.978,1366,7.843,1367,5.978,1368,5.978,1369,7.843]],["t/216",[18,2.165,20,0.878,27,1.42,82,6.567,332,4.228,433,2.977,607,4.101,610,3.135,715,4.528,737,3.986,802,5.175,1140,4.918,1370,6.971,1371,6.971,1372,8.334,1373,6.567,1374,6.567,1375,6.567,1376,6.567]],["t/218",[27,1.45,40,4.348,531,3.217,1140,4.253,1377,7.916,1378,7.916,1379,7.916,1380,7.916]],["t/220",[27,1.566,610,3.585,1091,3.348,1381,7.51,1382,7.51,1383,5.178,1384,7.51,1385,7.51,1386,7.51,1387,7.51,1388,7.51]],["t/222",[18,2.199,704,5.45,715,5.836]],["t/224",[1,1.643,2,1.604,11,3.12,18,2.06,20,0.66,26,2.644,27,1.332,28,2.163,32,4.299,34,3.175,50,2.993,55,1.585,68,2.273,75,3.547,78,1.894,119,2.096,198,2.49,206,3.173,301,2.914,322,2.772,350,3.079,395,2.313,415,2.49,417,3.175,433,2.235,506,2.914,550,2.882,567,4.432,568,3.281,610,2.354,672,2.313,715,3.4,851,3.4,944,2.593,1252,3.281,1300,4.125,1302,4.125,1307,4.686,1383,3.4,1389,3.536,1390,4.125,1391,4.931,1392,4.931,1393,4.445,1394,4.931,1395,4.931,1396,4.931,1397,4.931,1398,4.125,1399,4.931,1400,4.931]],["t/226",[1,2.696,27,1.255,715,5.578,970,4.977,1307,4.78,1331,7.293]],["t/228",[1,1.474,8,1.282,11,2.005,18,1.149,20,0.853,27,1.671,55,1.422,62,1.825,112,2.614,205,2.32,326,1.699,395,3.505,436,1.59,545,3.082,557,2.235,610,3.043,1307,2.614,1361,4.777,1401,3.989,1402,4.425,1403,4.425,1404,4.425,1405,9.028,1406,4.425,1407,6.375,1408,4.425,1409,4.425,1410,4.425,1411,4.425,1412,4.425,1413,4.425,1414,4.425,1415,3.989,1416,5.747,1417,4.425,1418,4.425,1419,4.425,1420,4.425,1421,4.425,1422,4.425,1423,4.425]],["t/230",[27,1.468,263,4.444,395,3.795,681,5.209,1205,6.768,1424,7.293]],["t/232",[14,2.388,15,2.624,18,1.504,20,1.027,22,1.796,27,1.424,52,3.615,55,1.86,58,2.163,326,2.223,432,3.513,513,3.851,550,3.842,560,4.423,581,3.991,704,5.908,1260,3.727,1281,4.561,1307,5.422,1344,4.842,1425,5.789,1426,5.218,1427,5.218,1428,5.789,1429,5.218,1430,5.789,1431,4.842,1432,5.789,1433,5.789]],["t/234",[2,2.239,6,4.086,18,1.281,20,0.921,26,1.894,68,2.273,73,3.281,88,5.277,97,2.273,190,4.125,195,4.445,202,2.593,214,3.12,220,2.772,263,2.709,284,1.975,326,3.468,331,3.695,395,3.72,476,3.4,569,2.993,693,3.281,698,5.106,704,5.106,712,2.84,716,2.772,729,4.125,731,3.4,732,3.281,733,4.445,851,3.4,921,4.125,942,4.125,1205,4.125,1239,2.914,1339,4.445,1401,4.445,1424,4.445,1434,4.931,1435,4.931,1436,4.931,1437,4.931,1438,4.931,1439,4.445,1440,4.931,1441,4.931,1442,4.445,1443,4.931]],["t/236",[18,2.613,199,5.793,200,3.682,279,3.754,323,5.858,389,3.066,765,4.945,1098,6.218,1444,7.434]],["t/238",[2,2.575,20,1.059,128,6.622,198,3.998,1109,6.622,1445,7.916,1446,7.136,1447,7.916,1448,6.238]],["t/241",[14,3.252,71,2.314,94,2.11,119,2.562,121,3.311,132,2.93,142,4.516,200,3.906,269,3.252,284,3.159,415,3.982,557,3.982,668,4.321,931,5.008,960,3.311,1008,4.321,1142,5.241,1148,5.041,1248,4.749,1250,3.918,1449,6.027,1450,6.027,1451,4.147]],["t/243",[20,0.944,22,1.021,27,1.626,28,1.444,71,2.404,97,1.517,134,1.731,154,2.872,168,2.686,179,2.36,329,2.119,401,2.466,436,1.182,531,3.104,550,2.621,561,2.594,610,2.988,711,6.018,850,2.594,931,4.808,936,2.466,957,1.85,1035,2.594,1140,1.768,1448,5.559,1452,4.604,1453,4.604,1454,5.108,1455,3.291,1456,3.291,1457,3.291,1458,3.291,1459,3.291,1460,6.884,1461,3.291,1462,4.604,1463,3.291,1464,3.291,1465,3.291,1466,3.291,1467,3.291,1468,3.291,1469,2.967,1470,2.967,1471,2.967,1472,2.967,1473,2.967,1474,2.967,1475,3.291,1476,3.291,1477,2.594,1478,3.291,1479,3.291,1480,5.108,1481,3.291,1482,4.273,1483,2.967,1484,3.291,1485,5.108,1486,3.291,1487,3.291,1488,3.291,1489,5.559,1490,3.291,1491,2.466,1492,3.291,1493,2.594,1494,3.291]],["t/245",[12,2.805,14,2.246,27,0.845,28,2.388,58,2.035,59,3.299,62,2.246,66,2.213,162,2.991,239,2.51,241,5.081,244,6.583,246,3.304,265,4.29,267,4.29,268,3.217,272,3.904,299,2.213,301,3.217,306,4.274,307,3.753,384,4.079,454,2.427,559,2.925,656,2.697,1013,2.749,1016,4.554,1017,3.753,1019,4.908,1495,4.29,1496,4.908,1497,4.908,1498,4.079,1499,7.369,1500,3.753,1501,4.554,1502,4.554,1503,5.444,1504,7.369,1505,4.554,1506,5.444]],["t/248",[1,2.231,2,1.542,6,2.442,20,0.634,26,1.82,27,1.551,28,2.079,44,3.764,58,1.771,78,2.571,113,2.304,157,4.064,205,2.826,269,3.202,293,3.051,531,1.926,545,2.762,547,3.086,550,1.984,610,3.196,737,2.877,793,3.735,957,2.665,970,3.521,1012,2.546,1014,3.153,1140,2.546,1224,4.273,1250,3.967,1507,7.764,1508,5.017,1509,3.965,1510,8.436,1511,6.696,1512,4.74,1513,3.268,1514,4.74,1515,4.74,1516,4.74]],["t/250",[7,2.073,20,0.702,26,2.016,27,1.543,28,2.303,58,1.962,60,2.651,66,2.133,269,3.381,305,2.303,322,2.951,436,1.886,525,2.884,730,2.761,734,3.278,957,2.951,1014,3.492,1066,3.278,1083,4.391,1250,4.354,1509,7.978,1517,5.278,1518,4.732,1519,7.189,1520,5.249,1521,4.732,1522,5.249,1523,6.013,1524,6.013,1525,5.249,1526,5.249]],["t/252",[23,2.646,27,1.532,71,2.47,113,2.177,119,1.904,205,1.63,230,2.646,284,3.014,389,1.847,394,2.883,435,3.313,436,1.609,448,2.797,478,3.211,531,3.342,550,1.875,610,3.07,778,2.718,902,3.211,931,2.406,936,3.356,957,2.518,960,2.46,1140,2.406,1142,3.615,1448,6.48,1452,4.037,1453,4.037,1460,7.413,1469,4.037,1470,4.037,1471,4.037,1472,4.037,1473,4.037,1474,4.037,1477,3.529,1482,3.747,1483,4.037,1527,3.903,1528,4.037,1529,3.747,1530,4.037,1531,3.747,1532,4.037,1533,4.479,1534,4.479,1535,3.747,1536,4.479,1537,4.479,1538,4.479,1539,4.479,1540,4.479,1541,4.14,1542,4.479]],["t/254",[11,3.335,20,1.196,24,2.365,48,4.738,427,3.954,961,5.799,970,3.87,1142,5.028,1482,6.156,1489,5.799,1543,6.702,1544,7.359,1545,7.359]],["t/256",[13,3.673,15,3.031,20,0.895,62,2.758,78,2.568,121,4.631,172,3.951,194,3.951,205,2.434,284,2.679,346,3.852,350,4.176,410,5.27,531,3.426,548,3.517,560,3.852,863,6.028,970,3.517,1351,5.594,1541,4.305,1546,6.028,1547,6.028,1548,6.687,1549,6.687]],["t/258",[20,0.903,23,3.987,27,1.554,63,2.222,71,2.592,78,2.592,117,6.079,134,3.549,402,3.408,436,2.425,531,2.743,547,3.111,647,3.887,858,5.318,1535,5.645,1541,4.345,1550,5.318,1551,6.749,1552,6.749,1553,6.749]],["t/260",[9,2.624,10,2.605,15,2.777,22,1.901,46,3.366,63,2.624,71,2.353,78,3.061,172,3.62,194,3.62,205,2.23,212,4.978,218,4.829,362,4.225,432,4.838,547,3.674,647,4.591,778,4.838,931,3.292,1084,3.529,1527,3.719,1530,5.524,1535,5.126,1541,5.132,1554,6.128,1555,5.126,1556,6.128]],["t/262",[13,3.544,20,1.102,71,3.487,78,2.478,111,5.908,119,2.743,121,4.987,178,4.153,278,3.627,329,4.153,380,4.292,531,2.622,679,3.916,686,3.916,1142,3.627,1489,7.154,1529,7.594,1541,4.153,1546,5.816,1557,6.452]],["t/264",[22,2.404,40,4.256,71,2.976,567,4.988,931,4.163,1477,6.106,1541,4.988,1558,6.106,1559,8.317,1560,6.985]],["t/266",[1,1.914,16,3.229,39,3.95,43,3.229,45,4.118,58,2.146,66,2.334,68,2.647,71,2.933,75,3.935,80,3.086,94,2.011,121,3.155,162,4.196,220,3.229,233,3.02,239,2.647,287,4.303,294,4.303,301,3.393,316,4.531,446,3.587,507,3.086,531,2.334,588,3.086,782,4.303,927,4.303,1042,3.393,1073,2.845,1093,4.804,1527,5.209,1561,5.743,1562,5.177]],["t/268",[7,2.088,8,1.532,13,2.904,28,2.319,39,2.283,71,2.03,75,3.722,94,1.851,113,2.57,119,2.248,121,2.904,198,2.67,205,1.924,215,4.166,293,3.404,316,4.587,331,3.962,389,2.98,454,2.357,607,3.302,608,3.404,697,6.043,713,4.384,732,3.517,741,3.404,793,4.166,860,4.384,871,4.98,887,3.645,929,2.357,1073,4.587,1086,3.962,1462,6.512,1517,3.404,1563,4.766,1564,4.766,1565,5.287,1566,5.287,1567,4.766,1568,4.766]],["t/270",[1,1.712,2,1.671,8,1.489,9,2.669,20,0.687,27,1.47,39,3.059,63,1.692,71,1.973,75,4.177,94,1.799,113,2.498,200,2.546,233,2.702,256,3.209,280,5.582,293,3.308,302,2.089,446,3.209,518,3.85,519,5.08,548,2.702,671,3.685,749,5.308,1099,4.049,1562,7.308,1569,5.139,1570,5.139,1571,7.084,1572,5.139,1573,5.139,1574,5.139,1575,4.632,1576,7.084,1577,5.139,1578,5.139,1579,5.139,1580,5.139,1581,3.85,1582,3.036,1583,5.139,1584,5.139]],["t/272",[8,2.553,10,3.254,15,2.094,16,2.597,20,0.88,36,3.185,39,2.839,61,2.38,71,1.774,75,4.719,94,2.302,145,5.181,197,2.974,200,2.289,279,2.333,294,3.462,301,2.73,302,1.878,316,3.792,427,4.113,446,4.106,472,2.43,507,2.482,563,3.074,671,3.313,672,2.167,720,2.38,749,3.462,752,3.865,778,4.646,782,3.462,869,3.313,871,4.533,1012,2.482,1282,3.185,1491,3.462,1585,4.62,1586,4.62,1587,4.62,1588,5.927,1589,4.165,1590,6.575,1591,4.62,1592,4.62,1593,4.62,1594,4.62,1595,4.62,1596,4.62,1597,4.62,1598,4.165]],["t/275",[2,3.075,12,4.71,20,1.165,49,2.939,50,4.279,58,1.907,66,2.865,105,4.86,205,2.566,208,4.021,216,3.518,300,3.366,305,2.239,308,3.285,436,2.533,448,3.187,449,2.528,454,3.143,472,2.684,524,3.824,525,3.873,713,4.279,720,2.629,851,3.518,918,3.824,1051,4.6,1053,4.021,1102,3.518,1175,4.021,1222,4.6,1314,6.355,1348,2.803,1599,4.6,1600,3.518,1601,4.6,1602,5.103,1603,3.824,1604,5.103]],["t/277",[6,2.2,14,1.761,20,0.831,23,2.523,27,0.964,43,2.401,46,2.346,58,2.322,68,2.864,78,1.64,81,2.459,99,4.655,102,3.062,116,4.655,119,4.153,121,3.413,154,4.521,183,2.944,194,2.523,206,2.864,259,4.655,435,3.201,449,3.078,559,2.294,588,2.294,601,3.062,816,3.88,959,4.455,960,2.346,993,5.487,1119,3.062,1209,3.572,1221,2.294,1250,1.904,1252,2.841,1296,2.749,1350,2.592,1493,3.365,1582,4.752,1605,4.27,1606,2.841,1607,3.365,1608,4.27,1609,4.27,1610,4.27,1611,4.27,1612,4.27,1613,4.455,1614,3.849,1615,3.572,1616,4.27,1617,4.27,1618,4.27,1619,2.944,1620,4.283,1621,3.849]],["t/279",[14,2.02,20,0.655,24,1.574,26,1.881,55,1.574,58,2.954,67,2.474,68,3.159,81,2.821,97,2.258,119,2.082,205,3.559,214,3.106,305,4.204,332,3.153,380,3.259,394,3.153,400,3.86,402,2.474,432,2.973,454,2.184,545,2.02,560,2.821,633,4.097,745,4.415,796,2.821,809,3.86,844,2.973,923,4.415,929,2.184,968,3.259,1004,2.973,1239,4.048,1250,2.184,1252,3.259,1259,3.67,1260,3.153,1285,3.512,1317,4.415,1619,4.724,1622,4.415,1623,5.4,1624,4.898,1625,4.898,1626,4.415,1627,4.415]],["t/281",[1,1.762,10,2.248,14,2.181,24,2.644,27,1.6,58,1.976,78,2.03,81,3.045,94,1.851,202,2.781,205,2.995,284,2.894,545,3.648,547,3.33,672,3.388,732,3.517,1004,4.384,1162,4.423,1239,3.124,1250,3.221,1582,4.268,1607,5.692,1628,4.766,1629,4.766,1630,8.229,1631,5.287,1632,5.287,1633,4.423,1634,5.287]],["t/283",[8,1.806,27,1.554,63,2.051,66,3.275,78,3.095,132,3.03,166,4.329,199,3.589,205,2.268,268,3.682,278,3.504,302,2.533,479,4.911,547,2.873,1221,3.348,1239,5.277,1252,4.146,1628,5.618,1635,6.232,1636,5.213,1637,6.038,1638,6.232,1639,6.232]],["t/285",[27,1.633,78,2.393,545,3.324,547,2.873,957,3.504,970,3.277,1140,3.348,1221,3.348,1239,3.682,1250,2.778,1513,4.296,1633,5.213,1636,5.213,1637,4.669,1640,6.232,1641,8.931,1642,4.469,1643,6.232,1644,5.618,1645,6.232,1646,6.232,1647,6.232]],["t/287",[27,1.625,58,2.065,78,2.122,302,2.246,545,3.472,547,2.548,720,2.847,929,2.464,957,3.107,960,3.036,970,2.906,1032,3.677,1140,2.969,1162,4.623,1221,2.969,1233,4.141,1250,2.464,1513,3.81,1629,4.982,1633,4.623,1636,4.623,1642,3.963,1644,4.982,1648,5.527,1649,5.527,1650,5.527,1651,5.527,1652,8.418,1653,5.527,1654,5.527,1655,5.527,1656,5.527,1657,5.527,1658,5.527]],["t/289",[1,1.235,2,2.192,20,1.004,27,0.868,39,1.601,63,1.221,71,2.149,98,1.992,111,4.012,117,2.659,121,3.702,126,1.95,132,2.72,134,2.943,172,2.191,173,2.778,205,1.35,214,1.681,220,2.085,228,2.556,284,2.242,346,3.882,360,2.556,371,4.409,415,1.873,531,3.051,548,3.544,608,2.387,680,3.723,785,2.922,849,3.102,929,1.653,931,1.992,1013,2.826,1058,2.136,1088,6.163,1091,4.209,1119,2.659,1161,2.778,1174,3.102,1248,6.349,1517,3.602,1547,3.342,1581,2.778,1659,6.075,1660,3.342,1661,3.708,1662,3.708,1663,5.596,1664,3.708,1665,3.708,1666,3.708,1667,3.708,1668,3.708,1669,3.708,1670,3.708,1671,5.596,1672,3.708,1673,2.778,1674,2.922,1675,3.708,1676,5.596,1677,3.708,1678,2.778]],["t/291",[27,1.572,55,2.092,214,2.95,433,2.95,436,3.275,686,3.95,725,3.846,856,4.877,1046,4.487,1058,3.749,1090,5.942,1091,4.279,1189,6.53,1679,5.445,1680,6.509,1681,6.509,1682,6.509]],["t/293",[9,1.279,20,0.52,22,1.205,27,1.567,28,1.704,55,1.249,63,1.908,68,1.791,78,2.225,112,2.295,119,1.652,122,4.566,126,3.048,134,3.048,191,3.061,206,1.791,237,2.358,302,1.579,433,1.761,435,2.001,436,1.396,448,2.426,531,1.579,547,2.671,550,1.627,610,1.855,816,2.426,929,2.583,931,2.087,1015,3.25,1042,2.295,1046,3.995,1058,4.426,1085,3.502,1088,6.242,1090,2.786,1091,2.583,1119,4.155,1221,2.087,1513,2.678,1517,2.501,1581,2.911,1642,4.155,1683,6.876,1684,3.502,1685,3.885,1686,3.885,1687,3.885,1688,3.885,1689,3.885,1690,7.684,1691,3.885,1692,3.502,1693,3.885,1694,3.885,1695,3.885,1696,3.885,1697,3.885,1698,3.885,1699,3.885,1700,3.885,1701,3.885]],["t/295",[20,0.737,22,1.127,27,1.598,28,2.418,55,1.168,62,1.499,63,2.446,78,2.117,94,1.273,118,2.723,126,4.731,134,1.911,173,2.723,196,2.506,205,1.323,435,1.872,545,1.499,547,3.069,550,1.522,557,1.835,786,2.723,862,2.723,929,2.457,934,3.04,970,2.898,988,2.864,1042,2.147,1046,2.506,1058,4.6,1174,3.04,1221,1.953,1293,3.276,1361,2.723,1513,3.8,1637,2.723,1642,2.606,1683,6.681,1692,3.276,1702,6.882,1703,3.634,1704,3.276,1705,3.634,1706,3.634,1707,3.634,1708,3.634,1709,2.723,1710,2.723,1711,5.512,1712,3.634,1713,6.658,1714,3.634,1715,3.634,1716,5.512,1717,3.634,1718,3.634,1719,3.634,1720,3.634,1721,3.634,1722,3.634,1723,3.634,1724,3.634,1725,3.634,1726,3.634]],["t/297",[11,3.981,28,3.134,126,5.002,333,5.629,816,4.461,1088,4.753,1091,4.24,1200,6.055,1702,5.629,1727,7.144,1728,5.353,1729,7.144,1730,7.144]],["t/299",[134,4.118,436,2.813,507,4.207,860,4.753,1603,5.868,1683,6.551,1702,7.316,1731,7.059,1732,7.831]],["t/301",[1,1.193,9,1.179,14,3.042,20,0.729,26,1.375,27,1.496,46,1.967,55,1.151,62,2.248,67,1.808,71,1.375,99,4.083,134,1.883,183,3.757,196,2.469,278,2.013,293,2.305,436,2.37,447,4.389,510,3.757,513,2.382,518,4.083,531,3.866,647,2.063,686,5.854,730,1.883,732,2.382,968,2.382,1001,2.382,1073,3.268,1088,2.382,1091,1.596,1108,6.71,1136,4.913,1489,5.199,1493,4.295,1733,2.683,1734,3.581,1735,3.228,1736,5.45,1737,5.45,1738,3.581,1739,3.581,1740,2.995,1741,6.598,1742,5.947,1743,6.598,1744,3.228,1745,3.581,1746,5.45,1747,4.559,1748,5.45]],["t/303",[0,4.877,2,2.301,8,2.53,17,4.555,20,0.946,66,2.875,80,3.801,98,3.801,105,4.877,108,5.918,205,2.575,263,3.886,320,4.877,322,3.977,1100,4.877,1183,5.073,1225,5.918,1749,6.377,1750,5.918]],["t/305",[9,2.214,14,1.968,20,1.193,22,1.48,27,1.384,55,1.533,67,2.409,85,3.759,98,2.563,113,2.319,205,3.643,263,2.621,293,3.071,394,5.016,428,3.991,435,2.458,526,3.574,545,3.213,547,3.9,730,2.509,796,2.748,928,4.475,944,3.537,988,3.759,1009,3.289,1183,3.421,1186,3.174,1250,2.127,1501,3.991,1750,6.517,1751,6.063,1752,4.3,1753,4.771,1754,6.063,1755,4.3,1756,4.771,1757,4.3,1758,4.771,1759,4.3]],["t/307",[1,1.301,8,2.664,9,1.286,10,1.66,20,0.522,22,1.212,27,1.525,48,2.514,52,2.439,55,1.255,66,1.587,105,2.693,167,3.267,205,3.256,269,1.611,284,1.565,305,1.713,319,2.693,362,2.693,394,2.514,435,2.012,449,1.935,545,2.4,547,4.474,730,2.054,816,3.633,847,3.267,887,2.693,1183,6.415,1221,2.098,1250,1.741,1362,4.867,1431,3.267,1606,2.598,1750,4.867,1752,5.244,1754,5.244,1757,3.521,1759,3.521,1760,3.906,1761,3.906,1762,3.267,1763,3.906,1764,3.906,1765,3.906,1766,4.867,1767,3.906,1768,3.906,1769,3.906,1770,3.906,1771,3.906,1772,3.906,1773,3.906,1774,3.906,1775,3.906,1776,3.521,1777,3.521]],["t/309",[2,2.531,4,3.508,20,0.965,27,1.435,52,2.142,58,1.282,94,1.201,112,2.027,130,2.027,132,3.123,192,4.155,193,2.459,205,3.215,214,1.555,305,3.975,374,1.976,419,2.703,423,4.411,435,2.717,436,1.232,475,2.869,476,2.365,525,1.884,531,2.931,545,3.389,633,4.411,638,5.665,667,2.57,672,1.609,683,2.57,713,2.082,730,2.773,732,2.282,776,2.142,827,2.365,918,2.57,956,2.459,968,3.508,974,3.092,1008,2.459,1020,2.869,1066,4.012,1084,1.976,1179,2.703,1239,3.796,1265,2.869,1371,2.869,1744,3.092,1766,6.032,1776,4.754,1777,4.754,1778,3.092,1779,3.43,1780,3.43,1781,5.273,1782,5.273,1783,5.273,1784,3.092,1785,3.43,1786,3.43,1787,3.43,1788,3.43,1789,3.43,1790,3.43,1791,3.43]],["t/311",[1,1.199,13,3.005,20,0.885,27,1.516,55,1.156,61,1.854,63,1.185,78,1.382,80,1.933,94,1.26,119,1.53,130,2.126,154,4.156,166,1.933,170,1.783,200,3.278,269,2.256,283,3.771,432,2.184,435,3.809,448,2.247,506,2.126,526,2.696,531,1.463,547,2.522,550,1.507,610,2.612,647,4.258,720,1.854,778,2.184,794,2.184,929,2.439,931,1.933,957,2.023,970,1.892,1068,2.836,1140,1.933,1186,2.394,1221,1.933,1280,4.931,1342,3.01,1451,3.48,1513,2.481,1527,4.826,1558,2.836,1560,3.244,1582,5.299,1606,2.394,1626,3.244,1627,3.244,1637,4.099,1642,2.58,1733,4.099,1792,3.244,1793,3.01,1794,3.244,1795,3.599,1796,5.47,1797,5.47,1798,5.47,1799,3.599,1800,3.244,1801,3.244,1802,3.599,1803,5.47,1804,3.599,1805,3.599,1806,3.599,1807,3.599]],["t/314",[7,2.15,10,2.315,20,0.986,23,4.354,27,1.143,35,4.554,58,3.122,67,2.749,87,4.29,94,1.906,117,3.904,280,4.29,360,3.753,362,3.753,454,2.427,568,3.622,569,4.473,574,4.29,608,3.505,707,4.29,814,4.079,835,3.622,1004,3.304,1008,3.904,1032,3.622,1033,6.171,1039,5.284,1040,4.908,1041,4.908,1042,3.217,1043,4.908,1044,4.908,1045,4.079,1177,4.908,1252,3.622,1282,3.753,1531,4.554,1564,4.908,1808,5.444,1809,5.444,1810,5.444]],["t/316",[2,2.254,6,2.985,18,1.505,23,2.295,27,1.075,55,1.249,58,1.452,66,3.123,88,2.584,196,4.778,199,2.238,216,3.995,217,2.584,230,2.295,239,1.791,246,2.358,268,2.295,279,1.962,300,1.855,302,1.579,374,2.238,394,4.462,401,2.911,413,2.911,433,1.761,453,3.995,454,3.843,472,2.043,502,3.25,503,3.061,545,1.602,548,2.043,577,3.183,672,1.822,698,2.501,713,2.358,807,2.501,812,2.786,835,3.855,858,4.566,869,4.155,902,2.786,936,2.911,981,3.25,1004,4.206,1012,2.087,1027,2.678,1033,2.678,1039,2.786,1042,2.295,1045,4.342,1053,3.061,1055,3.502,1056,6.055,1057,3.502,1058,2.238,1059,3.502,1060,2.786,1061,3.502,1062,3.25,1063,3.502,1064,3.502,1065,3.502,1066,2.426,1067,3.502,1068,3.061,1069,3.502,1070,4.847,1071,3.502,1072,3.25,1346,3.502,1709,4.342,1811,3.885]],["t/318",[62,3.035,172,4.348,199,4.239,246,5.429,248,4.466,300,3.513,350,4.596,374,4.239,449,3.645,871,5.074,1073,3.645,1074,6.156,1075,6.156,1076,5.799,1077,6.634]],["t/320",[2,2.909,15,3.335,18,1.912,236,4.596,279,3.716,391,6.634,447,4.896,944,3.87,1066,4.596,1277,6.634,1323,5.799,1495,5.799,1568,6.634,1619,5.074,1812,7.359]],["t/323",[2,1.898,6,3.006,9,1.921,18,2.391,20,1.033,26,2.241,52,3.644,67,2.947,94,2.043,104,4.598,110,4.023,193,4.184,194,3.447,214,2.645,223,4.881,279,2.947,319,4.023,454,2.601,525,4.24,560,3.361,661,5.26,893,5.26,928,3.882,960,3.205,1012,3.135,1022,3.447,1073,2.89,1100,4.023,1223,5.26,1281,4.598,1285,4.184,1427,5.26,1429,5.26,1673,4.372,1813,5.835,1814,5.26,1815,5.26]],["t/325",[2,1.342,9,2.363,11,1.87,18,2.289,22,1.28,27,1.66,40,2.266,41,3.719,43,4.448,64,3.451,81,3.49,131,2.504,202,2.17,279,2.083,309,2.504,326,1.584,417,2.656,426,2.504,501,2.376,525,2.266,550,1.727,557,2.083,593,2.438,610,1.97,680,2.745,681,2.656,725,2.438,749,4.54,806,2.745,827,2.844,842,3.091,957,2.319,1022,2.438,1102,2.844,1250,1.839,1329,3.719,1343,5.461,1416,3.719,1613,2.958,1615,3.451,1816,6.059,1817,4.126,1818,3.451,1819,3.719,1820,4.126,1821,4.126,1822,3.719,1823,4.126,1824,3.719,1825,3.719,1826,4.126,1827,4.126,1828,4.126,1829,4.126]],["t/327",[9,1.876,15,3.875,18,2.369,20,0.762,22,1.768,26,2.188,58,2.13,59,2.25,63,2.814,77,3.459,94,2.661,97,2.627,122,4.49,154,3.204,170,2.823,173,4.27,251,4.49,305,2.5,350,3.559,430,5.137,545,2.35,588,3.062,796,3.282,927,4.27,999,6.356,1008,4.086,1032,5.055,1083,4.767,1830,5.699,1831,5.699,1832,5.699,1833,5.137,1834,5.699,1835,4.767,1836,5.699]],["t/329",[2,0.831,13,1.404,18,2.298,20,0.342,23,2.467,26,2.347,27,1.642,28,1.121,34,5.583,43,4.289,44,2.348,61,2.151,62,1.054,63,2.012,67,2.674,73,3.522,75,2.151,94,0.895,157,1.551,202,2.784,214,2.4,230,1.51,287,1.915,302,1.039,309,4.998,360,2.879,374,1.472,377,1.915,398,2.014,426,1.551,456,1.833,525,2.294,531,1.039,557,3.087,567,1.646,593,2.467,607,1.596,610,3.217,679,2.534,727,2.014,730,1.344,765,1.7,844,1.551,852,2.304,860,1.551,917,1.833,993,1.915,1001,1.7,1022,1.51,1048,2.138,1091,1.14,1100,2.879,1186,1.7,1259,1.915,1307,3.612,1328,3.493,1350,1.551,1446,2.304,1518,2.304,1606,1.7,1620,1.762,1710,1.915,1740,2.138,1794,3.764,1837,2.556,1838,2.304,1839,2.138,1840,2.304,1841,2.556,1842,2.138,1843,2.304,1844,2.556,1845,2.304,1846,2.138,1847,2.556,1848,2.304,1849,2.556,1850,2.556,1851,7.231,1852,2.556,1853,2.304,1854,2.556,1855,4.176]],["t/332",[18,1.469,26,2.172,27,1.631,40,4.153,44,3.179,75,3.894,98,3.038,183,3.898,322,3.179,399,4.055,531,2.298,550,2.368,557,2.856,610,2.699,672,2.652,698,3.64,776,3.531,903,5.097,1252,3.762,1260,3.64,1282,3.898,1674,5.957,1728,5.664,1856,4.456,1857,4.73,1858,5.655,1859,4.73,1860,5.097,1861,4.73,1862,5.097,1863,5.097]],["t/334",[6,1.95,18,1.772,20,1.184,26,2.913,27,1.581,40,3.748,44,3.195,52,2.363,62,2.344,67,1.911,71,1.453,75,1.95,113,1.84,132,1.84,322,2.128,333,2.982,447,2.518,550,1.585,610,1.807,712,3.93,731,2.609,957,4.978,1032,5.679,1091,3.042,1140,3.666,1296,3.658,1370,3.166,1600,2.609,1613,2.714,1674,4.478,1679,4.753,1778,3.411,1818,3.166,1835,3.166,1856,5.976,1857,3.166,1859,3.166,1861,3.166,1862,3.411,1863,5.122,1864,3.785,1865,6.823,1866,3.785,1867,7.584,1868,3.411,1869,3.785,1870,3.785,1871,3.785,1872,3.785,1873,3.785]],["t/336",[2,1.512,18,1.208,26,2.537,27,1.654,40,5.042,75,3.403,531,3.122,607,2.903,608,2.993,610,2.22,957,4.967,1091,4.209,1140,3.549,1163,4.191,1674,6.591,1856,3.664,1857,3.889,1859,3.889,1860,4.191,1861,6.426,1868,4.191,1874,4.65,1875,4.65,1876,4.65,1877,3.664,1878,4.65,1879,4.65,1880,4.65,1881,4.65]],["t/338",[1,3.056,11,2.792,18,1.89,27,1.423,66,1.715,78,2.366,80,2.268,115,3.326,118,3.162,170,2.091,205,2.242,217,2.808,233,2.22,240,2.91,259,3.162,301,4.298,305,2.702,317,2.268,322,2.373,325,2.562,326,1.621,346,2.431,351,3.326,402,2.132,507,2.268,518,3.162,548,2.22,550,1.767,559,2.268,607,2.636,610,2.015,785,3.326,887,4.247,929,1.882,970,2.22,1140,2.268,1142,2.373,1187,4.247,1255,3.026,1307,4.298,1383,2.91,1517,2.717,1521,3.805,1567,3.805,1793,3.531,1882,4.221,1883,4.221,1884,4.221,1885,2.562,1886,4.221,1887,3.531,1888,4.221,1889,5.153,1890,3.805,1891,5.553,1892,4.221,1893,3.531,1894,4.221,1895,6.161,1896,6.161,1897,4.221,1898,4.221]],["t/340",[1,1.718,2,0.801,7,0.972,11,1.116,18,1.34,20,1.004,22,1.854,24,1.301,26,1.981,27,1.639,28,1.08,31,1.844,44,2.899,65,1.765,67,1.243,68,1.135,78,0.945,94,1.418,97,1.135,105,1.697,107,1.585,119,1.721,205,1.474,206,1.135,248,1.494,305,1.08,308,1.585,309,3.627,310,1.844,407,1.844,525,1.352,531,1,550,2.159,557,2.045,560,1.418,567,1.585,610,3.153,690,1.638,703,2.059,741,1.585,797,1.765,808,1.765,851,1.697,856,1.844,929,2.299,944,1.295,957,2.276,970,1.295,1001,1.638,1075,2.059,1140,3.21,1142,1.384,1187,1.697,1250,2.299,1255,1.765,1307,5.064,1328,2.059,1345,3.65,1383,2.791,1393,2.219,1415,4.649,1517,1.585,1620,1.697,1815,2.219,1833,4.649,1889,2.059,1893,2.059,1899,2.219,1900,2.462,1901,2.462,1902,2.219,1903,2.462,1904,4.049,1905,4.049,1906,5.158,1907,2.462,1908,2.462,1909,2.462,1910,2.462,1911,2.462,1912,2.219,1913,4.049,1914,4.049,1915,3.65,1916,2.462,1917,2.462,1918,2.462,1919,2.219,1920,2.462,1921,2.219,1922,2.219,1923,2.219,1924,2.462,1925,2.219,1926,4.049,1927,2.462,1928,5.158,1929,2.462,1930,2.462,1931,4.049,1932,2.462,1933,2.462,1934,2.462]],["t/342",[18,2.553,20,1.09,112,3.746,198,3.202,200,3.141,269,3.714,305,2.781,317,3.406,326,2.435,548,3.334,557,3.202,851,4.371,885,4.082,929,2.826,1022,4.814,1307,4.814,1323,4.996,1451,4.735,1885,3.848,1935,5.715,1936,4.546]],["t/344",[1,1.278,2,0.75,10,0.981,15,1.045,18,2.404,20,0.976,22,1.528,26,1.892,27,1.451,28,1.012,37,3.459,39,0.996,40,1.267,49,1.329,50,1.4,59,0.911,62,1.583,63,1.89,70,1.165,71,1.892,132,1.121,170,1.143,200,3.157,202,1.213,204,1.239,205,1.793,206,1.063,214,1.045,218,1.818,237,2.329,239,1.063,240,1.59,256,1.44,266,2.061,269,1.583,300,1.101,301,1.363,317,3.696,341,1.929,360,1.59,409,1.728,424,1.929,436,1.378,443,2.875,447,1.534,449,1.143,451,1.654,472,1.213,478,1.654,510,1.59,545,1.583,557,1.165,568,1.534,591,4.117,608,1.485,647,2.837,654,1.818,668,1.654,671,1.654,685,2.837,712,1.329,887,1.59,944,1.213,971,2.079,1001,1.534,1027,1.59,1140,2.647,1161,2.875,1184,1.818,1187,1.59,1284,1.929,1350,1.4,1351,1.929,1383,1.59,1451,3.352,1527,2.329,1607,5.421,1613,1.654,1660,2.079,1673,1.728,1678,1.728,1728,1.728,1793,1.929,1800,3.459,1801,4.441,1825,2.079,1856,1.818,1885,2.329,1889,1.929,1891,3.459,1893,1.929,1912,2.079,1919,2.079,1921,2.079,1922,2.079,1936,2.751,1937,2.079,1938,1.818,1939,4.776,1940,2.307,1941,1.485,1942,2.307,1943,2.307,1944,3.837,1945,3.837,1946,2.307,1947,2.875,1948,3.837,1949,3.171,1950,2.307,1951,3.837,1952,3.837,1953,2.307,1954,2.307,1955,2.307,1956,2.307,1957,2.307,1958,2.307,1959,1.485,1960,2.079,1961,4.524,1962,2.307,1963,2.307,1964,2.307,1965,2.307]],["t/346",[18,2.481,20,0.965,67,3.643,154,4.056,305,3.165,443,5.405,647,4.155,778,5.363,1022,4.262,1084,4.155,1389,5.173,1451,3.794,1527,4.378,1541,4.644,1678,5.405]],["t/348",[12,3.509,18,1.769,20,1.141,115,5.368,293,5.49,302,2.768,305,3.741,326,2.616,389,2.81,443,6.39,1003,4.884,1233,6.39,1307,4.025,1350,4.134,1784,6.14,1819,6.14,1915,6.14,1966,6.14,1967,6.14]],["t/350",[6,3.383,18,2.165,20,1.115,45,4.709,119,2.792,121,3.608,284,3.338,302,2.669,389,2.709,432,3.986,448,4.101,588,3.528,778,3.986,960,3.608,1001,4.369,1004,3.986,1022,3.88,1084,3.783,1100,5.745,1142,3.692,1966,5.92,1968,5.92,1969,6.567,1970,5.92]],["t/352",[10,2.041,18,2.323,24,1.543,38,2.176,40,2.638,61,2.474,62,1.981,66,1.952,67,2.425,73,3.194,83,2.914,94,1.681,119,2.041,170,2.379,205,1.748,218,3.784,230,3.992,268,2.837,284,3.582,299,3.178,326,2.595,336,3.784,340,3.443,389,1.981,447,3.194,507,3.63,557,2.425,588,2.58,647,2.766,725,2.837,796,2.766,928,3.194,944,2.525,959,4.845,1015,4.017,1045,3.598,1091,2.141,1097,5.325,1142,3.799,1250,2.141,1281,3.784,1294,4.017,1307,2.837,1389,4.845,1451,2.525,1508,3.598,1885,2.914,1939,3.598,1971,4.802,1972,4.802,1973,4.802,1974,4.329]],["t/354",[0,5.578,4,5.383,5,5.578,18,2.102,473,7.293,561,6.376,1975,8.091]],["t/356",[1,2.094,9,2.069,26,2.414,80,3.377,217,4.181,320,4.333,362,4.333,366,5.666,402,3.174,509,3.453,807,4.047,1060,4.507,1250,2.802,1842,5.258,1887,5.258,1976,5.666,1977,6.778,1978,5.81,1979,7.465,1980,5.666,1981,5.666,1982,6.286,1983,6.286,1984,6.286,1985,5.258,1986,6.286,1987,6.286,1988,5.666]],["t/358",[9,2.063,14,1.782,20,0.578,24,1.388,27,1.583,28,1.895,59,2.912,63,1.422,126,3.295,200,2.14,239,1.991,253,2.488,264,2.698,279,2.182,298,2.781,300,2.063,302,1.756,304,3.295,317,2.321,325,2.622,327,2.874,417,2.781,545,2.584,593,2.553,672,2.026,757,3.237,762,5.799,772,4.32,816,2.698,1017,4.32,1978,5.286,1989,5.648,1990,5.648,1991,4.32,1992,3.894,1993,3.614,1994,4.168,1995,5.648,1996,6.266,1997,6.266,1998,6.266,1999,5.648,2000,6.266,2001,6.266,2002,5.648,2003,5.241,2004,5.648]],["t/360",[1,1.199,7,1.421,8,1.043,9,1.801,12,1.854,14,2.256,27,1.558,59,2.92,62,2.256,63,1.185,66,1.463,200,1.783,239,1.659,264,2.247,298,2.317,299,3.232,300,1.718,304,1.892,306,2.612,317,1.933,327,2.394,332,2.317,427,1.933,509,1.977,762,3.922,772,3.771,796,2.073,1013,1.817,1042,2.126,1073,1.783,1307,4.368,1500,4.562,1762,3.01,1923,3.244,1976,3.244,1977,3.01,1979,4.311,1981,3.244,1985,3.01,1990,3.244,1994,3.639,2005,7.393,2006,3.599,2007,4.576,2008,3.599,2009,3.599,2010,3.599,2011,3.244,2012,3.599,2013,3.599,2014,3.599,2015,5.47,2016,8.085,2017,5.47,2018,5.47,2019,7.549,2020,5.47,2021,5.47,2022,5.47,2023,5.47,2024,5.47]],["t/362",[7,1.657,9,2.019,11,1.902,14,1.731,18,1.09,24,1.349,27,1.615,48,4.668,51,4.834,55,1.972,59,2.863,220,2.359,264,2.621,284,2.457,286,3.625,302,1.706,309,2.547,325,2.547,332,2.702,433,1.902,478,4.399,762,4.399,1001,2.792,1017,4.229,1502,6.065,1985,3.51,1988,5.53,1994,4.081,1999,5.53,2019,3.783,2025,4.197,2026,5.53,2027,6.536,2028,8.487,2029,6.135,2030,6.135,2031,6.135,2032,3.783,2033,6.135,2034,7.251,2035,4.197,2036,4.197,2037,3.783,2038,4.197]],["t/364",[18,1.971,264,4.739,309,4.605,317,4.077,635,4.885,1383,5.231,1978,5.441,1979,7.18,2039,7.588,2040,7.588,2041,7.588,2042,7.588]],["t/366",[18,1.457,20,0.751,22,1.741,23,3.315,24,2.417,56,4.204,91,4.694,119,2.385,120,4.694,122,5.926,124,6.291,279,2.834,298,3.612,302,3.057,340,4.023,422,3.232,433,2.543,545,2.314,815,7.647,1042,3.315,1087,5.003,1600,3.868,1620,3.868,1978,6.975,1980,8.169,2043,7.521,2044,5.611,2045,4.694,2046,5.611,2047,7.521]],["t/368",[14,1.721,20,0.817,39,1.802,59,2.854,60,2.107,63,2.011,66,1.696,112,2.465,174,2.776,198,2.107,200,2.067,236,3.815,239,3.667,298,2.686,299,2.937,302,3.441,304,3.213,312,3.49,317,2.242,327,2.776,340,2.992,389,1.721,417,2.686,419,3.288,427,3.282,454,3.222,545,2.52,547,3.667,968,2.776,1012,2.242,1032,2.776,1056,3.288,1091,1.86,1176,3.761,1344,3.49,1731,3.761,1762,3.49,1853,3.761,1978,6.719,1989,5.507,1994,5.292,1995,3.761,2002,5.507,2003,5.11,2004,5.507,2045,3.49,2048,6.109,2049,3.126,2050,3.288,2051,3.761,2052,4.173,2053,4.173,2054,4.173,2055,4.173,2056,4.173]],["t/370",[1,2.27,18,1.769,23,4.025,24,2.189,27,1.057,86,4.254,220,3.829,279,3.44,298,4.385,304,4.485,306,3.252,316,3.374,325,4.134,507,3.66,1543,5.104,1977,7.787,1978,4.884,1979,5.368,2057,6.812,2058,6.812,2059,6.812]],["t/373",[9,1.936,27,1.433,59,2.323,66,2.39,239,2.711,278,3.307,309,3.57,317,3.16,728,4.92,925,4.635,1194,4.635,1340,4.92,1523,4.92,1524,4.92,1947,6.919,1993,4.92,1994,5.162,2060,5.882,2061,7.76,2062,5.882,2063,5.882,2064,5.882,2065,5.882,2066,5.882,2067,5.882,2068,7.76,2069,5.882,2070,5.882,2071,4.407,2072,5.882,2073,5.302,2074,5.882]],["t/375",[27,1.648,59,1.472,62,1.537,302,1.515,304,1.96,305,1.635,402,1.882,449,1.846,762,2.672,797,2.672,906,2.479,1017,2.569,1350,2.262,1523,3.117,1524,3.117,1947,4.209,1994,4.497,2016,9.548,2075,3.727,2076,3.359,2077,3.727,2078,3.727,2079,5.617,2080,3.359,2081,3.727,2082,3.727,2083,3.727,2084,3.727,2085,3.727,2086,3.727,2087,3.727,2088,3.727,2089,3.727,2090,3.727,2091,3.727,2092,3.727,2093,3.727,2094,3.727,2095,3.727,2096,3.727,2097,3.727,2098,3.727,2099,3.727,2100,3.727,2101,3.727,2102,3.727,2103,3.727,2104,3.727,2105,3.727,2106,3.727,2107,3.727,2108,3.727,2109,3.727,2110,3.727,2111,3.727,2112,3.727]],["t/377",[8,2.245,10,3.294,20,1.037,39,3.346,263,4.256,299,3.149,316,3.838,572,6.106,1013,3.913,1014,5.155,1500,5.342]],["t/379",[2113,7.543,2114,7.543,2115,8.368,2116,8.368]],["t/381",[2113,7.72,2114,7.72]],["t/384",[8,2.011,14,2.863,30,4.212,39,3.725,59,2.741,70,3.505,239,3.199,306,3.313,337,5.469,472,3.65,796,3.998,844,4.212,1517,5.555,2117,6.941,2118,9.389,2119,8.628,2120,6.941]],["t/386",[8,2.397,62,3.413,472,4.351,1209,6.921,2121,8.274]],["t/388",[248,5.138,433,3.837,2122,8.465]],["t/390",[8,2.485,13,3.777,18,2.428,20,0.92,27,1.067,70,3.472,157,4.173,182,4.93,417,4.426,433,3.116,569,4.173,608,4.426,842,5.152,1022,4.062,1383,4.74,2123,6.876,2124,6.876,2125,5.418,2126,6.876,2127,6.198]],["t/392",[12,3.006,14,2.407,27,0.905,59,3.048,66,2.371,239,3.558,241,5.321,244,4.598,246,3.541,265,4.598,267,4.598,268,3.447,272,4.184,298,3.756,299,3.137,306,4.129,307,4.023,309,3.541,384,4.372,656,2.89,1016,4.881,1017,4.023,1451,3.068,1495,4.598,1496,5.26,1497,5.26,1498,4.372,1947,5.783,2128,5.835,2129,4.561,2130,7.719,2131,5.835,2132,5.26]],["t/395",[1,2.025,20,0.813,22,1.885,24,1.953,63,2,106,4.169,198,4.004,202,3.196,240,4.189,304,3.196,306,2.901,389,3.27,401,4.553,449,3.01,454,2.709,513,4.043,671,4.357,672,2.85,702,5.083,1035,6.247,1186,4.043,1225,5.083,1348,4.847,1451,3.196,1733,5.94,1941,3.912,1949,3.912,2045,5.083,2133,4.789,2134,6.077]],["t/397",[8,1.937,10,2.843,22,2.075,126,3.517,237,4.059,260,4.059,389,3.477,436,2.402,449,3.313,454,2.981,463,5.011,635,4.305,685,3.852,720,3.445,1348,3.673,1451,3.517,1890,6.028,1941,4.305,1949,5.427,2135,6.687,2136,6.687,2137,6.687,2138,6.687,2139,6.687]],["t/399",[8,1.853,20,1.096,59,3.57,68,2.948,119,2.719,214,2.899,220,3.595,304,4.309,306,3.053,389,2.638,427,4.857,563,4.255,635,4.117,654,5.039,716,3.595,1042,3.779,1187,4.409,1348,4.501,1941,4.117,1949,4.117,2140,6.395,2141,7.386,2142,5.765]],["t/401",[427,4.299,635,5.152,1348,4.396,1498,5.996,1941,5.152,1949,5.152,2143,8.003,2144,7.214]],["t/403",[8,2.567,10,2.605,20,0.82,304,3.223,308,3.945,316,3.035,353,7.185,395,2.874,427,4.282,472,3.223,509,3.366,679,3.719,734,3.827,812,4.394,871,4.225,1260,3.945,1348,5.654,1448,4.829,1451,4.934,1733,4.591,2145,6.128,2146,6.128]],["t/405",[8,1.677,15,2.624,20,1.153,59,3.032,63,2.528,170,2.867,230,3.42,264,3.615,279,2.923,282,4.561,298,4.943,304,4.038,306,2.763,307,5.294,317,3.11,336,4.561,417,3.727,635,3.727,654,4.561,844,3.513,848,4.561,1108,4.337,1187,3.991,1348,4.733,1350,4.66,1600,3.991,1941,3.727,1949,3.727,2142,5.218,2147,6.922,2148,5.789,2149,5.789]],["t/407",[8,1.808,9,1.414,10,3.124,18,1.116,20,0.983,27,0.666,48,2.765,59,3.383,63,1.414,106,4.241,112,2.538,196,2.961,246,4.46,298,2.765,300,2.05,306,4.089,322,2.415,427,3.352,433,1.947,454,2.782,463,3.218,547,1.98,550,1.798,635,4.017,679,2.607,683,3.218,734,5.58,785,4.917,961,3.384,997,3.872,1027,2.961,1056,5.79,1348,4.909,1451,3.864,1941,4.017,1949,4.017,2051,3.872,2141,3.872,2147,3.872,2150,4.295,2151,5.624,2152,3.872,2153,4.295,2154,6.239,2155,4.295,2156,3.872,2157,4.295]],["t/410",[8,1.775,11,2.777,18,1.592,46,3.366,77,4.838,116,4.591,126,3.223,130,3.62,178,3.945,193,4.394,196,4.225,230,3.62,240,4.225,317,3.292,336,4.829,588,3.292,710,5.126,713,3.719,732,5.303,737,3.719,842,4.591,967,3.445,1058,3.529,1217,5.126,1348,5.343,1606,4.077,2158,6.128,2159,6.128,2160,5.524,2161,6.128]],["t/412",[1,1.52,11,2.068,20,1.017,29,3.817,46,2.506,52,2.849,55,2.094,63,2.73,97,2.103,98,3.501,126,3.427,132,3.168,206,2.103,217,3.035,236,2.849,317,3.501,424,3.817,445,4.335,468,3.595,477,3.817,478,3.271,563,3.035,607,2.849,620,4.113,734,4.07,800,4.113,805,4.113,808,3.271,844,2.769,957,2.565,967,3.664,968,3.035,1009,3.145,1091,2.905,1140,2.451,1179,3.595,1285,4.673,1296,2.937,1348,4.555,1370,3.817,1398,3.817,1619,3.145,1936,4.673,2127,4.113,2160,4.113,2162,4.113,2163,4.563,2164,4.563,2165,4.563,2166,4.563,2167,4.563,2168,4.563,2169,3.595,2170,4.563,2171,2.849,2172,4.563,2173,4.563,2174,4.563]],["t/414",[20,0.728,44,3.061,51,4.29,59,3.299,98,2.925,107,3.505,126,4.708,198,2.749,199,3.136,224,3.505,237,3.304,239,2.51,317,2.925,322,3.061,409,5.522,426,3.304,563,3.622,588,2.925,685,3.136,712,3.136,734,3.4,770,4.908,802,4.29,860,3.304,862,4.079,873,4.554,989,4.29,1090,3.904,1091,3.724,1109,4.554,1260,4.744,1296,3.505,1348,5.138,1679,4.554,1709,4.079,2175,4.908,2176,5.444,2177,5.444]],["t/416",[8,2.297,20,0.813,22,1.885,59,3.131,126,4.169,166,3.265,206,2.801,253,4.566,300,3.785,304,3.196,306,2.901,786,4.553,871,4.189,1348,3.338,1500,4.189,1702,4.789,1728,7.268,1747,5.083,1792,5.478,1936,5.684,1941,3.912,1949,3.912,2178,6.077,2179,6.077,2180,6.077,2181,6.077,2182,6.077]],["t/418",[2,2.446,20,1.135,40,3.082,55,1.803,58,2.097,77,3.406,80,4.04,106,2.951,246,3.406,269,3.102,389,2.314,419,5.926,453,3.868,454,3.782,647,4.332,662,6.779,712,3.232,860,3.406,902,4.023,989,4.422,1035,5.926,1072,4.694,1091,2.502,1342,4.694,1348,5.458,1451,3.955,1835,4.694,2183,5.611,2184,5.611]],["t/421",[7,2.185,20,1.234,22,1.133,24,1.174,62,1.506,71,1.403,77,2.217,118,2.737,121,2.006,204,1.962,256,2.281,266,4.525,269,1.506,284,3.206,286,2.158,299,1.484,370,2.878,415,1.845,436,1.312,442,4.788,446,2.281,550,2.316,557,1.845,559,2.972,577,2.006,656,1.809,680,2.43,681,3.562,848,4.359,887,2.518,967,3.11,977,2.619,1009,2.518,1013,4.418,1014,4.956,1062,5.586,1092,5.136,1093,3.055,1219,2.053,1509,5.586,1543,2.737,1582,3.946,1603,5.004,2037,4.987,2185,3.652,2186,5.066,2187,3.652,2188,3.652,2189,3.358,2190,3.292,2191,3.652,2192,3.652,2193,3.652,2194,3.652,2195,2.281,2196,3.652,2197,3.652,2198,3.652,2199,3.652,2200,2.878,2201,3.652,2202,3.652,2203,3.652]],["t/424",[22,2.094,24,2.169,168,4.875,205,2.456,236,4.215,299,3.446,415,3.408,436,2.425,455,5.318,550,2.826,656,3.343,696,3.987,906,4.49,977,4.839,1219,5.212,2186,5.212,2204,4.345,2205,6.084]],["t/426",[20,0.813,22,1.885,24,1.953,132,2.954,168,4.169,198,3.069,204,3.265,205,2.212,299,3.222,370,4.789,415,3.069,436,2.183,531,3.586,577,3.338,624,4.189,656,3.927,696,3.59,818,5.478,977,4.357,1013,3.069,1219,3.416,1710,4.553,1967,5.478,2186,5.258,2195,3.795,2204,3.912,2206,8.824]],["t/428",[22,2.929,94,2.182,113,3.03,168,3.277,212,3.892,278,3.504,282,4.911,299,3.63,308,4.012,415,4.07,422,5.632,436,2.239,501,3.589,656,3.087,927,4.669,977,4.469,1219,3.504,1362,5.213,2186,5.021,2189,3.782,2204,5.188]],["t/430",[58,2.619,62,2.89,247,5.522,266,3.765,299,3.528,415,3.539,488,6.223,501,4.036,550,2.934,696,4.14,2186,4.879,2189,5.981,2200,5.522,2207,5.25,2208,6.316]],["t/432",[12,3.383,24,2.678,38,2.977,112,3.88,115,5.175,247,5.175,266,3.528,299,2.669,435,3.383,436,2.359,545,2.709,564,4.709,681,4.228,696,3.88,720,3.383,776,4.101,906,4.369,944,3.454,1013,3.317,1390,5.494,1491,4.921,1959,4.228,2125,5.175,2186,5.413]],["t/434",[15,2.825,20,1.078,63,2.051,97,4.353,106,3.277,132,3.03,284,2.496,299,3.838,317,3.348,332,4.012,415,3.147,476,4.296,656,3.087,931,3.348,1013,4.07,1145,5.213,1508,6.692,1678,4.669,1959,4.012,2186,5.021,2209,5.618,2210,6.232]],["t/436",[15,3.536,20,0.793,71,2.277,73,3.945,94,2.076,162,4.285,205,2.158,233,3.118,236,4.872,284,2.375,286,4.609,299,2.41,370,4.672,393,4.96,402,2.994,407,4.443,415,3.94,455,4.672,476,4.088,488,4.251,577,3.257,656,2.937,796,3.415,1073,2.937,1508,4.443,1543,5.845,1555,4.96,2186,3.333,2195,3.703,2209,5.345,2211,7.859,2212,5.929]],["t/439",[24,2.468,38,3.48,94,2.027,162,3.18,168,3.044,178,3.727,190,4.842,233,3.044,269,3.554,308,3.727,436,2.08,455,4.561,618,7.207,817,4.561,860,3.513,906,3.851,919,4.561,967,3.254,1058,3.334,1076,4.561,1087,3.851,1251,5.218,1296,3.727,2186,3.254,2189,5.229,2190,5.218,2207,4.337,2213,5.789,2214,4.561,2215,4.337,2216,4.337,2217,5.789,2218,5.789,2219,5.789,2220,5.789]],["t/441",[22,1.715,24,1.776,38,3.374,44,3.107,50,3.354,60,2.791,62,3.07,75,2.847,88,3.677,94,1.935,102,5.338,121,3.036,162,4.089,168,4.427,198,2.791,214,2.505,226,3.354,233,3.915,305,2.424,316,2.738,436,2.674,563,3.677,1603,4.141,2175,4.982,2186,4.185,2189,5.466,2207,4.141,2214,4.355,2215,4.141,2216,4.141,2221,4.355,2222,4.982,2223,7.444,2224,4.982,2225,4.982,2226,5.527]],["t/443",[1,2.059,38,2.801,49,3.559,75,4.129,82,4.869,99,4.63,162,4.402,198,4.047,316,3.061,329,5.727,548,3.25,967,3.474,1001,4.111,1959,3.978,2125,4.869,2186,3.474,2189,5.399,2215,4.63,2216,4.63,2221,4.869,2227,8.895,2228,6.179,2229,4.869,2230,6.179,2231,5.57,2232,6.179]],["t/445",[24,2.882,38,2.849,60,3.174,62,2.593,75,3.238,126,3.306,217,4.181,269,2.593,329,4.047,618,5.258,656,4.014,713,3.815,817,4.953,989,4.953,1296,4.047,1598,5.666,1992,5.666,2186,3.534,2189,5.443,2215,4.71,2221,6.385,2233,8.103,2234,7.304,2235,6.286,2236,5.258]],["t/447",[9,2.472,94,2.63,395,3.522,656,3.72,713,4.558,2189,4.558,2200,7.135,2215,5.627,2216,5.627,2221,5.918,2234,6.77,2237,7.51,2238,7.51]],["t/449",[2,3.054,20,0.928,22,2.153,60,3.505,81,3.998,226,4.212,395,3.255,427,3.729,887,4.785,929,3.094,1014,5.74,1371,5.806,1500,4.785,1935,6.257,2007,5.806,2186,3.902,2189,5.237,2200,5.469]],["t/451",[24,2.741,80,4.582,83,5.176,113,4.146,131,4.134,132,3.312,205,3.388,207,6.274,269,2.81,970,3.582,1084,3.923,1087,4.531,2071,5.104,2239,6.14]],["t/453",[24,2.992,60,3.44,61,3.509,66,2.768,80,3.66,83,5.176,94,2.385,109,4.531,207,5.814,214,3.865,657,4.696,1255,4.884,2240,6.72,2241,7.787,2242,5.698]],["t/455",[20,0.63,24,1.513,27,1.549,70,2.378,78,1.808,83,4.695,94,1.649,98,2.53,113,2.289,183,3.247,205,2.426,206,2.171,207,4.831,350,2.941,362,3.247,395,2.209,410,3.711,422,2.712,479,3.711,545,1.942,547,2.171,647,3.839,686,4.695,794,2.858,929,2.099,944,2.477,1119,3.377,1127,4.245,1614,4.245,1742,4.245,1747,5.575,2071,4.994,2229,5.252,2240,3.711,2241,5.575,2242,3.939,2243,6.008,2244,4.709,2245,4.709,2246,4.709,2247,3.939,2248,4.709,2249,4.709,2250,5.575,2251,4.709,2252,3.032,2253,4.245,2254,4.709,2255,4.709]],["t/457",[9,2.181,20,0.886,24,2.694,27,1.426,46,3.64,48,4.266,70,3.347,78,2.545,134,3.485,166,3.56,202,3.485,205,2.412,207,4.138,422,4.827,510,4.569,686,5.087,1637,4.965,2239,5.974,2241,5.543,2250,7.011,2256,5.974]],["t/459",[24,3.157,27,1.339,70,3.505,78,2.665,83,5.698,94,2.43,113,3.374,118,5.2,183,4.785,202,3.65,207,4.334,422,3.998,794,4.212,810,4.977,2256,7.778]],["t/461",[1,1.689,20,0.939,24,2.255,34,3.263,46,2.784,48,3.263,69,4.239,70,3.543,80,4.324,83,4.884,98,2.723,109,3.371,112,4.145,113,2.464,119,2.154,131,3.076,154,2.849,166,2.723,194,2.994,202,2.665,207,5.424,395,3.291,448,3.165,647,2.919,686,3.076,712,2.919,730,2.665,810,3.634,1022,2.994,1073,2.51,1084,2.919,1087,3.371,1158,4.568,1451,2.665,1582,2.994,1824,4.568,1936,3.634,1941,3.263,1949,3.263,2071,3.797,2242,4.239,2243,6.325,2247,4.239,2250,4.239,2253,4.568,2257,4.568,2258,5.068,2259,3.797]],["t/463",[1,2.357,11,3.207,22,2.195,27,1.469,77,4.294,78,2.717,106,3.721,647,5.029,1250,3.154,1513,4.877,1642,5.073,2240,6.88,2260,7.87,2261,7.075,2262,7.075]],["t/465",[24,2.49,80,4.163,83,4.703,113,3.767,131,4.703,205,2.82,207,5.761,269,3.196,970,4.075,2260,6.985]],["t/467",[24,2.439,119,3.226,205,2.762,207,4.739,284,3.65,506,4.483,679,4.605,929,3.383,2071,5.685,2240,5.979,2263,7.588,2264,7.588]],["t/470",[78,2.592,79,5.645,120,5.645,132,3.281,212,4.215,226,4.096,283,4.653,667,6.353,716,3.794,851,4.653,960,3.707,1014,4.49,1032,4.49,1316,6.084,1615,5.645,1877,6.681,2026,6.084,2133,5.318,2205,6.084,2265,6.681,2266,6.749,2267,6.749]],["t/473",[1,0.416,8,1.528,9,0.411,12,0.643,14,1.997,20,0.877,22,1.166,24,0.988,27,1.587,28,0.548,44,0.702,51,2.421,55,0.401,58,1.148,59,1.714,60,1.552,62,1.267,66,0.915,67,0.631,107,3.116,110,0.861,134,0.657,162,0.686,178,3.64,197,0.804,198,1.552,206,1.416,214,1.02,217,0.831,224,0.804,233,0.657,239,1.038,264,1.919,267,0.984,268,0.738,279,0.631,284,2.113,286,1.816,297,0.984,299,0.507,300,0.596,302,1.528,304,1.616,316,0.619,320,0.861,325,0.758,374,0.719,389,0.515,402,0.631,436,0.809,443,1.686,449,1.862,463,0.936,506,0.738,509,3.601,513,1.497,525,0.686,531,1.249,557,1.899,577,0.686,581,0.861,584,0.861,656,0.619,672,0.586,720,1.16,725,1.816,762,0.895,772,2.592,776,0.78,802,0.984,806,1.497,807,0.804,811,2.77,856,2.302,925,3.815,931,0.671,1017,0.861,1042,0.738,1073,1.115,1082,2.302,1108,2.302,1145,1.045,1150,1.126,1250,1.37,1442,1.126,1498,0.936,1500,0.861,1502,4.049,1528,1.126,1532,2.029,1550,0.984,1601,1.126,1709,0.936,1749,2.77,1818,1.045,1887,1.045,1925,1.126,1947,0.936,1970,2.029,1993,1.045,1994,1.497,2003,1.045,2027,3.389,2032,2.77,2050,2.962,2073,2.77,2076,1.126,2186,3.367,2236,1.045,2268,1.249,2269,1.249,2270,1.249,2271,1.249,2272,4.841,2273,1.126,2274,2.251,2275,1.249,2276,1.249,2277,1.249,2278,1.249,2279,1.249,2280,1.249,2281,1.249,2282,1.249,2283,1.249,2284,1.249,2285,1.249,2286,1.249,2287,1.249,2288,1.249,2289,1.249,2290,1.249,2291,1.249,2292,1.249,2293,1.249,2294,1.249,2295,1.249,2296,1.249,2297,1.249,2298,1.249,2299,1.249,2300,1.249,2301,2.251,2302,2.251,2303,1.249,2304,1.249,2305,1.249,2306,1.249,2307,1.126,2308,1.249,2309,1.249,2310,1.249,2311,1.249,2312,1.249,2313,1.249,2314,1.249,2315,2.251,2316,1.249,2317,1.249,2318,1.126,2319,1.249,2320,3.759,2321,1.249,2322,4.841,2323,5.99,2324,4.841,2325,3.073,2326,3.073,2327,2.251,2328,3.073,2329,3.073,2330,3.073,2331,3.073,2332,3.073,2333,3.073,2334,3.073,2335,3.073,2336,3.073,2337,3.073,2338,1.249,2339,3.073,2340,1.249,2341,1.249,2342,3.073,2343,3.073,2344,3.073,2345,3.073,2346,1.249,2347,3.073,2348,4.841,2349,3.073,2350,3.073,2351,1.249,2352,1.249,2353,1.249,2354,1.249,2355,1.249,2356,1.249,2357,1.249,2358,1.249,2359,1.249,2360,1.249,2361,1.249,2362,1.249,2363,1.249,2364,1.249,2365,1.249,2366,1.249,2367,1.249,2368,1.249,2369,1.249,2370,1.249,2371,1.249]],["t/476",[24,2.192,63,2.245,106,2.559,113,2.366,132,3.316,157,2.953,204,2.614,266,2.614,269,2.007,283,5.884,284,3.419,297,5.374,308,5.07,667,3.646,1060,5.646,1194,3.834,1348,3.747,1588,4.386,1846,7.517,1877,7.692,2211,4.386,2231,6.148,2265,7.081,2372,4.386,2373,4.866,2374,4.866,2375,4.866,2376,4.866,2377,4.386,2378,4.386,2379,4.866,2380,4.866,2381,4.866,2382,4.866,2383,4.866,2384,4.866]],["t/478",[39,3.455,61,4.123,70,4.041,236,4.997,239,3.689,1194,6.306,1241,7.214,2171,4.997]],["t/480",[1,1.814,9,1.792,14,2.246,27,1.451,28,2.388,59,3.299,63,1.792,198,2.749,236,3.4,239,3.851,304,2.863,306,2.599,402,3.722,507,3.959,906,3.622,1058,4.245,1060,3.904,1091,2.427,1550,4.29,1877,4.29,2133,7.371,2151,4.908,2171,5.841,2236,4.554,2265,7.054,2385,5.444,2386,5.444,2387,5.444,2388,5.444]],["t/482",[7,3.231,1086,6.13,1142,4.599,2171,5.109,2265,6.447,2372,7.375]],["t/484",[20,1.242,43,4.403,672,3.673,1451,4.118,1939,5.868,2129,4.627,2389,7.059,2390,6.171,2391,7.831]],["t/486",[7,1.145,9,1.898,20,0.619,26,1.114,27,0.717,39,1.252,45,2.08,70,2.335,77,1.76,86,2.888,106,1.525,116,3.465,124,2.426,126,1.525,134,1.525,189,2.08,206,1.337,264,1.811,266,2.484,269,1.196,282,2.285,286,3.407,299,2.344,363,4.321,426,1.76,442,2.08,488,2.08,509,3.168,531,1.179,548,1.525,656,1.437,660,2.285,681,3.712,734,1.811,835,3.076,925,2.285,958,2.614,967,1.63,1012,1.558,1058,3.321,1073,1.437,1086,5.739,1091,1.293,1097,2.285,1189,2.285,1219,1.63,1250,2.571,1258,2.614,1398,2.426,1451,1.525,1960,2.614,2050,2.285,2080,2.614,2129,4.525,2214,2.285,2222,5.198,2377,2.614,2378,2.614,2392,2.9,2393,2.9,2394,2.9,2395,3.703,2396,2.9,2397,2.9,2398,2.9,2399,2.9,2400,2.426,2401,2.9,2402,2.9,2403,2.9,2404,2.9,2405,5.198,2406,2.9,2407,2.9,2408,2.9,2409,2.9,2410,4.783,2411,3.601,2412,2.9,2413,2.9,2414,2.9,2415,2.9,2416,2.9,2417,2.9,2418,2.9,2419,2.9,2420,2.9,2421,2.285,2422,2.9,2423,4.624,2424,2.9,2425,2.9,2426,2.9,2427,2.9,2428,3.316,2429,2.9,2430,2.9,2431,2.9,2432,2.9,2433,2.08,2434,2.9,2435,2.9,2436,4.624,2437,2.9,2438,2.9,2439,2.9,2440,2.9,2441,2.9,2442,4.624,2443,4.624,2444,4.624,2445,4.624,2446,2.9,2447,2.9]],["t/488",[22,2.435,24,2.911,36,2.797,60,2.049,63,2.341,70,2.049,81,2.337,85,3.197,127,3.394,157,2.462,166,2.18,179,4.291,238,3.394,239,1.87,266,2.18,299,3.19,329,2.612,389,1.673,433,1.839,436,1.458,501,2.337,507,2.18,509,3.287,624,2.797,668,2.909,869,2.909,1013,3.964,1087,4.73,1219,2.281,1350,3.632,1550,6.185,1961,3.197,2049,3.04,2207,3.04,2224,3.657,2395,4.973,2410,2.534,2411,2.534,2448,6.409,2449,7.895,2450,5.984,2451,5.006,2452,5.984,2453,6.185,2454,4.057,2455,3.657,2456,3.657,2457,4.057,2458,3.737]],["t/491",[9,1.692,24,2.605,63,1.692,145,4.049,214,2.329,238,4.299,239,2.369,299,3.725,395,2.41,415,2.595,454,2.291,509,4.8,557,2.595,656,2.546,701,6.386,725,3.036,796,4.08,1013,2.595,1020,4.299,1048,4.299,1087,3.419,1219,4.558,1350,3.119,1431,4.299,1581,3.85,2129,3.036,2169,5.582,2204,3.308,2395,5.097,2410,3.209,2411,3.209,2428,5.813,2459,4.632,2460,4.049,2461,5.139,2462,3.85]],["t/493",[22,1.398,59,1.78,61,2.322,63,2.485,106,4.777,198,3.262,299,3.069,375,3.77,395,3.541,427,2.421,433,2.042,436,1.619,451,3.231,454,2.009,507,2.421,509,4.147,572,3.551,685,2.596,796,3.72,806,2.998,844,2.735,992,4.062,1013,3.813,1058,2.596,1084,2.596,1219,4.245,1491,3.377,1961,3.551,2129,4.461,2156,4.062,2169,5.09,2171,2.814,2204,4.159,2229,3.551,2252,2.901,2307,4.062,2390,5.09,2395,4.248,2410,2.814,2411,2.814,2428,4.632,2433,3.231,2458,4.715,2460,3.551,2462,5.657,2463,6.459]],["t/495",[15,2.266,24,1.606,30,3.034,36,3.446,59,1.974,63,2.842,77,5.508,106,2.629,127,4.181,166,2.685,179,3.584,256,3.121,299,3.51,301,2.953,436,1.796,448,3.121,509,2.746,557,2.524,656,4.278,679,3.034,713,3.034,734,3.121,796,2.879,1013,3.51,1058,2.879,1219,2.81,1237,3.939,1294,4.181,1451,3.655,1491,3.745,1505,4.181,1961,3.939,2204,3.218,2390,5.476,2395,4.449,2410,3.121,2411,3.121,2428,3.584,2433,3.584,2448,4.506,2449,4.506,2451,4.181,2453,3.939,2458,3.121]],["t/498",[8,1.664,20,1.022,24,1.846,119,2.442,166,3.086,168,4.017,266,4.104,269,2.369,363,4.303,393,4.804,395,2.694,509,3.155,525,3.155,557,3.857,656,2.845,668,4.118,794,3.486,1097,4.526,1219,4.294,1477,4.526,2162,5.177,2247,4.804,2395,4.421,2421,7.208,2451,4.804,2453,4.526,2458,4.77,2464,5.743,2465,5.743,2466,5.743,2467,5.743,2468,5.743,2469,5.743]],["t/500",[8,2.79,77,5.556,112,3.88,132,3.193,181,4.369,314,4.528,415,3.317,593,3.88,1013,4.209,1140,3.528,1175,5.175,1219,3.692,1606,4.369,1613,4.709,2204,4.228,2395,4.293,2411,5.717,2470,5.92,2471,6.567]],["t/503",[7,2.286,8,1.677,20,0.774,38,2.624,63,2.836,69,4.842,132,2.814,181,3.851,194,3.42,260,3.513,299,2.353,681,3.727,885,3.727,917,4.151,967,3.254,1620,3.991,1885,3.513,2129,4.537,2171,4.795,2195,5.38,2252,4.943,2259,4.337,2318,5.218,2395,2.982,2410,4.795,2411,5.73,2458,4.795,2472,5.506,2473,5.218,2474,4.842]],["t/505",[1,0.752,8,1.092,12,1.587,15,1.708,16,1.269,17,0.807,19,0.898,20,0.758,24,2.481,27,0.194,29,1.048,38,1.023,39,1.627,46,0.688,49,0.722,50,0.76,55,0.403,58,0.844,60,1.14,61,1.163,62,0.517,63,2.312,66,1.972,70,1.14,75,0.645,88,0.834,94,0.791,106,1.982,130,0.74,131,1.37,132,2.116,146,1.048,154,1.732,168,0.659,170,0.621,181,2.507,204,1.213,206,0.578,212,0.782,214,1.708,251,0.987,256,0.782,259,0.939,268,0.74,269,0.517,285,2.035,286,0.74,297,0.987,299,1.532,301,2.227,305,0.55,308,1.454,325,0.76,329,1.454,331,0.939,363,1.692,367,1.048,371,0.987,374,1.3,389,2.337,395,3.192,402,0.633,417,0.807,427,0.673,433,1.023,436,0.811,445,1.502,446,0.782,453,1.557,454,3.3,463,0.939,503,0.987,507,0.673,509,0.688,510,0.864,520,1.779,524,0.939,531,0.509,548,0.659,550,0.525,560,0.722,564,0.898,569,0.76,577,1.24,579,1.048,584,1.557,591,0.898,612,0.807,685,1.3,716,0.704,757,0.939,773,1.889,794,0.76,806,0.834,816,0.782,827,1.557,835,0.834,857,1.129,870,1.779,885,4.522,917,3.479,919,0.987,931,0.673,967,4.162,1009,0.864,1045,0.939,1073,0.621,1082,0.939,1084,0.722,1199,1.129,1229,1.129,1237,0.987,1242,1.048,1250,1.68,1259,0.939,1282,0.864,1286,1.048,1296,0.807,1498,0.939,1599,1.129,1600,0.864,1619,0.864,1704,1.129,1709,0.939,1755,2.035,1822,2.035,1845,1.129,1846,1.889,1885,0.76,1899,1.129,1936,0.898,1939,2.309,1959,0.807,2049,3.96,2129,4.645,2152,1.129,2169,0.987,2171,3.538,2189,2.641,2195,3.301,2204,1.454,2207,0.939,2216,1.692,2229,0.987,2252,2.802,2257,1.129,2259,1.692,2389,1.129,2390,2.428,2395,2.918,2410,4.822,2411,3.538,2433,2.21,2458,4.1,2460,1.779,2462,0.939,2472,4.062,2473,1.129,2474,4.058,2475,1.253,2476,1.253,2477,1.253,2478,5.876,2479,3.398,2480,1.253,2481,1.253,2482,1.129,2483,1.253,2484,1.253,2485,2.258,2486,1.253,2487,1.253,2488,1.129,2489,2.258,2490,1.253,2491,1.253,2492,3.082,2493,1.253,2494,1.253,2495,1.253,2496,4.764,2497,1.253,2498,1.253,2499,1.129,2500,1.253,2501,1.253,2502,1.253,2503,1.253,2504,1.253,2505,1.129,2506,1.253,2507,1.129,2508,1.253,2509,1.129,2510,1.129,2511,1.129,2512,2.258,2513,1.253,2514,1.253,2515,1.253,2516,1.253,2517,1.253]],["t/507",[15,2.414,24,2.851,38,2.414,94,1.865,106,2.801,132,2.589,199,3.067,234,4.801,239,2.455,395,3.405,423,6.073,433,2.414,436,1.913,445,3.543,506,3.147,509,2.925,513,3.543,885,4.674,967,4.643,1250,2.374,1282,3.672,2049,3.99,2129,4.289,2171,3.326,2189,5.013,2195,3.326,2252,3.429,2395,2.744,2410,5.158,2433,3.819,2458,5.158,2459,4.801,2478,4.455,2496,4.801,2499,4.801,2509,4.801,2518,5.326,2519,5.326,2520,5.326]],["t/509",[1,2.008,15,2.732,24,1.937,46,3.311,63,2.596,70,3.044,132,2.93,454,2.687,457,5.433,747,5.041,885,3.88,917,4.321,967,3.388,1620,4.155,1885,3.658,2129,4.659,2171,3.764,2195,4.924,2225,5.433,2252,5.076,2259,5.908,2395,4.062,2410,4.924,2411,4.924,2458,3.764,2474,5.041,2478,5.041,2510,5.433,2511,5.433,2521,6.027]],["t/512",[14,2.661,16,3.627,27,1.001,39,2.786,98,3.466,299,3.349,301,5.364,446,6.496,1013,3.258,2208,5.816,2273,5.816,2395,5.207,2433,4.626,2522,6.452,2523,6.452]],["t/514",[1,1.301,8,1.132,11,1.77,19,2.8,20,0.93,22,1.212,38,1.77,39,2.512,54,3.267,71,1.5,77,2.37,80,2.098,126,2.054,170,1.935,198,1.972,226,2.37,260,2.37,284,1.565,299,2.365,301,2.308,304,2.054,316,3.444,346,2.25,375,3.267,389,1.611,395,2.729,402,1.972,426,2.37,454,2.594,476,2.693,536,3.521,685,2.25,741,3.746,810,2.8,901,3.521,928,2.598,931,2.098,934,4.867,960,3.196,1013,1.972,1092,2.693,1142,3.271,1200,4.011,1219,3.271,1250,3.1,1508,2.926,1517,2.514,1581,2.926,1885,3.531,1939,2.926,2129,4.867,2252,2.514,2395,4.737,2400,3.267,2405,3.521,2411,2.439,2460,3.078,2462,2.926,2472,2.8,2524,3.906,2525,3.906,2526,3.906,2527,3.906,2528,3.906,2529,3.906,2530,3.906,2531,3.906,2532,3.906,2533,3.906,2534,3.906,2535,3.906,2536,3.906,2537,3.906,2538,3.906,2539,3.906,2540,3.521]],["t/517",[1,2.15,8,1.869,20,0.863,27,1.409,39,3.558,80,3.466,233,3.393,326,2.478,346,3.716,351,5.084,394,4.153,685,4.746,741,4.153,885,4.153,967,3.627,1885,3.916,2195,4.029,2395,3.324,2428,4.626,2458,4.029,2472,4.626,2541,6.452,2542,6.452,2543,7.428,2544,6.452]],["t/519",[19,3.819,20,0.971,24,2.333,26,2.788,39,2.299,54,4.455,58,1.99,106,2.801,208,4.197,212,3.326,227,4.455,240,3.672,263,2.925,286,3.147,409,3.99,435,2.744,436,2.608,462,4.801,513,5.495,534,5.44,559,2.861,561,4.197,672,2.498,685,3.067,717,5.205,741,5.317,782,3.99,885,3.429,967,2.994,1073,2.638,1099,4.197,1603,3.99,1684,4.801,1885,3.232,2171,3.326,2195,3.326,2395,4.255,2458,3.326,2472,3.819,2545,5.326,2546,5.326,2547,4.801]],["t/521",[1,0.849,2,0.828,8,1.53,20,0.706,24,1.338,26,0.978,27,0.395,32,1.591,39,1.1,55,0.819,80,3.613,106,2.777,119,1.77,131,1.546,132,3.509,144,1.695,153,1.34,166,1.368,168,1.34,170,1.262,171,1.756,207,1.591,233,1.34,256,2.6,263,2.287,272,1.826,300,1.216,316,1.262,332,1.64,351,3.281,363,3.957,380,1.695,384,1.909,389,1.051,400,3.281,402,2.103,409,1.909,436,0.915,445,1.695,454,2.719,509,1.399,513,1.695,584,1.756,682,2.131,685,2.398,772,1.756,778,3.702,847,2.131,885,1.64,967,1.432,993,1.909,1082,1.909,1092,1.756,1099,3.281,1239,1.505,1300,3.483,1364,2.296,1531,2.131,1607,3.281,1619,3.641,1728,1.909,1885,1.546,1959,2.681,2049,1.909,2129,3.12,2133,2.007,2144,2.296,2195,2.6,2252,1.64,2259,3.12,2395,4.463,2400,2.131,2421,2.007,2428,2.986,2456,3.754,2462,1.909,2470,2.296,2472,5.462,2479,3.754,2505,2.296,2543,2.296,2547,2.296,2548,4.164,2549,2.547,2550,2.547,2551,4.164,2552,2.547,2553,2.547,2554,2.547,2555,2.547,2556,4.164,2557,2.296,2558,2.547,2559,2.547,2560,2.547,2561,2.547,2562,2.547,2563,2.547,2564,2.547,2565,2.547,2566,2.547,2567,2.547,2568,2.547,2569,4.164,2570,2.547,2571,2.547,2572,2.547,2573,2.547,2574,4.164,2575,4.164,2576,4.164,2577,2.547,2578,2.547,2579,2.547,2580,2.547,2581,2.547,2582,2.547,2583,4.164,2584,2.547,2585,2.547,2586,2.547,2587,2.547,2588,2.547,2589,2.547,2590,2.547,2591,2.547,2592,2.547,2593,2.547,2594,2.547,2595,4.164,2596,3.754,2597,2.547,2598,2.547,2599,2.547]],["t/524",[8,1.373,20,0.634,27,0.735,28,2.079,55,1.523,59,3.894,62,1.955,66,1.926,272,4.801,275,3.735,299,3.617,302,1.926,306,4.532,316,2.348,548,3.521,656,3.846,1012,2.546,1013,3.921,1027,3.268,1073,2.348,1074,3.965,1500,4.616,1994,3.153,2007,3.965,2011,4.273,2129,3.956,2132,4.273,2395,4.346,2410,2.96,2421,3.735,2600,4.74,2601,4.74,2602,4.74,2603,4.74,2604,4.74,2605,4.74,2606,4.74,2607,4.74,2608,4.74,2609,4.74,2610,4.74,2611,4.74]],["t/526",[22,1.933,81,3.589,113,3.03,168,3.277,179,4.469,247,4.911,256,3.892,266,3.348,433,2.825,436,3.209,454,2.778,506,3.682,548,3.277,624,4.296,829,6.35,944,3.277,977,4.469,1013,3.147,1073,3.087,1219,3.504,1959,4.012,2050,6.35,2125,4.911,2395,4.152,2453,6.35,2612,6.232,2613,6.232,2614,6.232]],["t/529",[14,2.369,17,3.697,26,2.206,31,4.303,33,4.804,66,3.104,109,6.336,130,3.393,236,3.587,278,3.229,279,2.9,300,2.742,326,2.933,340,4.118,402,4.62,436,2.063,454,4.246,525,3.155,534,4.303,1066,3.587,1119,4.118,1284,4.804,1555,4.804,1678,4.303,2214,4.526,2507,5.177,2615,4.526,2616,5.743,2617,5.743,2618,5.743,2619,5.743]],["t/531",[2,2.526,11,1.547,14,1.408,27,0.53,31,2.558,33,2.856,46,2.886,48,4.124,55,1.097,58,1.964,60,2.654,61,1.759,68,2.422,74,3.078,94,1.84,97,1.574,107,3.383,119,2.234,131,2.072,172,2.017,193,2.448,205,1.243,220,1.919,228,2.354,257,2.132,278,1.919,305,2.81,374,1.966,426,3.189,427,1.834,435,4.226,454,2.343,525,1.875,559,2.823,560,3.026,569,2.072,591,2.448,601,2.448,663,2.856,716,2.954,725,2.017,737,2.072,765,2.271,795,3.078,797,2.448,807,2.198,810,2.448,814,2.558,862,2.558,919,2.69,929,1.522,931,2.823,943,3.937,944,2.763,960,1.875,1004,3.189,1012,1.834,1042,2.017,1276,3.078,1296,2.198,1340,4.395,1439,3.078,1495,2.69,1527,3.888,1600,2.354,1620,2.354,1621,3.078,1623,2.69,1937,3.078,1938,5.668,1959,3.383,2482,3.078,2615,2.69,2620,6.406,2621,3.414,2622,3.414,2623,3.414,2624,3.414,2625,3.414,2626,3.414,2627,3.414,2628,3.414,2629,5.255,2630,3.414,2631,3.414,2632,3.414,2633,3.414,2634,3.414,2635,3.414,2636,3.414,2637,3.414,2638,3.078]],["t/533",[61,3.383,97,3.027,200,4.128,204,3.528,226,3.986,284,3.338,287,4.921,299,2.669,305,4.016,712,3.783,720,3.383,849,5.494,1527,6.163,1582,4.924,1623,5.175,1838,5.92,1938,5.175,2615,5.175]],["t/535",[1,1.322,13,2.18,20,0.531,24,1.276,38,1.799,39,1.714,43,2.231,55,1.276,58,2.201,70,2.004,71,2.696,78,2.696,94,1.39,113,3.413,194,2.345,199,2.286,200,3.847,205,2.143,224,2.555,279,2.004,302,1.613,305,3.079,402,2.974,432,2.409,435,4.273,458,5.308,488,2.846,531,1.613,560,3.391,588,2.132,695,3.578,697,3.32,712,2.286,730,3.097,744,3.578,860,2.409,928,2.64,929,1.769,944,2.087,959,2.846,960,3.234,1003,4.222,1032,3.917,1060,2.846,1068,3.128,1084,3.391,1221,2.132,1233,2.974,1248,3.128,1250,3.129,1260,2.555,1389,2.846,1527,5.034,1559,3.578,1582,5.314,1606,2.64,1619,2.736,1623,3.128,2540,3.578,2596,3.578,2615,3.128,2639,3.969,2640,3.969,2641,3.578]],["t/537",[1,1.899,20,1.144,40,3.13,97,2.627,121,3.13,172,3.367,199,3.282,204,3.062,284,3.425,302,2.316,329,3.669,371,4.49,389,2.35,402,2.878,415,3.837,448,3.559,778,4.612,782,4.27,806,3.791,928,3.791,929,2.541,931,4.082,1003,4.086,1142,3.204,1200,3.929,1527,3.459,1529,4.767,1541,3.669,1543,4.27,1558,4.49,1582,4.49,2642,7.599,2643,7.599,2644,5.699,2645,5.699]],["t/539",[20,0.921,22,1.53,38,2.235,43,2.772,55,1.585,60,2.49,71,3.046,94,1.727,130,2.914,154,3.87,197,3.175,200,3.41,237,2.993,269,2.84,402,3.477,415,2.49,427,2.649,431,4.125,435,3.547,445,3.281,510,3.4,550,2.882,647,3.965,778,2.993,929,2.198,931,2.649,1012,2.649,1250,3.069,1260,3.175,1389,6.155,1451,2.593,1527,4.814,1541,3.175,1582,5.336,1606,3.281,1733,3.695,1902,4.445,1938,3.886,1968,4.445,2204,3.175,2252,3.175,2557,4.445,2646,4.931,2647,4.931,2648,4.931]],["t/541",[1,2.205,2,2.152,8,1.585,15,1.631,20,0.481,26,1.382,27,1.147,55,1.156,58,2.763,68,1.659,71,2.101,78,2.101,94,1.916,113,1.75,170,1.783,200,1.783,205,3.556,212,3.416,214,2.479,224,2.317,302,1.463,305,2.4,327,2.394,432,2.184,435,3.409,479,2.836,507,1.933,526,2.696,531,1.463,545,2.256,547,3.86,566,3.244,657,2.481,663,3.01,765,4.402,796,3.151,845,3.244,864,2.836,943,4.099,944,3.48,970,2.877,981,3.01,1004,2.184,1046,2.481,1100,2.481,1217,3.01,1221,1.933,1250,2.439,1268,3.244,1361,2.696,1390,3.01,1517,2.317,1558,2.836,1622,3.244,1659,3.244,1673,2.696,1766,5.536,2488,3.244,2649,3.599,2650,3.599,2651,3.599,2652,5.47,2653,5.47,2654,4.931,2655,5.47,2656,5.47,2657,3.599,2658,3.599]],["t/543",[1,1.899,13,3.13,18,1.48,27,1.179,61,2.936,64,4.767,70,2.878,78,2.188,94,1.995,130,3.367,145,4.49,170,2.823,205,3.111,284,3.425,299,2.316,398,4.49,446,3.559,545,3.134,547,3.503,765,5.055,943,4.27,960,3.13,1003,5.448,1084,3.282,1142,4.272,1148,4.767,1186,3.791,1233,4.27,1250,2.541,1582,3.367,2654,5.137,2659,5.699,2660,5.699,2661,5.699,2662,5.699]],["t/545",[20,0.651,39,2.101,58,1.818,60,2.457,61,4.057,68,2.243,71,2.619,78,1.869,205,3.761,214,2.205,220,2.735,269,2.007,302,1.978,435,2.507,526,3.646,531,1.978,545,2.813,547,3.144,725,2.875,765,3.237,860,2.953,921,5.705,929,3.041,944,2.559,970,2.559,1003,3.489,1004,4.139,1073,2.41,1082,3.646,1142,2.735,1200,3.354,1221,2.614,1242,4.07,1250,2.169,1259,3.646,1265,5.705,1286,4.07,1323,3.834,1563,4.386,1582,2.875,1735,4.386,1959,3.132,2641,4.386,2663,4.866,2664,4.866,2665,4.866]],["t/549",[9,2.355,20,0.697,26,3.655,32,3.255,43,2.93,58,1.948,110,3.593,119,3.041,172,3.079,189,5.856,319,3.593,427,2.8,476,3.593,550,2.995,571,4.36,929,2.324,959,3.737,1049,4.698,1090,5.129,1091,3.189,1186,4.759,1266,4.698,1350,5.591,1426,4.698,1493,5.637,1613,5.129,1673,3.905,1751,4.698,2638,4.698,2666,5.212,2667,5.212,2668,5.212,2669,5.212,2670,4.698,2671,5.212,2672,5.212,2673,5.212,2674,5.212]],["t/551",[9,2.468,20,0.595,26,2.459,27,1.611,44,4.216,52,2.78,68,2.951,112,2.63,134,2.341,162,2.445,170,2.205,228,3.069,286,2.63,374,2.564,449,2.205,453,3.069,501,2.564,550,3.637,560,2.564,610,3.057,657,3.069,672,3.003,712,2.564,844,2.702,960,3.517,1046,3.069,1088,2.961,1102,3.069,1129,4.013,1131,4.013,1186,2.961,1221,4.029,1255,3.192,1505,3.724,1740,3.724,1839,3.724,2675,7.499,2676,4.452,2677,4.013,2678,7.499,2679,4.452,2680,4.452,2681,4.452,2682,4.452,2683,4.452,2684,4.452,2685,4.452]],["t/553",[26,3.313,27,1.598,316,4.274,610,4.119,2686,6.941,2687,8.628,2688,6.941,2689,8.628,2690,6.941,2691,6.941,2692,6.941,2693,6.941]],["t/555",[27,1.549,550,3.351,1221,4.299,2677,7.214,2694,9.404]],["t/558",[2,2.802,27,1.578,55,1.86,67,2.923,78,2.223,81,4.963,119,2.461,189,4.151,278,3.254,293,3.727,435,4.727,436,2.08,545,2.388,550,2.424,574,4.561,929,3.841,1260,3.727,1361,4.337,1389,5.506,2670,5.218,2695,5.789,2696,6.922,2697,7.678,2698,5.789]],["t/561",[9,2.488,11,3.86,14,2.332,27,1.669,49,3.257,50,3.432,55,1.817,68,3.485,78,2.903,81,3.257,134,4.478,524,4.237,547,2.607,686,5.168,1012,3.038,1221,3.038,1501,4.73,2699,7.56,2700,5.655,2701,5.655]],["t/563",[9,2.242,78,2.616,97,3.14,119,2.896,154,3.829,160,5.698,199,3.923,794,4.134,829,5.368,1575,6.14,1589,6.14,2696,6.14,2702,6.812,2703,6.812,2704,6.812,2705,6.812,2706,6.812,2707,6.812,2708,6.812,2709,6.812,2710,6.812,2711,6.812,2712,6.812,2713,6.812]],["t/565",[2,2.893,6,3.183,9,2.638,20,0.827,26,3.416,44,3.474,58,2.309,68,2.848,93,5.169,162,4.402,278,3.474,319,4.26,502,5.169,725,4.735,730,4.215,731,4.26,1012,3.32,1600,4.26,1710,4.63,1974,5.57,2455,5.57,2714,6.179,2715,6.179,2716,6.179,2717,6.179,2718,6.179,2719,6.179]],["t/567",[2,2.027,18,2.093,20,1.078,26,3.095,28,2.734,42,8.051,94,2.182,170,3.087,174,4.146,202,3.277,215,4.911,237,4.891,322,3.504,374,3.589,550,2.609,731,4.296,765,4.146,944,3.277,1084,4.642,1285,4.469,1710,4.669,1814,5.618,1839,5.213,1840,5.618,1842,5.213,1843,5.618,1848,5.618]]],"invertedIndex":[["",{"_index":27,"t":{"5":{"position":[[129,1],[207,1]]},"9":{"position":[[823,1],[830,1],[842,1],[844,1],[854,1],[864,1],[866,1],[876,1],[889,1],[891,1],[899,1],[912,1]]},"13":{"position":[[174,2],[177,1],[200,2],[261,2],[264,1],[292,2],[362,2],[365,1],[399,2],[462,2],[465,1],[526,2]]},"15":{"position":[[108,1],[181,1],[365,1],[446,1],[461,1]]},"17":{"position":[[60,1]]},"19":{"position":[[704,2],[760,2],[763,2],[766,1],[768,3],[772,3],[818,2],[821,2],[824,1],[826,3],[830,3],[871,2],[874,2],[877,1],[879,3],[883,3],[934,2],[992,2],[995,2],[998,1],[1000,3],[1004,3],[1008,2],[1030,2],[1075,2],[1156,2],[1159,2],[1162,1],[1164,3],[1168,3],[1209,2],[1212,2],[1215,1],[1217,3],[1221,3]]},"37":{"position":[[7,1],[9,1],[24,1],[54,1],[103,1],[130,1],[172,1],[202,1],[247,1],[264,1],[308,1],[310,1],[325,1],[371,1],[373,1],[394,1],[420,1],[447,1],[500,1],[502,1],[517,1],[535,1],[567,1],[584,1],[602,1],[614,1],[638,1],[658,1],[695,1],[711,1],[735,1],[775,1],[789,1],[838,1],[863,1],[900,1],[919,1],[959,1],[973,1],[1007,1],[1022,1],[1049,1],[1051,1],[1063,1],[1091,1],[1118,1],[1144,1],[1160,1],[1175,1],[1177,1],[1198,1],[1226,1],[1258,1],[1260,1],[1275,1],[1309,1],[1311,1],[1323,1],[1357,1],[1359,1],[1379,1],[1402,1],[1404,1],[1419,1],[1451,1],[1490,1],[1514,1],[1555,1],[1557,1],[1577,1],[1626,1],[1647,1],[1690,1],[1712,1],[1752,1],[1770,1],[1815,1],[1832,1],[1851,1],[1869,1],[1915,1],[1941,1],[1974,1],[1988,1],[2003,1],[2027,1],[2051,1],[2070,1],[2105,1],[2121,1]]},"39":{"position":[[527,1],[775,1]]},"43":{"position":[[414,1]]},"45":{"position":[[131,1],[590,1],[602,1],[604,1],[612,1],[614,1],[624,1],[643,1],[877,1],[889,1],[902,1],[904,1],[912,1],[931,1],[965,1],[977,1],[990,1],[992,1],[1002,1],[1024,1]]},"57":{"position":[[55,1]]},"59":{"position":[[186,1],[333,1],[335,1],[354,1],[374,1],[459,2],[462,2],[465,2]]},"61":{"position":[[89,1],[124,1],[166,1]]},"63":{"position":[[173,1],[182,1],[254,2],[257,1]]},"65":{"position":[[78,1]]},"74":{"position":[[180,2]]},"80":{"position":[[137,1],[146,1],[176,1],[178,1],[180,3],[245,1],[283,1]]},"82":{"position":[[114,3],[163,3]]},"84":{"position":[[127,1],[149,2],[152,1],[266,2],[281,2],[284,1],[349,3],[372,2],[439,1],[502,1],[529,1],[553,1]]},"88":{"position":[[40,1]]},"90":{"position":[[83,1]]},"92":{"position":[[470,2]]},"102":{"position":[[265,2],[316,2]]},"106":{"position":[[16,3],[47,4]]},"108":{"position":[[30,2]]},"110":{"position":[[480,1],[486,1]]},"117":{"position":[[174,1],[271,1],[443,1],[583,1],[659,1]]},"121":{"position":[[68,3],[80,1],[157,1],[159,3]]},"126":{"position":[[697,2]]},"129":{"position":[[427,2],[454,1],[594,2],[607,1],[630,1],[669,2],[672,1],[711,2],[714,3],[2338,2],[2359,1],[2449,2],[2541,2],[2571,1],[2594,1],[2666,2],[2669,1],[2687,2],[2690,1],[2758,2],[2761,3],[3261,2],[3282,1],[3389,2],[3574,2],[3609,1],[3657,2],[3703,1],[3783,2],[3895,2],[3923,1]]},"131":{"position":[[134,1],[136,1],[138,1],[190,1],[192,1],[244,1],[246,1],[1261,2],[1280,1],[1282,2],[1301,1],[1331,2],[1397,2],[1420,1],[1514,2],[1537,1],[1539,2],[1558,1],[1674,2],[1696,1],[1698,2],[1715,1],[2427,2],[2449,1],[2451,2],[2468,1],[2639,2],[2665,1],[3617,1],[4266,1],[4275,1],[4421,2],[4443,1],[4445,2],[4462,1],[4549,1],[4990,2],[5012,1],[5014,2],[5031,1],[5141,2],[5235,2],[5263,1],[5265,2],[5284,1],[5317,1]]},"136":{"position":[[28,3]]},"140":{"position":[[0,1]]},"146":{"position":[[156,2],[180,2],[183,2],[193,2]]},"148":{"position":[[837,1],[930,1],[992,1]]},"155":{"position":[[341,1],[359,1],[434,1],[436,3],[440,1]]},"161":{"position":[[143,1],[269,1],[299,3],[361,1],[363,3],[414,1],[470,1],[524,2],[554,1],[567,1],[615,1],[617,1],[737,1],[742,1],[793,2],[796,3],[823,1],[901,2],[904,3],[908,1],[910,3],[914,1]]},"163":{"position":[[377,2],[401,2],[426,1],[460,2],[463,2],[597,2],[600,2],[623,2],[685,1],[719,2],[722,2],[849,2]]},"165":{"position":[[292,2],[350,1],[372,1],[431,1],[456,1],[458,1],[540,2],[588,1],[610,1],[670,1],[695,1],[719,1],[744,1],[746,1]]},"167":{"position":[[310,3]]},"177":{"position":[[344,2],[409,2]]},"183":{"position":[[537,1],[892,2]]},"185":{"position":[[110,1],[117,1],[229,1],[239,1],[281,1],[452,1],[457,1],[475,1],[590,1],[597,1],[730,1],[737,1],[856,3],[1044,1],[1052,1],[1085,1],[1087,1],[1089,1],[1091,1],[1101,1],[1103,1],[1105,1],[1107,1],[1114,1],[1116,1],[1121,1],[1123,1],[1130,1],[1614,1],[1795,1]]},"187":{"position":[[932,1]]},"193":{"position":[[287,1],[289,1]]},"197":{"position":[[51,1],[501,3],[505,2],[508,2],[511,5],[517,2]]},"199":{"position":[[748,1],[948,1]]},"205":{"position":[[429,2],[432,2],[435,1],[479,2],[482,2],[485,1],[487,3],[491,3],[495,3],[541,2],[544,2],[547,1],[549,3],[553,3],[680,2],[683,2],[686,1],[707,2],[710,2],[713,1],[783,2],[786,2],[789,1],[791,3],[795,2],[798,3],[802,3],[826,2],[829,2],[832,1],[901,2],[904,2],[907,1],[909,3],[913,2],[916,3],[920,3]]},"207":{"position":[[220,6],[276,3],[321,3],[325,2],[375,3],[379,2],[418,3],[422,2]]},"209":{"position":[[311,3],[342,4],[506,2],[688,2]]},"214":{"position":[[182,1]]},"216":{"position":[[82,3],[167,1],[246,3]]},"218":{"position":[[16,2],[106,2]]},"220":{"position":[[0,2],[52,1],[107,3],[186,2]]},"224":{"position":[[414,1],[496,1],[771,1],[798,2]]},"226":{"position":[[51,1]]},"228":{"position":[[118,1],[138,1],[274,1],[306,1],[324,1],[326,3],[330,2],[349,2],[411,1],[439,3],[443,3],[456,1],[470,2],[473,3],[614,1],[636,3],[640,2],[692,2],[695,2],[698,1],[725,1],[845,3],[849,2],[906,2],[909,2],[912,1],[985,3]]},"230":{"position":[[0,3],[8,2]]},"232":{"position":[[434,3],[503,2],[562,3],[566,3]]},"243":{"position":[[96,2],[247,1],[285,1],[294,1],[296,1],[347,2],[350,1],[393,2],[396,1],[439,2],[442,1],[489,2],[492,1],[550,2],[553,2],[556,2],[559,2],[615,2],[671,2],[726,2],[904,2],[942,1],[950,1],[969,1],[971,1],[999,1],[1105,1],[1178,1]]},"245":{"position":[[386,2]]},"248":{"position":[[297,2],[422,2],[447,2],[450,2],[564,1],[636,2],[670,1],[686,1],[750,1],[790,1]]},"250":{"position":[[329,2],[367,1],[369,2],[372,5],[423,1],[457,2],[460,5],[466,1]]},"252":{"position":[[308,2],[359,2],[415,2],[471,2],[526,2],[649,1],[662,1],[773,2],[786,1],[832,1]]},"258":{"position":[[112,1],[198,2],[217,2],[220,3],[224,1]]},"270":{"position":[[305,2],[318,2],[382,3],[409,3],[445,3],[481,3]]},"277":{"position":[[283,1],[313,1]]},"281":{"position":[[245,2],[271,1],[280,1],[312,1],[325,1],[392,3],[413,4],[427,3],[448,4],[453,2],[481,2]]},"283":{"position":[[89,2],[164,1],[205,1],[215,1],[220,1],[222,1]]},"285":{"position":[[0,2],[43,1],[54,1],[84,1],[103,1],[158,1],[209,2],[265,1],[347,1],[349,1]]},"287":{"position":[[0,2],[57,1],[76,1],[108,1],[139,1],[169,1],[183,1],[334,1],[391,2],[455,1],[489,1],[491,1]]},"289":{"position":[[367,2],[1024,2]]},"291":{"position":[[123,1],[139,1],[154,1],[201,1],[216,3],[220,1]]},"293":{"position":[[636,1],[721,1],[732,1],[767,1],[783,1],[785,1],[844,1],[896,2],[899,2],[918,1],[920,1],[947,1],[996,1],[1059,1],[1061,1]]},"295":{"position":[[598,1],[647,2],[678,1],[687,1],[727,1],[750,2],[753,2],[756,1],[758,1],[767,1],[776,1],[823,1],[861,1],[900,1],[930,1],[984,1],[986,1],[1078,1],[1118,1],[1155,2]]},"301":{"position":[[490,1],[521,1],[591,1],[628,1],[731,2],[748,1],[780,2],[793,1],[901,2],[918,1],[1014,2],[1031,1]]},"305":{"position":[[336,1],[349,1],[621,2],[661,1],[684,1]]},"307":{"position":[[418,2],[458,1],[474,1],[520,1],[552,1],[582,1],[595,1],[633,3],[887,1],[974,2],[1022,1],[1079,1]]},"309":{"position":[[413,2],[451,1],[508,1],[542,1],[599,1],[751,2],[789,1],[810,1],[844,1],[865,1]]},"311":{"position":[[422,2],[486,1],[540,1],[725,1],[775,2],[842,1],[857,1],[902,1],[917,2],[920,2],[936,1],[989,1],[991,1]]},"314":{"position":[[297,1],[299,1]]},"316":{"position":[[725,1],[817,1],[879,1]]},"325":{"position":[[506,2],[564,3],[568,2],[571,1],[573,2],[623,2],[626,2],[629,1],[631,2],[707,3],[711,2],[714,1],[728,1],[730,2],[733,2],[736,1],[738,2],[788,1],[790,2],[849,2],[852,2],[855,1],[857,3],[861,3],[865,3],[869,3],[873,3]]},"329":{"position":[[1444,2],[1447,2],[1450,1],[1517,2],[1520,2],[1523,1],[1537,1],[1539,2],[1542,2],[1545,1],[1547,2],[1595,2],[1608,2],[1611,2],[1614,1],[1622,1],[1624,1],[1626,1],[1637,3],[1651,2],[1654,2],[1657,1],[1665,1],[1667,1],[1669,1],[1680,3],[1684,3],[1753,2],[1756,2],[1759,1],[1773,1],[1775,2],[1778,2],[1781,1],[1783,2],[1831,2],[1844,2],[1847,2],[1850,1],[1858,1],[1860,1],[1862,1],[1873,3],[1877,3],[1881,3]]},"332":{"position":[[240,2],[260,1],[288,2],[291,2],[294,1],[334,2],[355,2],[358,2],[361,1],[376,1],[423,2],[451,2]]},"334":{"position":[[438,2],[458,1],[523,2],[526,1],[602,2],[657,3],[680,2],[740,2],[743,2],[752,2],[769,3],[803,2],[806,2],[809,1],[811,2],[842,1],[895,2]]},"336":{"position":[[120,2],[140,1],[202,2],[205,2],[208,1],[248,2],[306,2],[309,2],[312,1],[325,1],[367,2],[399,2],[465,2],[468,2],[471,1],[483,2],[552,2],[626,2],[629,2],[632,1],[721,2]]},"338":{"position":[[771,2],[774,2],[777,1],[795,1],[919,1],[983,1],[1034,3]]},"340":{"position":[[337,2],[340,2],[343,1],[358,1],[404,1],[424,1],[485,2],[488,2],[509,1],[553,1],[580,3],[599,2],[602,2],[605,1],[629,3],[646,2],[649,1],[673,2],[1330,2],[1333,2],[1336,1],[1376,2],[1379,2],[1382,1],[1396,1],[1398,2],[1401,2],[1404,1],[1423,1],[1512,1],[1525,2],[1528,2],[1563,2],[1566,2],[1569,1],[1654,3],[1707,2],[1710,2],[1713,1],[1721,1],[1734,1],[1736,1],[1760,1],[1824,3],[1828,3],[1832,3]]},"344":{"position":[[1203,1],[1231,1],[1353,2],[1356,2],[1359,1],[1387,1],[1435,1],[1508,5],[1514,3],[1518,2],[1569,1],[1605,1],[1636,1],[1669,2],[1829,2],[1832,2],[1835,1],[1863,3]]},"358":{"position":[[401,1],[419,1],[434,1],[449,1],[493,1],[540,1],[542,3],[619,1],[637,1],[652,1],[667,1],[711,1],[758,1],[760,3]]},"360":{"position":[[470,1],[496,1],[509,1],[551,1],[609,1],[671,1],[737,1],[781,1],[844,1],[870,1],[883,1],[925,1],[983,1],[1045,1],[1111,1],[1155,1]]},"362":{"position":[[157,1],[185,1],[198,1],[261,1],[289,1],[302,1],[412,1],[462,1],[482,4],[487,1],[537,1],[592,1],[607,1],[618,2],[630,1],[632,6],[651,1],[663,1]]},"370":{"position":[[52,1]]},"373":{"position":[[298,1],[317,1],[327,1],[339,1]]},"375":{"position":[[313,1],[402,1],[459,1],[480,1],[533,1],[567,1],[632,1],[702,1],[742,1],[782,1],[819,1],[858,1],[905,1],[954,1],[1003,1],[1042,1],[1078,1],[1114,1],[1146,1],[1172,1],[1202,1],[1232,1],[1259,1],[1286,1],[1323,1],[1357,1],[1393,1],[1446,1]]},"390":{"position":[[147,2]]},"392":{"position":[[220,1]]},"407":{"position":[[614,1]]},"455":{"position":[[140,2],[233,1],[288,2],[304,1],[339,2],[394,2],[422,1],[435,1],[537,2],[647,3]]},"457":{"position":[[179,1],[239,2],[262,1]]},"459":{"position":[[90,1],[116,1]]},"463":{"position":[[164,1],[228,2],[231,1]]},"473":{"position":[[353,2],[1733,1],[1748,1],[1776,1],[1824,1],[1845,1],[2466,1],[2502,1],[2529,1],[2576,1],[2596,1],[2619,1],[2644,1],[2654,3],[2658,1],[2660,1],[2678,1],[2702,1],[2738,1],[2769,1],[2817,1],[2838,1],[2881,1],[2919,1],[2944,1],[2968,1],[2970,1],[3171,1],[3217,2],[3220,1],[3391,1],[3418,1],[3465,1],[3614,1],[3637,1],[3662,1],[3672,3],[3676,1],[3678,1],[3696,1],[3720,1],[3756,1],[3787,1],[3835,1],[3856,1],[3899,1],[3937,1],[3962,1],[3986,1],[3988,1],[4217,1],[4330,1],[4357,1],[4404,1],[4495,1],[4518,1],[4543,1],[4553,3],[4557,1],[4559,1],[4587,1],[4611,1],[4647,1],[4678,1],[4726,1],[4747,1],[4790,1],[4828,1],[4853,1],[4877,1],[4879,1]]},"480":{"position":[[403,1],[496,1],[538,1],[551,2],[554,1]]},"486":{"position":[[1676,1],[1754,1]]},"505":{"position":[[689,1]]},"512":{"position":[[111,1]]},"517":{"position":[[16,1],[169,1],[223,1]]},"521":{"position":[[690,2]]},"524":{"position":[[246,1]]},"531":{"position":[[225,1]]},"541":{"position":[[760,2],[790,2],[859,2],[887,2]]},"543":{"position":[[215,2],[248,2]]},"551":{"position":[[335,1],[337,2],[340,3],[383,1],[414,2],[447,2],[529,1],[556,1],[576,1],[602,1],[604,2],[607,3],[634,1],[663,2],[666,2],[737,1]]},"553":{"position":[[28,1],[106,1],[137,1],[158,1],[212,1],[287,1]]},"555":{"position":[[36,1],[38,5],[63,1]]},"558":{"position":[[334,1],[356,2],[359,3],[363,1],[412,1],[434,1],[456,1],[458,1]]},"561":{"position":[[105,1],[107,2],[138,2],[160,2],[197,1],[212,2],[215,1],[217,2],[220,3],[224,1],[226,2],[271,1],[273,2],[276,3],[280,1],[282,2],[301,1]]}}}],["0",{"_index":2073,"t":{"373":{"position":[[329,2]]},"473":{"position":[[2983,2],[4001,2],[4892,2]]}}}],["0.12",{"_index":777,"t":{"117":{"position":[[436,6]]}}}],["0000d231816abba584714c9",{"_index":1726,"t":{"295":{"position":[[1128,26]]}}}],["02",{"_index":483,"t":{"53":{"position":[[27,2],[57,2]]},"167":{"position":[[410,2]]}}}],["04",{"_index":723,"t":{"106":{"position":[[453,2]]},"209":{"position":[[292,2]]}}}],["05",{"_index":1723,"t":{"295":{"position":[[1050,2]]}}}],["0a7ac9",{"_index":787,"t":{"121":{"position":[[88,10]]}}}],["0f3551",{"_index":790,"t":{"121":{"position":[[146,10]]}}}],["1",{"_index":49,"t":{"7":{"position":[[218,2],[331,2]]},"19":{"position":[[667,2]]},"53":{"position":[[175,1]]},"115":{"position":[[128,2],[169,2],[553,3]]},"117":{"position":[[229,4]]},"129":{"position":[[647,3],[2611,3]]},"131":{"position":[[144,2],[159,3],[3372,1],[3734,1]]},"148":{"position":[[294,1]]},"275":{"position":[[352,2]]},"344":{"position":[[1422,3]]},"443":{"position":[[314,1]]},"505":{"position":[[3043,2]]},"561":{"position":[[110,2]]}}}],["1.1",{"_index":1944,"t":{"344":{"position":[[948,3],[1473,5]]}}}],["1.2",{"_index":1948,"t":{"344":{"position":[[1100,3],[1544,5]]}}}],["10",{"_index":728,"t":{"108":{"position":[[174,2]]},"131":{"position":[[4781,2]]},"373":{"position":[[242,2]]}}}],["10000",{"_index":1872,"t":{"334":{"position":[[746,5]]}}}],["10px",{"_index":619,"t":{"84":{"position":[[239,7]]}}}],["11",{"_index":722,"t":{"106":{"position":[[450,2]]},"165":{"position":[[868,2]]},"209":{"position":[[289,2]]}}}],["12",{"_index":1181,"t":{"167":{"position":[[407,2]]}}}],["1234",{"_index":1040,"t":{"146":{"position":[[223,4],[324,5]]},"314":{"position":[[341,5]]}}}],["127.0.0.1",{"_index":2323,"t":{"473":{"position":[[2544,9],[2554,11],[2994,9],[3433,9],[3443,11],[4012,9],[4372,9],[4382,11],[4903,9]]}}}],["127.0.0.1:9001",{"_index":2322,"t":{"473":{"position":[[2511,17],[2604,14],[3400,17],[3622,14],[4339,17],[4503,14]]}}}],["13:27:00",{"_index":2346,"t":{"473":{"position":[[2906,8]]}}}],["13:39:44",{"_index":2369,"t":{"473":{"position":[[4815,8]]}}}],["13:40:05",{"_index":2359,"t":{"473":{"position":[[3924,8]]}}}],["15:20:30.888",{"_index":1725,"t":{"295":{"position":[[1056,12]]}}}],["172.29.173.128",{"_index":2078,"t":{"375":{"position":[[173,16]]}}}],["18",{"_index":1496,"t":{"245":{"position":[[56,2]]},"392":{"position":[[56,2]]}}}],["1993",{"_index":1061,"t":{"148":{"position":[[533,4]]},"316":{"position":[[421,4]]}}}],["1add",{"_index":185,"t":{"15":{"position":[[798,11]]}}}],["button>button",{"_index":822,"t":{"126":{"position":[[652,13]]}}}],["button>mybuttonopt",{"_index":832,"t":{"129":{"position":[[171,13],[214,13]]}}}],["button>two",{"_index":819,"t":{"126":{"position":[[607,10]]}}}],["byte",{"_index":2424,"t":{"486":{"position":[[1039,5]]}}}],["c6f6",{"_index":2442,"t":{"486":{"position":[[1707,4],[1798,4]]}}}],["cach",{"_index":1506,"t":{"245":{"position":[[380,5]]}}}],["calcul",{"_index":2540,"t":{"514":{"position":[[1160,9]]},"535":{"position":[[572,13]]}}}],["calendar",{"_index":1234,"t":{"185":{"position":[[241,8]]}}}],["call",{"_index":557,"t":{"67":{"position":[[44,6]]},"74":{"position":[[44,6]]},"151":{"position":[[282,6]]},"211":{"position":[[118,6]]},"228":{"position":[[675,4]]},"241":{"position":[[265,5],[367,5]]},"295":{"position":[[281,6]]},"325":{"position":[[591,6]]},"329":{"position":[[139,4],[810,4],[1003,4],[1223,6]]},"332":{"position":[[59,4]]},"340":{"position":[[1037,6],[1542,4]]},"342":{"position":[[211,5]]},"344":{"position":[[2460,6]]},"352":{"position":[[309,4]]},"421":{"position":[[488,5]]},"473":{"position":[[240,6],[390,6],[613,6],[3132,4]]},"491":{"position":[[140,5]]},"495":{"position":[[207,7]]},"498":{"position":[[226,7],[345,6]]}}}],["callback",{"_index":1866,"t":{"334":{"position":[[330,9]]}}}],["callback1",{"_index":839,"t":{"129":{"position":[[659,9],[2623,10]]}}}],["callback2",{"_index":840,"t":{"129":{"position":[[701,9],[2719,10]]}}}],["calld",{"_index":1918,"t":{"340":{"position":[[890,5]]}}}],["callout",{"_index":603,"t":{"82":{"position":[[58,9]]}}}],["camelcas",{"_index":160,"t":{"15":{"position":[[372,11],[468,11]]},"177":{"position":[[215,9],[283,9],[372,9]]},"563":{"position":[[27,10]]}}}],["cant",{"_index":2658,"t":{"541":{"position":[[1088,4]]}}}],["canva",{"_index":2478,"t":{"505":{"position":[[340,6],[443,6],[2040,6],[2287,6],[2856,6],[2914,6],[3107,7],[3385,7],[3412,6],[3485,6],[3616,6],[3919,6],[4204,7]]},"507":{"position":[[325,6]]},"509":{"position":[[226,6]]}}}],["canvas",{"_index":2492,"t":{"505":{"position":[[1849,8],[2666,8],[4742,9]]}}}],["capabl",{"_index":818,"t":{"126":{"position":[[589,13]]},"426":{"position":[[187,7]]}}}],["captur",{"_index":143,"t":{"13":{"position":[[561,8]]},"39":{"position":[[543,8]]}}}],["card",{"_index":179,"t":{"15":{"position":[[699,4]]},"119":{"position":[[242,6]]},"243":{"position":[[89,6]]},"488":{"position":[[138,4],[335,5]]},"495":{"position":[[82,5]]},"526":{"position":[[51,4]]}}}],["cardurlparam",{"_index":1479,"t":{"243":{"position":[[796,14]]}}}],["care",{"_index":608,"t":{"82":{"position":[[133,4],[211,4]]},"106":{"position":[[266,7]]},"126":{"position":[[256,8]]},"268":{"position":[[190,4]]},"289":{"position":[[461,5]]},"314":{"position":[[377,8]]},"336":{"position":[[102,4]]},"344":{"position":[[134,4]]},"390":{"position":[[16,4]]}}}],["carefulli",{"_index":971,"t":{"131":{"position":[[3793,10]]},"344":{"position":[[181,10]]}}}],["case",{"_index":67,"t":{"9":{"position":[[199,5],[1146,4]]},"11":{"position":[[374,5]]},"123":{"position":[[115,4]]},"148":{"position":[[115,4]]},"174":{"position":[[492,4]]},"185":{"position":[[1542,4]]},"197":{"position":[[32,5],[98,5],[205,5]]},"279":{"position":[[338,5]]},"301":{"position":[[275,6]]},"305":{"position":[[93,6]]},"314":{"position":[[423,4]]},"323":{"position":[[206,4]]},"329":{"position":[[410,4],[418,4],[563,5]]},"334":{"position":[[268,4]]},"340":{"position":[[1244,5]]},"346":{"position":[[187,6]]},"352":{"position":[[491,6]]},"473":{"position":[[4104,4]]},"558":{"position":[[8,6]]}}}],["catch",{"_index":1679,"t":{"291":{"position":[[7,5]]},"334":{"position":[[206,5],[619,14]]},"414":{"position":[[83,5]]}}}],["catch(error",{"_index":1681,"t":{"291":{"position":[[141,12]]}}}],["categor",{"_index":403,"t":{"41":{"position":[[88,10]]}}}],["categori",{"_index":532,"t":{"59":{"position":[[382,11]]}}}],["caus",{"_index":1189,"t":{"170":{"position":[[424,6]]},"174":{"position":[[233,5]]},"291":{"position":[[73,5],[203,6]]},"486":{"position":[[235,5]]}}}],["cd",{"_index":267,"t":{"27":{"position":[[81,2]]},"245":{"position":[[143,2]]},"392":{"position":[[171,2]]},"473":{"position":[[356,2]]}}}],["cell",{"_index":978,"t":{"131":{"position":[[4831,4],[5110,4]]}}}],["central",{"_index":400,"t":{"39":{"position":[[870,7]]},"183":{"position":[[787,7]]},"279":{"position":[[667,7]]},"521":{"position":[[68,7],[215,11]]}}}],["certain",{"_index":32,"t":{"5":{"position":[[192,7]]},"131":{"position":[[1988,7]]},"153":{"position":[[40,7]]},"155":{"position":[[65,7]]},"199":{"position":[[1142,7]]},"205":{"position":[[1022,7]]},"209":{"position":[[102,7]]},"224":{"position":[[176,7],[219,7]]},"521":{"position":[[652,7]]},"549":{"position":[[357,7]]}}}],["chain",{"_index":1865,"t":{"334":{"position":[[134,7],[300,7],[402,6]]}}}],["challeng",{"_index":990,"t":{"133":{"position":[[111,10]]}}}],["chang",{"_index":454,"t":{"47":{"position":[[61,7]]},"146":{"position":[[5,6]]},"148":{"position":[[126,7],[647,7],[749,6],[780,6],[870,7],[967,7]]},"159":{"position":[[48,8]]},"207":{"position":[[250,7]]},"245":{"position":[[261,6]]},"268":{"position":[[401,7]]},"275":{"position":[[169,8],[237,7]]},"279":{"position":[[630,8]]},"314":{"position":[[5,6]]},"316":{"position":[[140,7],[535,7],[637,6],[668,6],[757,7],[854,7]]},"323":{"position":[[105,7]]},"368":{"position":[[34,7],[232,7],[327,7]]},"395":{"position":[[18,6]]},"397":{"position":[[212,7]]},"407":{"position":[[308,7],[597,8]]},"418":{"position":[[264,8],[328,7],[443,7]]},"491":{"position":[[484,7]]},"493":{"position":[[35,7]]},"505":{"position":[[200,7],[492,7],[562,7],[1010,7],[1033,6],[1088,6],[1143,7],[1368,7],[1407,7],[1938,8],[2519,7],[3132,7],[3174,7],[3251,7],[4122,7]]},"509":{"position":[[142,7]]},"514":{"position":[[1023,7],[1195,8]]},"521":{"position":[[433,7],[609,7],[913,7],[1030,7]]},"526":{"position":[[220,6]]},"529":{"position":[[108,7],[128,6],[385,8],[438,6],[532,7]]},"531":{"position":[[368,6],[1305,6]]}}}],["channel",{"_index":2462,"t":{"491":{"position":[[428,7]]},"493":{"position":[[280,7],[394,7],[497,8]]},"505":{"position":[[1336,9]]},"514":{"position":[[621,9]]},"521":{"position":[[1515,7]]}}}],["chapter",{"_index":695,"t":{"102":{"position":[[209,7]]},"535":{"position":[[1154,7]]}}}],["charact",{"_index":708,"t":{"106":{"position":[[128,10]]},"197":{"position":[[490,10]]},"209":{"position":[[423,10]]}}}],["charset=utf",{"_index":2335,"t":{"473":{"position":[[2803,11],[3821,11],[4712,11]]}}}],["chat",{"_index":1524,"t":{"250":{"position":[[416,6],[579,7]]},"373":{"position":[[162,4]]},"375":{"position":[[300,4]]}}}],["check",{"_index":1350,"t":{"209":{"position":[[94,5],[139,5],[488,5]]},"214":{"position":[[236,5]]},"277":{"position":[[331,6]]},"329":{"position":[[183,5]]},"344":{"position":[[60,5]]},"348":{"position":[[270,7]]},"375":{"position":[[0,6]]},"405":{"position":[[3,5],[294,5]]},"488":{"position":[[598,6],[649,8]]},"491":{"position":[[185,5]]},"549":{"position":[[318,8],[472,11],[550,10],[564,8],[744,8]]}}}],["checkbox",{"_index":1054,"t":{"148":{"position":[[331,8]]}}}],["checkinputisvalid",{"_index":2673,"t":{"549":{"position":[[503,19]]}}}],["checkpermiss",{"_index":2672,"t":{"549":{"position":[[484,18]]}}}],["child",{"_index":1369,"t":{"214":{"position":[[294,5],[340,5]]}}}],["childcompon",{"_index":1306,"t":{"199":{"position":[[479,15]]}}}],["children",{"_index":611,"t":{"84":{"position":[[129,11],[335,13],[353,10]]},"131":{"position":[[2887,8],[3830,8]]},"133":{"position":[[524,8]]}}}],["choos",{"_index":251,"t":{"25":{"position":[[177,6]]},"151":{"position":[[57,7]]},"327":{"position":[[442,6]]},"505":{"position":[[3727,7]]}}}],["chosen",{"_index":1081,"t":{"151":{"position":[[240,6]]}}}],["chunk",{"_index":2174,"t":{"412":{"position":[[736,6]]}}}],["chunkwis",{"_index":2167,"t":{"412":{"position":[[372,9]]}}}],["ci",{"_index":307,"t":{"31":{"position":[[77,2]]},"37":{"position":[[1487,2]]},"43":{"position":[[294,2],[523,3]]},"138":{"position":[[117,2]]},"245":{"position":[[168,2]]},"392":{"position":[[196,2]]},"405":{"position":[[205,2],[247,2]]}}}],["ci/cd",{"_index":390,"t":{"39":{"position":[[19,5],[735,5]]}}}],["circl",{"_index":1191,"t":{"172":{"position":[[29,6]]},"174":{"position":[[343,7]]}}}],["circular",{"_index":638,"t":{"88":{"position":[[31,8]]},"90":{"position":[[56,8]]},"92":{"position":[[95,8],[293,8]]},"94":{"position":[[87,8]]},"170":{"position":[[0,8],[304,8]]},"174":{"position":[[246,8],[571,8]]},"309":{"position":[[0,8],[201,8],[267,8],[649,8],[918,8],[1288,8]]}}}],["claim",{"_index":2436,"t":{"486":{"position":[[1431,7],[1503,5]]}}}],["clariti",{"_index":53,"t":{"7":{"position":[[411,8]]},"9":{"position":[[1382,8]]}}}],["class",{"_index":78,"t":{"9":{"position":[[415,5],[463,7],[564,5],[630,5],[665,5],[741,5],[770,5]]},"13":{"position":[[228,5],[305,5],[412,5]]},"15":{"position":[[232,5],[344,7]]},"17":{"position":[[117,6],[188,5]]},"115":{"position":[[69,7]]},"157":{"position":[[42,7],[164,7]]},"161":{"position":[[318,5]]},"199":{"position":[[653,6],[722,6]]},"224":{"position":[[31,7]]},"248":{"position":[[432,5],[533,5]]},"256":{"position":[[245,6]]},"258":{"position":[[89,5]]},"260":{"position":[[60,6],[270,5]]},"262":{"position":[[323,5]]},"277":{"position":[[69,6]]},"281":{"position":[[463,5]]},"283":{"position":[[33,6],[141,5]]},"285":{"position":[[144,5]]},"287":{"position":[[258,5]]},"293":{"position":[[550,5],[929,5]]},"295":{"position":[[559,5],[810,5]]},"311":{"position":[[678,5]]},"338":{"position":[[293,5],[403,5]]},"340":{"position":[[733,6]]},"455":{"position":[[150,5]]},"457":{"position":[[161,5]]},"459":{"position":[[64,5]]},"463":{"position":[[140,5]]},"470":{"position":[[66,7]]},"535":{"position":[[553,5],[625,7],[715,8]]},"541":{"position":[[770,5],[869,5]]},"543":{"position":[[225,5]]},"545":{"position":[[482,5]]},"558":{"position":[[365,5]]},"561":{"position":[[0,7],[92,5]]},"563":{"position":[[0,7]]}}}],["class=\"bg",{"_index":761,"t":{"115":{"position":[[289,9]]}}}],["class=\"text",{"_index":763,"t":{"115":{"position":[[392,11],[530,11]]}}}],["classnam",{"_index":84,"t":{"9":{"position":[[642,13],[671,13],[747,13],[776,13],[832,9],[1003,12]]},"13":{"position":[[251,9],[352,9],[452,9]]}}}],["clean",{"_index":981,"t":{"131":{"position":[[4928,5]]},"316":{"position":[[84,5]]},"541":{"position":[[181,5]]}}}],["cleanup",{"_index":1940,"t":{"344":{"position":[[374,7]]}}}],["clear",{"_index":1505,"t":{"245":{"position":[[374,5]]},"495":{"position":[[491,5]]},"551":{"position":[[196,5]]}}}],["clearli",{"_index":1673,"t":{"289":{"position":[[822,7]]},"323":{"position":[[229,7]]},"344":{"position":[[362,7]]},"541":{"position":[[1308,7]]},"549":{"position":[[30,7]]}}}],["cli",{"_index":2045,"t":{"366":{"position":[[126,3]]},"368":{"position":[[136,3]]},"395":{"position":[[328,3]]}}}],["click",{"_index":624,"t":{"84":{"position":[[297,7]]},"205":{"position":[[469,9],[891,9]]},"207":{"position":[[268,7]]},"214":{"position":[[92,6]]},"426":{"position":[[14,6]]},"488":{"position":[[251,6]]},"526":{"position":[[103,5]]}}}],["click=\"callback1",{"_index":889,"t":{"129":{"position":[[3310,18]]}}}],["click=\"callback1\">opt",{"_index":854,"t":{"129":{"position":[[1080,25],[2387,25]]}}}],["click=\"callback2",{"_index":879,"t":{"129":{"position":[[2483,19]]}}}],["click=\"callback2\">opt",{"_index":855,"t":{"129":{"position":[[1141,25],[3423,25]]}}}],["click=\"callback3\">opt",{"_index":894,"t":{"129":{"position":[[3721,25]]}}}],["click=\"callback4\">opt",{"_index":896,"t":{"129":{"position":[[3802,25]]}}}],["click=\"emit('delete')\">delete{{option.label}}facebook",{"_index":627,"t":{"84":{"position":[[460,24]]}}}],["color=\"#25c2a0\">docusauru",{"_index":625,"t":{"84":{"position":[[394,26]]}}}],["color=\"'r",{"_index":878,"t":{"129":{"position":[[2468,14],[3408,14]]}}}],["colors.grey.darken3",{"_index":789,"t":{"121":{"position":[[111,20]]}}}],["column",{"_index":977,"t":{"131":{"position":[[4784,7]]},"421":{"position":[[164,6]]},"424":{"position":[[68,6]]},"426":{"position":[[353,6]]},"428":{"position":[[151,6]]},"526":{"position":[[22,7]]}}}],["columnboard",{"_index":2448,"t":{"488":{"position":[[59,12],[99,11],[270,11]]},"495":{"position":[[28,11]]}}}],["combin",{"_index":1620,"t":{"277":{"position":[[755,7],[792,8]]},"329":{"position":[[318,11]]},"340":{"position":[[814,8]]},"366":{"position":[[307,12]]},"503":{"position":[[187,11]]},"509":{"position":[[244,11]]},"531":{"position":[[1348,9]]}}}],["come",{"_index":926,"t":{"131":{"position":[[1019,4]]},"185":{"position":[[1996,5]]}}}],["command",{"_index":304,"t":{"31":{"position":[[18,7]]},"37":{"position":[[752,8],[936,8]]},"39":{"position":[[473,9]]},"86":{"position":[[34,7]]},"358":{"position":[[85,8],[272,8]]},"360":{"position":[[134,8]]},"368":{"position":[[289,7],[834,8]]},"370":{"position":[[24,8],[59,7]]},"375":{"position":[[269,8]]},"395":{"position":[[332,8]]},"399":{"position":[[41,8],[192,7]]},"403":{"position":[[168,7]]},"405":{"position":[[101,7],[175,7]]},"416":{"position":[[280,7]]},"473":{"position":[[325,8],[1672,8],[1956,7]]},"480":{"position":[[56,8]]},"514":{"position":[[593,8]]}}}],["commands.j",{"_index":348,"t":{"37":{"position":[[907,11]]}}}],["comment",{"_index":216,"t":{"19":{"position":[[587,7],[1033,9]]},"104":{"position":[[40,8]]},"148":{"position":[[994,8]]},"177":{"position":[[74,7]]},"185":{"position":[[89,7]]},"275":{"position":[[443,8]]},"316":{"position":[[230,9],[881,8]]}}}],["commit",{"_index":1056,"t":{"148":{"position":[[398,6],[479,6]]},"316":{"position":[[9,6],[109,7],[173,7],[264,6]]},"368":{"position":[[247,6]]},"407":{"position":[[357,6],[586,6],[620,6]]}}}],["common",{"_index":215,"t":{"19":{"position":[[447,6],[603,6],[910,6],[1047,6]]},"170":{"position":[[25,6],[97,6]]},"268":{"position":[[153,6]]},"567":{"position":[[159,6]]}}}],["common_login",{"_index":149,"t":{"15":{"position":[[62,13]]}}}],["commoncoursesteps.spec.j",{"_index":211,"t":{"19":{"position":[[292,25],[937,25],[1088,25]]}}}],["commun",{"_index":476,"t":{"49":{"position":[[258,9]]},"234":{"position":[[467,13]]},"309":{"position":[[971,11]]},"434":{"position":[[8,13]]},"436":{"position":[[129,14]]},"514":{"position":[[861,14]]},"549":{"position":[[38,11]]}}}],["compar",{"_index":812,"t":{"126":{"position":[[462,7]]},"129":{"position":[[2303,7]]},"131":{"position":[[3490,13]]},"148":{"position":[[975,8]]},"316":{"position":[[862,8]]},"403":{"position":[[181,7]]}}}],["compass",{"_index":1965,"t":{"344":{"position":[[2426,8]]}}}],["compat",{"_index":2330,"t":{"473":{"position":[[2709,11],[3727,11],[4618,11]]}}}],["compent",{"_index":1435,"t":{"234":{"position":[[58,8]]}}}],["compil",{"_index":1187,"t":{"170":{"position":[[355,8]]},"172":{"position":[[119,8]]},"338":{"position":[[604,9],[891,13]]},"340":{"position":[[491,13]]},"344":{"position":[[1550,13]]},"399":{"position":[[169,8]]},"405":{"position":[[146,8]]}}}],["complementari",{"_index":2498,"t":{"505":{"position":[[2444,13]]}}}],["complet",{"_index":310,"t":{"33":{"position":[[18,9]]},"126":{"position":[[450,11]]},"129":{"position":[[2868,10]]},"131":{"position":[[3469,10]]},"340":{"position":[[786,10]]}}}],["complex",{"_index":194,"t":{"17":{"position":[[135,7]]},"129":{"position":[[2841,7]]},"131":{"position":[[27,7],[1902,8],[2008,10]]},"185":{"position":[[250,7]]},"211":{"position":[[63,7]]},"256":{"position":[[164,7]]},"260":{"position":[[0,7]]},"277":{"position":[[546,7]]},"323":{"position":[[177,7]]},"461":{"position":[[585,7]]},"503":{"position":[[341,7]]},"535":{"position":[[604,7]]}}}],["complic",{"_index":874,"t":{"129":{"position":[[2247,11]]}}}],["compon",{"_index":326,"t":{"35":{"position":[[221,10]]},"84":{"position":[[76,10]]},"102":{"position":[[23,10],[358,10]]},"106":{"position":[[325,9]]},"110":{"position":[[84,11]]},"112":{"position":[[53,9]]},"119":{"position":[[226,10],[386,9]]},"126":{"position":[[24,9]]},"129":{"position":[[1313,11],[1920,10]]},"131":{"position":[[834,10],[988,9],[1008,10],[1103,9],[1170,9],[1857,10],[2276,9],[2866,10],[2916,10],[3443,9],[4357,11],[5380,10],[5529,9]]},"133":{"position":[[24,10],[58,10],[180,10],[369,9],[545,9],[684,10],[818,9],[1097,10],[1183,10],[1358,9]]},"151":{"position":[[569,10]]},"163":{"position":[[449,10],[708,10]]},"167":{"position":[[160,10]]},"177":{"position":[[82,10]]},"183":{"position":[[83,11],[766,13]]},"185":{"position":[[289,11],[391,10],[492,10],[654,11],[705,11],[1230,9],[1578,10],[1622,10],[1812,10],[1963,10],[2111,11]]},"187":{"position":[[812,10],[867,10],[910,10],[1054,10]]},"195":{"position":[[157,9],[174,9],[279,10],[347,9],[443,10]]},"197":{"position":[[165,9],[233,9],[276,10]]},"199":{"position":[[20,10],[145,11],[213,11],[374,9],[451,9],[543,9],[600,9],[635,9],[703,10],[830,10],[928,9],[1085,10],[1166,10]]},"201":{"position":[[24,10]]},"203":{"position":[[34,9]]},"205":{"position":[[591,9],[979,9]]},"209":{"position":[[851,9]]},"214":{"position":[[300,10],[346,9]]},"228":{"position":[[26,10]]},"232":{"position":[[217,9]]},"234":{"position":[[192,9],[219,10],[408,9],[630,10],[663,11]]},"325":{"position":[[356,9]]},"338":{"position":[[257,10]]},"342":{"position":[[42,9]]},"348":{"position":[[84,10]]},"352":{"position":[[79,10],[523,10]]},"517":{"position":[[242,9]]},"529":{"position":[[214,10],[264,11]]}}}],["componenta",{"_index":1193,"t":{"174":{"position":[[69,10],[537,11]]}}}],["componentb",{"_index":1197,"t":{"174":{"position":[[320,10],[472,11]]}}}],["components/featur",{"_index":1243,"t":{"185":{"position":[[836,19]]}}}],["compos",{"_index":704,"t":{"104":{"position":[[49,11]]},"110":{"position":[[0,11],[119,11],[261,11],[488,12]]},"165":{"position":[[25,10]]},"177":{"position":[[203,11]]},"185":{"position":[[2417,12]]},"195":{"position":[[133,11]]},"222":{"position":[[23,11]]},"232":{"position":[[17,10],[270,11],[385,10],[531,10]]},"234":{"position":[[293,11],[323,10],[535,12]]}}}],["composable.t",{"_index":736,"t":{"110":{"position":[[302,14]]}}}],["composables/appl",{"_index":1166,"t":{"165":{"position":[[379,26],[617,26]]}}}],["composed_valu",{"_index":1580,"t":{"270":{"position":[[510,15]]}}}],["composit",{"_index":735,"t":{"110":{"position":[[224,11]]}}}],["comput",{"_index":2675,"t":{"551":{"position":[[44,12],[107,11],[173,11]]}}}],["concept",{"_index":1225,"t":{"183":{"position":[[501,8]]},"303":{"position":[[97,8]]},"395":{"position":[[149,7]]}}}],["concern",{"_index":943,"t":{"131":{"position":[[1970,9]]},"183":{"position":[[420,7]]},"531":{"position":[[241,8],[1204,7]]},"541":{"position":[[107,9],[1333,7]]},"543":{"position":[[494,8]]}}}],["condit",{"_index":1613,"t":{"277":{"position":[[370,10],[570,11]]},"325":{"position":[[393,9]]},"334":{"position":[[168,10]]},"344":{"position":[[1677,10]]},"500":{"position":[[252,10]]},"549":{"position":[[577,9],[755,10]]}}}],["confid",{"_index":1275,"t":{"193":{"position":[[66,11]]}}}],["config",{"_index":871,"t":{"129":{"position":[[2148,6],[2197,6]]},"148":{"position":[[1161,6]]},"268":{"position":[[494,6],[543,6]]},"272":{"position":[[664,6],[804,6]]},"318":{"position":[[72,6]]},"403":{"position":[[352,6]]},"416":{"position":[[64,6]]}}}],["config.controller.t",{"_index":2524,"t":{"514":{"position":[[7,20]]}}}],["config.json",{"_index":2019,"t":{"360":{"position":[[596,12],[658,12],[724,12],[970,12],[1032,12],[1098,12]]},"362":{"position":[[688,12]]}}}],["configmap",{"_index":1562,"t":{"266":{"position":[[207,10]]},"270":{"position":[[112,9],[241,9],[284,9]]}}}],["configmap.yml.j2",{"_index":1573,"t":{"270":{"position":[[202,17]]}}}],["configuar",{"_index":2309,"t":{"473":{"position":[[1541,14]]}}}],["configur",{"_index":316,"t":{"33":{"position":[[243,14]]},"37":{"position":[[945,13],[992,14],[1586,13],[1781,13],[1951,13]]},"39":{"position":[[218,13],[610,15],[792,13],[878,13]]},"49":{"position":[[49,13]]},"155":{"position":[[188,13]]},"172":{"position":[[101,13]]},"211":{"position":[[168,12]]},"266":{"position":[[4,13],[231,13],[317,14],[340,13]]},"268":{"position":[[56,13],[209,13],[304,13],[369,13],[387,13]]},"272":{"position":[[11,13],[178,13],[690,14]]},"370":{"position":[[175,10]]},"377":{"position":[[14,9]]},"403":{"position":[[270,13]]},"441":{"position":[[506,13]]},"443":{"position":[[120,13]]},"473":{"position":[[1511,14]]},"514":{"position":[[68,13],[426,15],[702,13]]},"521":{"position":[[157,13]]},"524":{"position":[[348,14]]},"553":{"position":[[198,13],[271,15]]}}}],["confirmationdialog.composable.t",{"_index":1207,"t":{"177":{"position":[[225,32]]}}}],["conflict",{"_index":1599,"t":{"275":{"position":[[123,9]]},"505":{"position":[[117,9]]}}}],["congratul",{"_index":500,"t":{"53":{"position":[[408,16]]}}}],["conjunct",{"_index":2025,"t":{"362":{"position":[[35,11]]}}}],["connect",{"_index":509,"t":{"55":{"position":[[30,9]]},"98":{"position":[[135,11]]},"356":{"position":[[129,8]]},"360":{"position":[[52,7]]},"403":{"position":[[367,7]]},"473":{"position":[[1171,10],[1356,7],[2531,9],[2921,11],[2972,10],[3420,9],[3939,11],[3990,10],[4359,9],[4830,11],[4881,10]]},"486":{"position":[[429,10],[1173,10],[1236,10]]},"488":{"position":[[547,10],[872,9]]},"491":{"position":[[39,8],[114,11],[246,10],[363,9]]},"493":{"position":[[78,11],[296,9],[424,9]]},"495":{"position":[[327,9]]},"498":{"position":[[437,10]]},"505":{"position":[[1108,9]]},"507":{"position":[[272,9]]},"521":{"position":[[189,12]]}}}],["consid",{"_index":732,"t":{"110":{"position":[[131,8]]},"187":{"position":[[968,8],[1160,8],[1336,8]]},"234":{"position":[[168,9]]},"268":{"position":[[335,8]]},"281":{"position":[[0,8]]},"301":{"position":[[74,11]]},"309":{"position":[[1000,8]]},"410":{"position":[[0,8],[280,8]]}}}],["consider",{"_index":2663,"t":{"545":{"position":[[202,14]]}}}],["consist",{"_index":16,"t":{"2":{"position":[[179,11]]},"19":{"position":[[423,12]]},"21":{"position":[[4,10]]},"37":{"position":[[1604,10]]},"39":{"position":[[155,10],[823,10]]},"96":{"position":[[15,8]]},"131":{"position":[[861,8]]},"133":{"position":[[411,8]]},"157":{"position":[[405,11]]},"177":{"position":[[16,12]]},"266":{"position":[[29,8]]},"272":{"position":[[735,10]]},"505":{"position":[[247,12],[1229,12]]},"512":{"position":[[73,8]]}}}],["consol",{"_index":1194,"t":{"174":{"position":[[113,8]]},"373":{"position":[[259,7]]},"476":{"position":[[71,7]]},"478":{"position":[[10,7]]}}}],["console.log",{"_index":2179,"t":{"416":{"position":[[117,11]]}}}],["consoleerrorspi",{"_index":1382,"t":{"220":{"position":[[36,15]]}}}],["consoleerrorspy.mockrestor",{"_index":1388,"t":{"220":{"position":[[189,30]]}}}],["const",{"_index":610,"t":{"84":{"position":[[111,5]]},"131":{"position":[[114,5]]},"161":{"position":[[569,5],[800,5]]},"165":{"position":[[425,5],[713,5]]},"216":{"position":[[153,5]]},"220":{"position":[[30,5]]},"224":{"position":[[403,5]]},"228":{"position":[[597,5],[700,5]]},"243":{"position":[[944,5],[985,5],[1090,5]]},"248":{"position":[[672,5],[737,5]]},"252":{"position":[[651,5],[776,5]]},"293":{"position":[[769,5]]},"311":{"position":[[844,5],[923,5]]},"325":{"position":[[716,5]]},"329":{"position":[[1525,5],[1616,5],[1659,5],[1761,5],[1852,5]]},"332":{"position":[[363,5]]},"334":{"position":[[829,5]]},"336":{"position":[[314,5]]},"338":{"position":[[779,5]]},"340":{"position":[[345,5],[1384,5],[1406,5],[1715,5],[1747,5]]},"551":{"position":[[360,5],[731,5]]},"553":{"position":[[139,5],[192,5]]}}}],["constantli",{"_index":2497,"t":{"505":{"position":[[2344,10]]}}}],["constructor",{"_index":2699,"t":{"561":{"position":[[56,11],[166,11]]}}}],["constructor(id",{"_index":1638,"t":{"283":{"position":[[179,15]]}}}],["constructor(priv",{"_index":1513,"t":{"248":{"position":[[566,19]]},"285":{"position":[[160,19]]},"287":{"position":[[336,19]]},"293":{"position":[[638,19]]},"295":{"position":[[600,19],[825,19]]},"311":{"position":[[727,19]]},"463":{"position":[[166,19]]}}}],["constructor(prop",{"_index":2700,"t":{"561":{"position":[[178,18]]}}}],["consum",{"_index":2432,"t":{"486":{"position":[[1314,8]]}}}],["contact",{"_index":291,"t":{"29":{"position":[[402,7],[989,7]]}}}],["contain",{"_index":302,"t":{"29":{"position":[[918,8]]},"39":{"position":[[10,8],[410,8],[656,8]]},"148":{"position":[[19,7]]},"155":{"position":[[175,8]]},"163":{"position":[[131,7]]},"167":{"position":[[269,8]]},"174":{"position":[[182,8]]},"183":{"position":[[22,11]]},"185":{"position":[[129,8],[368,7],[635,7],[755,8],[1210,8],[1488,8],[1681,8],[1776,8],[2048,8],[2094,7],[2225,8]]},"205":{"position":[[241,8]]},"209":{"position":[[149,9]]},"211":{"position":[[338,8]]},"270":{"position":[[30,8]]},"272":{"position":[[167,10]]},"283":{"position":[[40,10]]},"287":{"position":[[526,7]]},"293":{"position":[[29,7]]},"316":{"position":[[95,9]]},"329":{"position":[[1188,8]]},"348":{"position":[[21,8]]},"350":{"position":[[19,7]]},"358":{"position":[[229,9]]},"362":{"position":[[135,10]]},"366":{"position":[[7,9],[172,8]]},"368":{"position":[[53,9],[109,9],[272,9],[385,9],[801,10]]},"375":{"position":[[230,10]]},"473":{"position":[[134,7],[1150,8],[1343,9],[1708,10]]},"524":{"position":[[27,10]]},"535":{"position":[[17,8]]},"537":{"position":[[273,8]]},"541":{"position":[[272,8]]},"545":{"position":[[766,7]]}}}],["content",{"_index":178,"t":{"15":{"position":[[691,7]]},"100":{"position":[[258,7]]},"144":{"position":[[151,7],[217,7]]},"177":{"position":[[51,7]]},"183":{"position":[[180,7]]},"262":{"position":[[351,8]]},"410":{"position":[[223,7]]},"439":{"position":[[457,7]]},"473":{"position":[[434,8],[652,8],[2771,7],[2819,7],[3789,7],[3837,7],[4680,7],[4728,7]]}}}],["contentelementresponsefactory.maptoresponse(el",{"_index":1488,"t":{"243":{"position":[[1107,53]]}}}],["context",{"_index":118,"t":{"11":{"position":[[144,7]]},"295":{"position":[[144,7]]},"338":{"position":[[321,7]]},"421":{"position":[[841,8]]},"459":{"position":[[193,7]]}}}],["continu",{"_index":2509,"t":{"505":{"position":[[4020,8]]},"507":{"position":[[430,9]]}}}],["contradict",{"_index":1847,"t":{"329":{"position":[[493,10]]}}}],["contrast",{"_index":1935,"t":{"342":{"position":[[238,8]]},"449":{"position":[[97,8]]}}}],["control",{"_index":1142,"t":{"163":{"position":[[20,10]]},"241":{"position":[[35,12],[118,10],[173,10],[254,10]]},"252":{"position":[[41,12],[85,10]]},"254":{"position":[[79,10],[102,10]]},"262":{"position":[[40,10]]},"338":{"position":[[830,12]]},"340":{"position":[[2006,7]]},"350":{"position":[[0,11]]},"352":{"position":[[566,10],[704,7]]},"482":{"position":[[27,10]]},"514":{"position":[[30,10],[147,10]]},"537":{"position":[[333,10]]},"543":{"position":[[4,11],[181,12]]},"545":{"position":[[774,12]]}}}],["controller/api",{"_index":1973,"t":{"352":{"position":[[261,14]]}}}],["conveni",{"_index":1761,"t":{"307":{"position":[[76,10]]}}}],["convent",{"_index":3,"t":{"2":{"position":[[27,11],[155,11]]},"15":{"position":[[531,10]]},"19":{"position":[[171,10]]},"21":{"position":[[55,11]]},"104":{"position":[[5,11]]},"177":{"position":[[266,10]]},"183":{"position":[[722,10]]}}}],["cooki",{"_index":2200,"t":{"421":{"position":[[999,7]]},"430":{"position":[[161,6]]},"447":{"position":[[2,6],[59,7]]},"449":{"position":[[192,6]]}}}],["cookie_session_refresh_interv",{"_index":2228,"t":{"443":{"position":[[88,31]]}}}],["copi",{"_index":1027,"t":{"144":{"position":[[141,5],[207,5]]},"146":{"position":[[235,4]]},"148":{"position":[[570,4]]},"316":{"position":[[458,4]]},"344":{"position":[[2377,4]]},"407":{"position":[[252,7]]},"524":{"position":[[276,4]]}}}],["copymodule.copy(payload)).rejects.tothrow",{"_index":1377,"t":{"218":{"position":[[19,42]]}}}],["copymodulemock.copybysharetoken",{"_index":1392,"t":{"224":{"position":[[464,31]]}}}],["copyprocess",{"_index":1378,"t":{"218":{"position":[[62,12]]}}}],["copyresultmodal.unit.t",{"_index":1355,"t":{"209":{"position":[[509,23]]}}}],["core",{"_index":410,"t":{"43":{"position":[[133,4]]},"187":{"position":[[1138,4]]},"256":{"position":[[23,4]]},"455":{"position":[[4,4]]}}}],["core.autocrlf",{"_index":1077,"t":{"148":{"position":[[1183,13]]},"318":{"position":[[94,13]]}}}],["core/error",{"_index":1670,"t":{"289":{"position":[[570,10]]}}}],["correct",{"_index":1678,"t":{"289":{"position":[[1136,7]]},"344":{"position":[[653,7]]},"346":{"position":[[46,7]]},"434":{"position":[[186,7]]},"529":{"position":[[489,11]]}}}],["correctli",{"_index":447,"t":{"45":{"position":[[1186,9]]},"172":{"position":[[177,10]]},"197":{"position":[[181,9]]},"301":{"position":[[183,9],[310,10],[946,10]]},"320":{"position":[[140,9]]},"334":{"position":[[948,9]]},"344":{"position":[[1995,9]]},"352":{"position":[[152,10]]}}}],["correspond",{"_index":145,"t":{"13":{"position":[[634,13]]},"272":{"position":[[331,13],[447,13]]},"491":{"position":[[436,13]]},"543":{"position":[[24,13]]}}}],["cost",{"_index":570,"t":{"70":{"position":[[191,4]]}}}],["coupl",{"_index":1265,"t":{"187":{"position":[[1069,7]]},"309":{"position":[[1340,7]]},"545":{"position":[[252,8],[306,8]]}}}],["cours",{"_index":81,"t":{"9":{"position":[[495,7]]},"15":{"position":[[302,7],[791,6]]},"19":{"position":[[143,8],[801,6],[844,6],[1139,6],[1182,6]]},"146":{"position":[[228,6]]},"277":{"position":[[635,6]]},"279":{"position":[[782,6]]},"281":{"position":[[116,7]]},"325":{"position":[[698,8],[834,8]]},"449":{"position":[[169,6]]},"488":{"position":[[51,7]]},"526":{"position":[[8,7]]},"558":{"position":[[349,6],[427,6],[447,8]]},"561":{"position":[[98,6]]}}}],["course.t",{"_index":1111,"t":{"161":{"position":[[125,10]]}}}],["courseopt",{"_index":890,"t":{"129":{"position":[[3329,23]]}}}],["disadvantag",{"_index":955,"t":{"131":{"position":[[2782,12]]}}}],["disconnect",{"_index":2500,"t":{"505":{"position":[[2949,11]]}}}],["discord",{"_index":2571,"t":{"521":{"position":[[1507,7]]}}}],["discuss",{"_index":2596,"t":{"521":{"position":[[2205,10],[2330,10]]},"535":{"position":[[1103,10]]}}}],["discussion_enabled=fals",{"_index":2103,"t":{"375":{"position":[[1177,24]]}}}],["display",{"_index":906,"t":{"131":{"position":[[266,7],[2054,7],[2989,7],[3915,10],[4052,7]]},"165":{"position":[[226,7]]},"207":{"position":[[297,7]]},"375":{"position":[[163,9]]},"424":{"position":[[247,9]]},"432":{"position":[[184,9]]},"439":{"position":[[293,7]]},"480":{"position":[[372,9]]}}}],["dist/apps/server/apps/server.app.j",{"_index":644,"t":{"92":{"position":[[47,35],[138,35],[245,35],[372,35],[434,35]]}}}],["distinguish",{"_index":1838,"t":{"329":{"position":[[61,13]]},"533":{"position":[[13,11]]}}}],["distribut",{"_index":2486,"t":{"505":{"position":[[1072,11]]}}}],["div",{"_index":705,"t":{"106":{"position":[[11,4]]},"115":{"position":[[284,4],[322,6]]},"209":{"position":[[306,4]]}}}],["divid",{"_index":865,"t":{"129":{"position":[[1689,8],[1912,7],[2032,7],[2140,7],[2441,7],[2677,9],[3094,8],[3381,7],[3775,7]]}}}],["do",{"_index":2640,"t":{"535":{"position":[[559,5]]}}}],["doc",{"_index":354,"t":{"37":{"position":[[1057,5]]},"39":{"position":[[568,6]]},"59":{"position":[[52,4]]},"74":{"position":[[86,3]]},"102":{"position":[[250,5]]},"131":{"position":[[3597,5]]}}}],["doc.md",{"_index":575,"t":{"74":{"position":[[68,6]]}}}],["docker",{"_index":1994,"t":{"358":{"position":[[390,6],[608,6]]},"360":{"position":[[459,6],[833,6]]},"362":{"position":[[146,6],[250,6]]},"368":{"position":[[46,6],[306,6],[884,6],[937,6]]},"373":{"position":[[60,7],[137,6]]},"375":{"position":[[223,6],[278,6],[358,6]]},"473":{"position":[[1661,6],[1719,6]]},"524":{"position":[[20,6]]}}}],["docker'",{"_index":2308,"t":{"473":{"position":[[1494,8]]}}}],["docker.io/etherpad/etherpad:2.0.0",{"_index":2315,"t":{"473":{"position":[[1847,33],[1909,33]]}}}],["docker.io/mongo",{"_index":2066,"t":{"373":{"position":[[200,15]]}}}],["docker.io/rocketchat/rocket.chat:4.7.2",{"_index":2112,"t":{"375":{"position":[[1461,38]]}}}],["docs/hello.md",{"_index":515,"t":{"57":{"position":[[26,14],[41,13]]},"59":{"position":[[123,13]]}}}],["document",{"_index":389,"t":{"37":{"position":[[2131,13]]},"39":{"position":[[586,13]]},"45":{"position":[[1273,13]]},"49":{"position":[[18,13]]},"55":{"position":[[0,9]]},"57":{"position":[[93,11],[111,8]]},"59":{"position":[[224,11],[447,11]]},"74":{"position":[[9,9],[103,8],[134,8]]},"84":{"position":[[18,13]]},"110":{"position":[[164,10]]},"136":{"position":[[200,13]]},"183":{"position":[[557,13]]},"193":{"position":[[191,10]]},"199":{"position":[[1019,13]]},"236":{"position":[[115,13]]},"252":{"position":[[293,14]]},"268":{"position":[[481,8],[526,8]]},"348":{"position":[[284,10]]},"350":{"position":[[165,13]]},"352":{"position":[[204,14]]},"368":{"position":[[711,13]]},"395":{"position":[[119,13],[198,14]]},"397":{"position":[[247,9],[282,8]]},"399":{"position":[[289,13]]},"418":{"position":[[35,14]]},"473":{"position":[[3157,13]]},"488":{"position":[[999,8]]},"505":{"position":[[184,9],[350,8],[383,9],[472,8],[782,9],[1459,9],[2844,8],[3511,9]]},"514":{"position":[[1038,9]]},"521":{"position":[[1217,13]]},"537":{"position":[[221,13]]}}}],["document.controller.t",{"_index":2525,"t":{"514":{"position":[[122,22]]}}}],["document.service.t",{"_index":2526,"t":{"514":{"position":[[237,19]]}}}],["docusauru",{"_index":452,"t":{"47":{"position":[[36,10],[94,10]]},"49":{"position":[[207,10],[247,10]]},"51":{"position":[[0,10]]},"53":{"position":[[164,10],[289,10]]},"57":{"position":[[82,10]]},"59":{"position":[[0,10],[213,10]]},"67":{"position":[[0,10]]},"72":{"position":[[0,10]]},"78":{"position":[[139,12],[329,12]]},"82":{"position":[[0,10]]},"84":{"position":[[512,10]]}}}],["docusaurus!{{email}}).tohavebeencalledwith",{"_index":1414,"t":{"228":{"position":[[477,62]]}}}],["expect(yourmodule.getloading).tobe(tru",{"_index":1423,"t":{"228":{"position":[[943,41]]}}}],["expect.any(applicationerror",{"_index":1387,"t":{"220":{"position":[[157,28]]}}}],["expect.objectcontaininghello",{"_index":598,"t":{"80":{"position":[[148,10],[254,10]]}}}],["h1>mi",{"_index":551,"t":{"63":{"position":[[193,6]]}}}],["hand",{"_index":1617,"t":{"277":{"position":[[646,6]]}}}],["handl",{"_index":132,"t":{"13":{"position":[[75,6]]},"131":{"position":[[772,6],[5442,7]]},"153":{"position":[[141,8]]},"241":{"position":[[284,6]]},"283":{"position":[[72,6]]},"289":{"position":[[436,6],[1262,7]]},"309":{"position":[[194,6],[260,6],[1232,8]]},"334":{"position":[[936,8]]},"344":{"position":[[1890,7]]},"412":{"position":[[287,6],[349,8]]},"426":{"position":[[276,8]]},"434":{"position":[[119,8]]},"451":{"position":[[72,8]]},"470":{"position":[[14,7]]},"476":{"position":[[326,7],[510,7]]},"500":{"position":[[25,7]]},"503":{"position":[[334,6]]},"505":{"position":[[168,8],[1884,7],[2474,7],[2610,7],[4556,6]]},"507":{"position":[[443,6]]},"509":{"position":[[342,8]]},"521":{"position":[[145,7],[355,8],[424,8],[831,9],[850,7],[962,6]]}}}],["handle(ev",{"_index":1656,"t":{"287":{"position":[[407,13]]}}}],["handler",{"_index":1162,"t":{"165":{"position":[[119,7]]},"281":{"position":[[152,8]]},"287":{"position":[[507,7]]}}}],["happen",{"_index":1852,"t":{"329":{"position":[[1131,6]]}}}],["happi",{"_index":1278,"t":{"193":{"position":[[277,9]]}}}],["hard",{"_index":918,"t":{"131":{"position":[[549,4]]},"174":{"position":[[140,4]]},"211":{"position":[[374,4]]},"275":{"position":[[533,4]]},"309":{"position":[[143,4]]}}}],["hardcoded_valu",{"_index":1579,"t":{"270":{"position":[[485,16]]}}}],["hardwar",{"_index":664,"t":{"98":{"position":[[166,8]]}}}],["haspermiss",{"_index":2674,"t":{"549":{"position":[[660,16]]}}}],["have",{"_index":2662,"t":{"543":{"position":[[350,6]]}}}],["head",{"_index":576,"t":{"74":{"position":[[192,7]]},"131":{"position":[[1326,4]]},"133":{"position":[[635,5]]}}}],["header",{"_index":2034,"t":{"362":{"position":[[466,6],[491,6],[610,7]]}}}],["headless",{"_index":311,"t":{"33":{"position":[[71,8]]}}}],["heavili",{"_index":972,"t":{"131":{"position":[[3851,7]]}}}],["hello",{"_index":516,"t":{"57":{"position":[[57,5]]},"59":{"position":[[188,5],[365,8]]}}}],["hellodocusauru",{"_index":597,"t":{"80":{"position":[[119,17],[227,17]]}}}],["helloworld.unit.t",{"_index":1322,"t":{"203":{"position":[[92,18]]}}}],["helloworld.vu",{"_index":1321,"t":{"203":{"position":[[77,14]]}}}],["help",{"_index":322,"t":{"35":{"position":[[48,4]]},"39":{"position":[[715,7]]},"41":{"position":[[83,4]]},"199":{"position":[[111,4]]},"205":{"position":[[42,7]]},"224":{"position":[[349,7]]},"250":{"position":[[224,4]]},"303":{"position":[[106,4]]},"332":{"position":[[119,5]]},"334":{"position":[[191,7]]},"338":{"position":[[388,4]]},"407":{"position":[[101,4]]},"414":{"position":[[494,7]]},"567":{"position":[[12,4]]}}}],["helper",{"_index":1815,"t":{"323":{"position":[[259,6]]},"340":{"position":[[1883,6]]}}}],["here",{"_index":672,"t":{"100":{"position":[[125,5],[502,5]]},"102":{"position":[[409,4]]},"106":{"position":[[405,5]]},"108":{"position":[[129,5]]},"112":{"position":[[394,5]]},"148":{"position":[[707,4]]},"151":{"position":[[170,5]]},"161":{"position":[[103,5]]},"165":{"position":[[843,5]]},"167":{"position":[[338,5],[362,5]]},"205":{"position":[[576,4]]},"224":{"position":[[810,5]]},"272":{"position":[[129,5]]},"281":{"position":[[408,4],[443,4]]},"309":{"position":[[166,4]]},"316":{"position":[[595,4]]},"332":{"position":[[426,4]]},"358":{"position":[[120,4]]},"395":{"position":[[259,5]]},"473":{"position":[[1243,4]]},"484":{"position":[[36,4]]},"519":{"position":[[119,4]]},"551":{"position":[[355,4],[622,4]]}}}],["here'",{"_index":1752,"t":{"305":{"position":[[310,6]]},"307":{"position":[[382,6],[931,6]]}}}],["hex",{"_index":774,"t":{"117":{"position":[[305,3]]}}}],["hi",{"_index":522,"t":{"59":{"position":[[156,5]]}}}],["hide",{"_index":2556,"t":{"521":{"position":[[703,5],[754,4]]}}}],["hierarchi",{"_index":744,"t":{"112":{"position":[[126,10]]},"535":{"position":[[612,9]]}}}],["high",{"_index":1286,"t":{"197":{"position":[[296,4]]},"505":{"position":[[4692,4]]},"545":{"position":[[234,4]]}}}],["higher",{"_index":1276,"t":{"193":{"position":[[100,6]]},"531":{"position":[[759,6]]}}}],["highli",{"_index":146,"t":{"13":{"position":[[676,6]]},"133":{"position":[[802,6]]},"505":{"position":[[1750,6]]}}}],["highlight",{"_index":594,"t":{"80":{"position":[[47,13]]},"84":{"position":[[117,9],[383,10],[449,10]]}}}],["hint",{"_index":1102,"t":{"157":{"position":[[309,4]]},"159":{"position":[[393,4]]},"185":{"position":[[787,5]]},"205":{"position":[[924,5]]},"275":{"position":[[431,4]]},"325":{"position":[[53,4]]},"551":{"position":[[450,5]]}}}],["his/her",{"_index":726,"t":{"108":{"position":[[52,7]]}}}],["histori",{"_index":1072,"t":{"148":{"position":[[1050,7]]},"316":{"position":[[937,7]]},"418":{"position":[[525,8]]}}}],["history300",{"_index":92,"t":{"9":{"position":[[901,10]]}}}],["hold",{"_index":393,"t":{"39":{"position":[[200,5]]},"436":{"position":[[225,5]]},"498":{"position":[[175,5]]}}}],["homepag",{"_index":444,"t":{"45":{"position":[[1075,8],[1147,8],[1165,8]]}}}],["hook",{"_index":351,"t":{"37":{"position":[[982,5]]},"338":{"position":[[373,5]]},"517":{"position":[[178,4]]},"521":{"position":[[294,4],[581,4]]}}}],["hooks/usemultiplayerstate.t",{"_index":2542,"t":{"517":{"position":[[140,28]]}}}],["horizont",{"_index":2554,"t":{"521":{"position":[[674,10]]}}}],["host",{"_index":925,"t":{"131":{"position":[[983,4]]},"373":{"position":[[332,6]]},"473":{"position":[[2598,5],[2989,4],[3616,5],[4007,4],[4497,5],[4898,4]]},"486":{"position":[[25,4]]}}}],["host'",{"_index":2305,"t":{"473":{"position":[[1371,6]]}}}],["host.docker.intern",{"_index":2303,"t":{"473":{"position":[[1264,20]]}}}],["host=http://localhost:4000",{"_index":2012,"t":{"360":{"position":[[311,26]]}}}],["hostnam",{"_index":2304,"t":{"473":{"position":[[1285,8]]}}}],["hour",{"_index":2226,"t":{"441":{"position":[[494,6]]}}}],["hpi",{"_index":1565,"t":{"268":{"position":[[149,3]]}}}],["hr",{"_index":2555,"t":{"521":{"position":[[693,5]]}}}],["href=\"'mailto",{"_index":975,"t":{"131":{"position":[[4532,16],[5300,16]]}}}],["href=\"'wikipedia.com'\">link",{"_index":891,"t":{"129":{"position":[[3482,28]]}}}],["html",{"_index":361,"t":{"37":{"position":[[1277,4],[1902,4]]},"39":{"position":[[665,4],[972,4]]},"67":{"position":[[99,5]]},"106":{"position":[[60,4]]},"126":{"position":[[433,4],[557,4],[584,4],[828,4]]},"129":{"position":[[832,4],[873,4],[986,4],[1033,4],[1210,4],[1777,4],[2343,4],[3266,4],[4076,5]]},"131":{"position":[[4811,4]]},"133":{"position":[[1374,4]]},"199":{"position":[[772,4],[792,4]]},"209":{"position":[[31,4],[355,4]]}}}],["html5",{"_index":691,"t":{"102":{"position":[[119,5]]}}}],["http",{"_index":1581,"t":{"270":{"position":[[526,11]]},"289":{"position":[[648,4]]},"293":{"position":[[264,4]]},"491":{"position":[[168,4]]},"514":{"position":[[170,4]]}}}],["http/1.1",{"_index":2324,"t":{"473":{"position":[[2587,8],[2662,8],[3605,8],[3680,8],[4486,8],[4561,8]]}}}],["http://127.0.0.1:9001/api",{"_index":2321,"t":{"473":{"position":[[2476,25]]}}}],["http://127.0.0.1:9001/api/1/createauthorifnotexistsfor\\?apikey\\=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd\\&name\\=michael\\&authormapper\\=7",{"_index":2354,"t":{"473":{"position":[[3230,160]]}}}],["http://127.0.0.1:9001/api/1/createauthorifnotexistsfor\\?apikey\\=secret\\&name\\=michael\\&authormapper\\=7",{"_index":2363,"t":{"473":{"position":[[4227,102]]}}}],["http://\\😅(catscontrol",{"_index":1898,"t":{"338":{"position":[[985,48]]}}}],["moduleref.get(sampleservic",{"_index":1897,"t":{"338":{"position":[[921,44]]}}}],["modules/featur",{"_index":1254,"t":{"187":{"position":[[64,16]]}}}],["modules/modul",{"_index":1754,"t":{"305":{"position":[[356,16],[624,15]]},"307":{"position":[[421,15],[1086,16]]}}}],["modules/modulea",{"_index":1782,"t":{"309":{"position":[[515,19],[817,19]]}}}],["modules/moduleb",{"_index":1783,"t":{"309":{"position":[[606,19],[872,19]]}}}],["modules/modulec/service.t",{"_index":1781,"t":{"309":{"position":[[416,27],[754,27]]}}}],["modules/oth",{"_index":1775,"t":{"307":{"position":[[977,14]]}}}],["modules/us",{"_index":1649,"t":{"287":{"position":[[83,17]]}}}],["mongo",{"_index":2162,"t":{"412":{"position":[[210,5]]},"498":{"position":[[369,5]]}}}],["mongo_url=mongodb://172.29.173.128:27030/rocketchat",{"_index":2085,"t":{"375":{"position":[[407,51]]}}}],["mongocli",{"_index":2184,"t":{"418":{"position":[[141,11]]}}}],["mongodb",{"_index":1947,"t":{"344":{"position":[[1081,7],[2418,7]]},"373":{"position":[[49,7],[108,7],[167,7],[251,7]]},"375":{"position":[[215,7],[305,7]]},"392":{"position":[[59,7],[205,8]]},"473":{"position":[[1163,7]]}}}],["mongomemorydatabasemodul",{"_index":1945,"t":{"344":{"position":[[957,25],[1205,25]]}}}],["mongomemorydatabasemodule.forroot",{"_index":1953,"t":{"344":{"position":[[1437,35]]}}}],["mongoos",{"_index":2168,"t":{"412":{"position":[[497,8]]}}}],["monitor",{"_index":1445,"t":{"238":{"position":[[4,10]]}}}],["more",{"_index":68,"t":{"9":{"position":[[205,4]]},"33":{"position":[[216,4]]},"47":{"position":[[114,4]]},"84":{"position":[[32,4]]},"100":{"position":[[287,4]]},"110":{"position":[[45,4]]},"121":{"position":[[15,4]]},"129":{"position":[[70,4],[79,4],[2929,4]]},"131":{"position":[[595,4],[779,4],[2215,4],[2349,4],[4956,4]]},"211":{"position":[[58,4]]},"224":{"position":[[641,4]]},"234":{"position":[[596,4]]},"266":{"position":[[443,4]]},"277":{"position":[[541,4],[886,4]]},"279":{"position":[[609,4],[639,4]]},"293":{"position":[[37,4]]},"340":{"position":[[2001,4]]},"399":{"position":[[307,4]]},"531":{"position":[[502,4],[561,4]]},"541":{"position":[[424,4]]},"545":{"position":[[582,4]]},"551":{"position":[[344,4],[611,4]]},"561":{"position":[[141,4],[285,4]]},"565":{"position":[[351,4]]}}}],["mount",{"_index":1300,"t":{"199":{"position":[[180,6],[195,6],[337,8]]},"224":{"position":[[829,5]]},"521":{"position":[[411,8],[858,8]]}}}],["mount(yourcomponenttobetest",{"_index":1412,"t":{"228":{"position":[[380,30]]}}}],["mous",{"_index":689,"t":{"102":{"position":[[87,5]]},"214":{"position":[[59,5],[86,5]]}}}],["move",{"_index":683,"t":{"100":{"position":[[402,4]]},"177":{"position":[[322,4]]},"183":{"position":[[756,4]]},"309":{"position":[[349,6]]},"407":{"position":[[299,4]]}}}],["ms",{"_index":1873,"t":{"334":{"position":[[766,2]]}}}],["much",{"_index":456,"t":{"47":{"position":[[109,4]]},"53":{"position":[[503,4]]},"129":{"position":[[1504,4]]},"131":{"position":[[1933,4]]},"133":{"position":[[145,4]]},"329":{"position":[[271,4]]}}}],["multipl",{"_index":46,"t":{"7":{"position":[[139,8],[276,8]]},"9":{"position":[[1044,8],[1271,8]]},"19":{"position":[[343,8]]},"110":{"position":[[518,8]]},"174":{"position":[[603,8]]},"260":{"position":[[122,8]]},"277":{"position":[[763,8]]},"301":{"position":[[845,8]]},"410":{"position":[[355,8]]},"412":{"position":[[606,8]]},"457":{"position":[[93,8]]},"461":{"position":[[164,8]]},"505":{"position":[[799,8]]},"509":{"position":[[156,8]]},"531":{"position":[[619,8],[1215,8]]}}}],["multiplay",{"_index":2543,"t":{"517":{"position":[[196,11],[275,11]]},"521":{"position":[[311,11]]}}}],["mutlipl",{"_index":1438,"t":{"234":{"position":[[284,8]]}}}],["myfeathersserviceadapt",{"_index":1511,"t":{"248":{"position":[[394,27],[539,24]]}}}],["mymenu.component.vu",{"_index":836,"t":{"129":{"position":[[432,20]]}}}],["mymodul",{"_index":1512,"t":{"248":{"position":[[438,8]]}}}],["myreactpag",{"_index":549,"t":{"63":{"position":[[159,13]]}}}],["name",{"_index":9,"t":{"2":{"position":[[85,6]]},"9":{"position":[[636,5]]},"13":{"position":[[234,5]]},"15":{"position":[[7,5],[46,6],[89,5],[162,5],[238,5],[331,5],[427,5],[524,6]]},"19":{"position":[[124,4],[164,6],[231,5]]},"21":{"position":[[31,5],[48,6]]},"53":{"position":[[125,5],[259,5]]},"115":{"position":[[485,4]]},"123":{"position":[[89,4]]},"129":{"position":[[3661,5]]},"131":{"position":[[147,5],[201,5],[5406,4]]},"133":{"position":[[92,5],[132,4],[291,6],[406,4],[559,5],[991,4],[1277,4],[1308,5],[1335,5],[1388,5]]},"146":{"position":[[148,7],[262,5]]},"151":{"position":[[228,4],[368,4]]},"163":{"position":[[500,5],[759,5]]},"177":{"position":[[29,5]]},"183":{"position":[[715,6]]},"187":{"position":[[111,4]]},"197":{"position":[[542,6]]},"199":{"position":[[660,4],[729,4]]},"203":{"position":[[16,5]]},"209":{"position":[[246,5]]},"260":{"position":[[67,5],[276,5]]},"270":{"position":[[197,4],[261,5],[279,4]]},"293":{"position":[[300,4]]},"301":{"position":[[1151,5]]},"305":{"position":[[304,5],[373,6]]},"307":{"position":[[1103,6]]},"323":{"position":[[237,5]]},"325":{"position":[[23,4],[217,5],[509,4]]},"327":{"position":[[433,4]]},"356":{"position":[[11,6]]},"358":{"position":[[405,4],[623,4]]},"360":{"position":[[474,4],[848,4]]},"362":{"position":[[161,4],[265,4]]},"373":{"position":[[150,4]]},"407":{"position":[[653,6]]},"447":{"position":[[9,5]]},"457":{"position":[[195,5]]},"473":{"position":[[1828,4]]},"480":{"position":[[132,4]]},"486":{"position":[[477,4],[587,4],[726,4]]},"491":{"position":[[466,4]]},"549":{"position":[[4,4],[604,5]]},"551":{"position":[[121,5],[220,5],[636,5]]},"561":{"position":[[124,5],[199,5]]},"563":{"position":[[18,5]]},"565":{"position":[[113,5],[225,4]]}}}],["name/index.t",{"_index":1757,"t":{"305":{"position":[[640,13]]},"307":{"position":[[437,13]]}}}],["name/interfac",{"_index":1772,"t":{"307":{"position":[[615,17]]}}}],["name/service.t",{"_index":1759,"t":{"305":{"position":[[704,17]]},"307":{"position":[[999,15]]}}}],["name:/default",{"_index":1584,"t":{"270":{"position":[[593,14]]}}}],["name:/templates/:appl",{"_index":1572,"t":{"270":{"position":[[168,28]]}}}],["name=some_migration_nam",{"_index":2137,"t":{"397":{"position":[[142,24]]}}}],["namespac",{"_index":280,"t":{"29":{"position":[[217,9],[361,11],[672,9],[953,9]]},"146":{"position":[[338,9]]},"270":{"position":[[294,10],[308,9]]},"314":{"position":[[386,9]]}}}],["natur",{"_index":1342,"t":{"207":{"position":[[135,7]]},"311":{"position":[[307,9]]},"418":{"position":[[207,7]]}}}],["navbar",{"_index":466,"t":{"49":{"position":[[93,6]]}}}],["navig",{"_index":288,"t":{"29":{"position":[[373,8]]},"35":{"position":[[57,8]]},"45":{"position":[[293,8],[511,8],[1131,8]]},"55":{"position":[[73,10]]}}}],["nbc",{"_index":1594,"t":{"272":{"position":[[497,5]]}}}],["near",{"_index":1229,"t":{"183":{"position":[[659,4]]},"505":{"position":[[2591,4]]}}}],["necessari",{"_index":293,"t":{"29":{"position":[[426,9],[1013,9]]},"31":{"position":[[41,9]]},"248":{"position":[[108,9]]},"268":{"position":[[294,9]]},"270":{"position":[[57,9]]},"301":{"position":[[1128,10]]},"305":{"position":[[490,9]]},"348":{"position":[[166,9],[231,9]]},"558":{"position":[[144,10]]}}}],["need",{"_index":58,"t":{"9":{"position":[[57,4]]},"106":{"position":[[255,4]]},"117":{"position":[[122,4],[542,4]]},"133":{"position":[[706,4]]},"136":{"position":[[119,4]]},"148":{"position":[[847,4]]},"159":{"position":[[61,4],[431,4]]},"174":{"position":[[207,4]]},"185":{"position":[[1952,6]]},"187":{"position":[[372,5],[634,5],[780,5],[950,4]]},"197":{"position":[[131,4]]},"205":{"position":[[1000,6]]},"211":{"position":[[51,4]]},"232":{"position":[[255,4]]},"245":{"position":[[285,4]]},"248":{"position":[[292,4]]},"250":{"position":[[62,4]]},"266":{"position":[[129,5]]},"275":{"position":[[225,5]]},"277":{"position":[[7,4],[870,4]]},"279":{"position":[[120,5],[348,4],[419,4]]},"281":{"position":[[214,5]]},"287":{"position":[[580,5]]},"309":{"position":[[963,4]]},"314":{"position":[[81,4],[270,4],[315,5]]},"316":{"position":[[734,4]]},"327":{"position":[[206,6]]},"418":{"position":[[366,7]]},"430":{"position":[[39,5]]},"473":{"position":[[150,6],[1063,6],[1479,5]]},"505":{"position":[[1710,4],[2333,4]]},"519":{"position":[[570,5]]},"531":{"position":[[1002,4],[1082,4]]},"535":{"position":[[333,5],[376,5]]},"541":{"position":[[366,5],[414,6],[449,5],[1205,5]]},"545":{"position":[[703,4]]},"549":{"position":[[83,4]]},"565":{"position":[[264,5]]}}}],["neededparam",{"_index":2691,"t":{"553":{"position":[[145,12]]}}}],["neg",{"_index":1283,"t":{"197":{"position":[[72,8],[310,8]]}}}],["nest",{"_index":887,"t":{"129":{"position":[[3244,6],[3539,6],[3611,6],[3869,6]]},"268":{"position":[[510,4]]},"307":{"position":[[87,4]]},"338":{"position":[[175,4],[349,4]]},"344":{"position":[[924,4]]},"421":{"position":[[555,4]]},"449":{"position":[[113,4]]}}}],["nest.j",{"_index":1449,"t":{"241":{"position":[[3,7]]}}}],["nest:start:consol",{"_index":2151,"t":{"407":{"position":[[68,18],[398,18]]},"480":{"position":[[73,18]]}}}],["nest:start:dev",{"_index":2132,"t":{"392":{"position":[[324,14]]},"524":{"position":[[378,14]]}}}],["nest:start:fil",{"_index":2607,"t":{"524":{"position":[[421,16]]}}}],["nestapp.get(rocketchatservic",{"_index":1525,"t":{"250":{"position":[[425,31]]}}}],["nestj",{"_index":1517,"t":{"250":{"position":[[12,6],[79,6],[275,6]]},"268":{"position":[[328,6]]},"289":{"position":[[137,7],[545,6]]},"293":{"position":[[284,7]]},"338":{"position":[[0,7]]},"340":{"position":[[32,7]]},"384":{"position":[[79,6],[163,6]]},"514":{"position":[[477,6]]},"541":{"position":[[657,7]]}}}],["nestjs/common",{"_index":1632,"t":{"281":{"position":[[287,17]]}}}],["nestjs/cqr",{"_index":1633,"t":{"281":{"position":[[332,15]]},"285":{"position":[[61,15]]},"287":{"position":[[146,15]]}}}],["nestjs/testing.test",{"_index":1888,"t":{"338":{"position":[[272,20]]}}}],["nestwinston",{"_index":1722,"t":{"295":{"position":[[1024,13]]}}}],["network",{"_index":1970,"t":{"350":{"position":[[118,8]]},"473":{"position":[[1384,7],[1503,7]]}}}],["network=\"host",{"_index":2311,"t":{"473":{"position":[[1629,14]]}}}],["never",{"_index":1835,"t":{"327":{"position":[[503,5]]},"334":{"position":[[409,5]]},"418":{"position":[[281,5]]}}}],["new",{"_index":436,"t":{"45":{"position":[[790,3]]},"53":{"position":[[520,3]]},"57":{"position":[[107,3]]},"63":{"position":[[261,3]]},"65":{"position":[[123,3]]},"123":{"position":[[32,3],[150,3],[195,3]]},"129":{"position":[[1753,3],[1900,3],[2988,3]]},"131":{"position":[[2037,3],[2133,3],[2272,3],[4373,3]]},"155":{"position":[[14,3],[95,3]]},"187":{"position":[[11,3],[40,3],[538,3],[563,3]]},"228":{"position":[[616,3]]},"243":{"position":[[72,3]]},"250":{"position":[[187,3]]},"252":{"position":[[657,4]]},"258":{"position":[[184,4]]},"275":{"position":[[359,3],[463,3]]},"291":{"position":[[34,3],[95,3],[162,3]]},"293":{"position":[[1004,3]]},"299":{"position":[[27,3]]},"301":{"position":[[523,3],[630,3],[795,3]]},"309":{"position":[[371,3]]},"344":{"position":[[1489,6],[2151,3]]},"397":{"position":[[45,3]]},"421":{"position":[[1215,3]]},"424":{"position":[[158,3]]},"426":{"position":[[148,3]]},"428":{"position":[[120,3]]},"432":{"position":[[51,3]]},"439":{"position":[[70,3]]},"441":{"position":[[146,3],[549,3]]},"473":{"position":[[116,3],[381,3]]},"488":{"position":[[995,3]]},"493":{"position":[[476,3]]},"495":{"position":[[419,3]]},"505":{"position":[[614,3],[4118,3]]},"507":{"position":[[396,3]]},"519":{"position":[[499,3],[532,3]]},"521":{"position":[[1239,3]]},"526":{"position":[[47,3],[62,3],[115,3]]},"529":{"position":[[315,3]]},"558":{"position":[[443,3]]}}}],["newer",{"_index":1230,"t":{"183":{"position":[[676,5]]}}}],["news].params.t",{"_index":1554,"t":{"260":{"position":[[44,15]]}}}],["news].response.dto",{"_index":1556,"t":{"260":{"position":[[287,20]]}}}],["newsmapper.mapcreatenewstodomain(param",{"_index":1540,"t":{"252":{"position":[[732,40]]}}}],["newsmapper.maptoresponse(new",{"_index":1542,"t":{"252":{"position":[[788,31]]}}}],["newsrepo",{"_index":1951,"t":{"344":{"position":[[1273,9],[1532,11]]}}}],["next",{"_index":802,"t":{"126":{"position":[[155,4]]},"216":{"position":[[204,9]]},"414":{"position":[[133,4]]},"473":{"position":[[1681,5]]}}}],["node",{"_index":244,"t":{"25":{"position":[[84,4]]},"94":{"position":[[125,4]]},"245":{"position":[[51,4],[339,4],[389,4]]},"392":{"position":[[51,4]]}}}],["node.j",{"_index":242,"t":{"25":{"position":[[66,8]]}}}],["node_modul",{"_index":364,"t":{"37":{"position":[[1365,13]]}}}],["node_modules/gulp/bin/gulp.j",{"_index":1504,"t":{"245":{"position":[[344,29],[394,29]]}}}],["non",{"_index":2219,"t":{"439":{"position":[[306,3]]}}}],["none",{"_index":1290,"t":{"197":{"position":[[395,4]]}}}],["note",{"_index":720,"t":{"106":{"position":[[439,5]]},"108":{"position":[[163,5]]},"129":{"position":[[107,4],[254,4],[1574,4],[2983,4]]},"131":{"position":[[248,4],[2032,4],[5352,4]]},"136":{"position":[[0,5]]},"148":{"position":[[1058,4]]},"165":{"position":[[857,5]]},"167":{"position":[[396,5]]},"183":{"position":[[542,5]]},"187":{"position":[[647,5],[1253,5]]},"272":{"position":[[766,4]]},"275":{"position":[[274,4]]},"287":{"position":[[493,4]]},"311":{"position":[[993,4]]},"397":{"position":[[121,4]]},"432":{"position":[[144,4]]},"473":{"position":[[1125,4],[1888,4]]},"533":{"position":[[135,4]]}}}],["notfoundexcept",{"_index":1474,"t":{"243":{"position":[[708,17]]},"252":{"position":[[508,17]]}}}],["noth",{"_index":997,"t":{"133":{"position":[[460,7]]},"407":{"position":[[794,7]]}}}],["notic",{"_index":845,"t":{"129":{"position":[[794,6]]},"541":{"position":[[890,6]]}}}],["notifications\"]').text",{"_index":1358,"t":{"209":{"position":[[581,24]]}}}],["noun",{"_index":151,"t":{"15":{"position":[[110,4],[183,4],[367,4],[448,4],[456,4]]}}}],["now",{"_index":506,"t":{"53":{"position":[[537,3]]},"57":{"position":[[123,3]]},"63":{"position":[[273,3]]},"65":{"position":[[135,3]]},"70":{"position":[[70,3],[116,3]]},"117":{"position":[[76,4],[365,3],[489,4]]},"224":{"position":[[857,3]]},"311":{"position":[[568,3]]},"467":{"position":[[20,4]]},"473":{"position":[[2307,3]]},"507":{"position":[[194,3]]},"526":{"position":[[351,4]]}}}],["npm",{"_index":306,"t":{"31":{"position":[[73,3]]},"33":{"position":[[86,3],[165,3]]},"37":{"position":[[2029,3]]},"69":{"position":[[32,3]]},"70":{"position":[[36,3]]},"136":{"position":[[50,3]]},"138":{"position":[[113,3],[150,3]]},"140":{"position":[[23,3]]},"142":{"position":[[0,3]]},"159":{"position":[[105,3],[180,3],[357,3]]},"245":{"position":[[164,3],[171,3],[232,3],[424,3]]},"360":{"position":[[349,3],[370,3]]},"370":{"position":[[20,3]]},"384":{"position":[[174,3]]},"392":{"position":[[192,3],[255,3],[316,3]]},"395":{"position":[[169,3]]},"399":{"position":[[135,3]]},"405":{"position":[[109,3]]},"407":{"position":[[0,3],[60,3],[237,3],[390,3],[753,3]]},"416":{"position":[[276,3]]},"480":{"position":[[65,3]]},"524":{"position":[[370,3],[413,3],[507,3],[548,3],[589,3],[621,3],[650,3]]}}}],["npx",{"_index":635,"t":{"88":{"position":[[0,3]]},"90":{"position":[[25,3]]},"92":{"position":[[12,3],[83,3],[174,3],[281,3],[417,3]]},"364":{"position":[[92,3]]},"397":{"position":[[0,3]]},"399":{"position":[[57,3]]},"401":{"position":[[0,3]]},"405":{"position":[[69,3]]},"407":{"position":[[152,3],[705,3]]}}}],["null",{"_index":1136,"t":{"161":{"position":[[744,5]]},"301":{"position":[[920,5],[1033,5]]}}}],["nullabl",{"_index":1746,"t":{"301":{"position":[[886,9],[999,9]]}}}],["number",{"_index":835,"t":{"129":{"position":[[326,6],[1614,6],[1674,6],[3019,6],[3079,6]]},"146":{"position":[[186,6],[286,6]]},"148":{"position":[[440,6],[465,7]]},"197":{"position":[[287,8],[301,8],[319,8],[639,6]]},"314":{"position":[[308,6]]},"316":{"position":[[51,7],[77,6]]},"486":{"position":[[799,6],[1476,6]]},"505":{"position":[[4786,6]]}}}],["numer",{"_index":1845,"t":{"329":{"position":[[359,8]]},"505":{"position":[[4662,8]]}}}],["nuxt",{"_index":2011,"t":{"360":{"position":[[283,4]]},"524":{"position":[[636,5]]}}}],["nx",{"_index":1227,"t":{"183":{"position":[[539,2]]}}}],["nyc",{"_index":1330,"t":{"205":{"position":[[585,3]]}}}],["o'connel",{"_index":1333,"t":{"205":{"position":[[626,9]]}}}],["object",{"_index":154,"t":{"15":{"position":[[150,6],[256,7]]},"37":{"position":[[796,6]]},"39":{"position":[[488,8]]},"129":{"position":[[2155,6]]},"131":{"position":[[619,6],[854,6],[893,6],[1200,6],[4268,6],[4329,6]]},"243":{"position":[[1482,6],[1628,7]]},"277":{"position":[[33,7],[95,6],[350,6],[452,6]]},"311":{"position":[[48,7],[366,8],[1202,6],[1281,7]]},"327":{"position":[[333,7]]},"346":{"position":[[87,8]]},"461":{"position":[[413,7]]},"505":{"position":[[589,8],[618,7],[3666,7]]},"539":{"position":[[497,7],[621,7]]},"563":{"position":[[81,6]]}}}],["objectid",{"_index":2248,"t":{"455":{"position":[[265,9]]}}}],["observ",{"_index":2553,"t":{"521":{"position":[[601,7]]}}}],["occlud",{"_index":2676,"t":{"551":{"position":[[273,7]]}}}],["occur",{"_index":1744,"t":{"301":{"position":[[823,5]]},"309":{"position":[[22,5]]}}}],["offer",{"_index":457,"t":{"47":{"position":[[122,6]]},"509":{"position":[[273,5]]}}}],["offici",{"_index":462,"t":{"49":{"position":[[9,8]]},"519":{"position":[[476,8]]}}}],["oidc",{"_index":2005,"t":{"360":{"position":[[60,6],[428,5],[479,4],[853,4]]}}}],["oidcmock__base_url",{"_index":2008,"t":{"360":{"position":[[203,20]]}}}],["ok",{"_index":2327,"t":{"473":{"position":[[2675,2],[3693,2]]}}}],["okay",{"_index":1427,"t":{"232":{"position":[[78,4]]},"323":{"position":[[370,4]]}}}],["old",{"_index":1603,"t":{"275":{"position":[[397,3]]},"299":{"position":[[57,3]]},"421":{"position":[[813,3],[1108,3],[1163,3]]},"441":{"position":[[119,3]]},"519":{"position":[[381,3]]}}}],["olli",{"_index":1443,"t":{"234":{"position":[[712,5]]}}}],["on",{"_index":214,"t":{"19":{"position":[[443,3]]},"45":{"position":[[413,3],[457,3],[531,3],[566,3]]},"106":{"position":[[148,3]]},"110":{"position":[[146,3],[191,5]]},"129":{"position":[[381,3]]},"131":{"position":[[789,3],[2417,3],[3895,3],[4952,3]]},"151":{"position":[[96,3]]},"183":{"position":[[818,3]]},"209":{"position":[[443,3]]},"234":{"position":[[277,3],[362,3]]},"279":{"position":[[364,3],[547,3]]},"289":{"position":[[860,3]]},"291":{"position":[[38,4]]},"309":{"position":[[1050,3]]},"323":{"position":[[202,3]]},"329":{"position":[[781,3],[1197,3],[1335,3]]},"344":{"position":[[2155,4]]},"399":{"position":[[30,3]]},"441":{"position":[[150,3]]},"453":{"position":[[10,3],[42,3]]},"473":{"position":[[1993,3],[2117,3]]},"491":{"position":[[51,3]]},"505":{"position":[[216,3],[993,3],[1351,3],[4314,3]]},"541":{"position":[[248,3],[434,3]]},"545":{"position":[[592,3]]}}}],["onassetcr",{"_index":2565,"t":{"521":{"position":[[1072,14]]}}}],["onboard",{"_index":235,"t":{"23":{"position":[[107,10]]}}}],["onc",{"_index":308,"t":{"33":{"position":[[0,4]]},"209":{"position":[[795,4]]},"275":{"position":[[546,4]]},"340":{"position":[[1935,5]]},"403":{"position":[[92,4]]},"428":{"position":[[229,4]]},"439":{"position":[[188,4]]},"476":{"position":[[337,4],[521,4],[784,4]]},"505":{"position":[[3936,4],[4398,4]]}}}],["onchangepag",{"_index":2560,"t":{"521":{"position":[[886,13]]}}}],["onchangepres",{"_index":2564,"t":{"521":{"position":[[995,17]]}}}],["onclick",{"_index":622,"t":{"84":{"position":[[269,11]]}}}],["oneof",{"_index":1463,"t":{"243":{"position":[[287,6]]}}}],["onetoon",{"_index":2249,"t":{"455":{"position":[[275,12]]}}}],["onmount",{"_index":2559,"t":{"521":{"position":[[841,8]]}}}],["onredo",{"_index":2562,"t":{"521":{"position":[[954,7]]}}}],["onundo",{"_index":2561,"t":{"521":{"position":[[943,6]]}}}],["open",{"_index":1959,"t":{"344":{"position":[[1885,4]]},"432":{"position":[[23,5]]},"434":{"position":[[269,4]]},"443":{"position":[[236,4]]},"505":{"position":[[3834,5]]},"521":{"position":[[522,7],[1528,4]]},"526":{"position":[[214,5]]},"531":{"position":[[410,4],[1033,4]]},"545":{"position":[[156,4]]}}}],["open/clos",{"_index":2622,"t":{"531":{"position":[[375,11]]}}}],["openapi",{"_index":1529,"t":{"252":{"position":[[135,7]]},"262":{"position":[[67,7],[136,7],[207,7]]},"537":{"position":[[244,8]]}}}],["openid",{"_index":1981,"t":{"356":{"position":[[122,6]]},"360":{"position":[[45,6]]}}}],["openldap",{"_index":2028,"t":{"362":{"position":[[119,8],[169,8],[227,8],[273,8],[331,8]]}}}],["oper",{"_index":1619,"t":{"277":{"position":[[742,9]]},"279":{"position":[[93,9],[140,9]]},"320":{"position":[[131,8]]},"412":{"position":[[225,10]]},"505":{"position":[[3325,10]]},"521":{"position":[[376,11],[506,10],[983,11]]},"535":{"position":[[163,9]]}}}],["oplogs",{"_index":2069,"t":{"373":{"position":[[232,9]]}}}],["opt/keycloak/bin/kc.sh",{"_index":2002,"t":{"358":{"position":[[546,23],[764,23]]},"368":{"position":[[173,23],[958,23]]}}}],["optim",{"_index":2257,"t":{"461":{"position":[[432,12]]},"505":{"position":[[2175,9]]}}}],["optimis",{"_index":2632,"t":{"531":{"position":[[974,12]]}}}],["option",{"_index":253,"t":{"25":{"position":[[215,8]]},"33":{"position":[[267,8]]},"82":{"position":[[107,6],[199,6]]},"115":{"position":[[48,7],[366,7]]},"117":{"position":[[34,8]]},"119":{"position":[[480,7]]},"121":{"position":[[49,7]]},"129":{"position":[[54,7],[149,7],[304,7],[346,7],[413,7],[487,6],[497,9],[618,11],[639,7],[681,7],[1073,6],[1117,7],[1134,6],[1178,7],[1404,7],[1634,7],[1647,7],[1859,6],[2380,6],[2424,7],[2461,6],[2503,6],[2522,7],[2582,11],[2603,7],[2699,7],[3039,7],[3052,7],[3150,7],[3192,7],[3229,7],[3303,6],[3364,7],[3401,6],[3460,7],[3522,7],[3546,7],[3618,6],[3647,9],[3676,7],[3714,6],[3758,7],[3795,6],[3839,7],[3876,7]]},"131":{"position":[[3365,6],[3566,6],[3727,6]]},"133":{"position":[[1460,6]]},"163":{"position":[[233,8]]},"358":{"position":[[209,6]]},"416":{"position":[[218,7],[310,8]]}}}],["optionaldata",{"_index":1667,"t":{"289":{"position":[[382,12]]}}}],["orchestr",{"_index":1233,"t":{"185":{"position":[[168,12],[1244,12]]},"287":{"position":[[558,13]]},"348":{"position":[[30,14],[146,13]]},"535":{"position":[[424,12]]},"543":{"position":[[521,14]]}}}],["order",{"_index":1012,"t":{"136":{"position":[[89,5]]},"148":{"position":[[586,5]]},"170":{"position":[[384,5]]},"172":{"position":[[140,5]]},"211":{"position":[[189,5]]},"248":{"position":[[68,5]]},"272":{"position":[[255,6]]},"316":{"position":[[474,5]]},"323":{"position":[[116,5]]},"368":{"position":[[743,5]]},"486":{"position":[[1559,5]]},"524":{"position":[[323,5]]},"531":{"position":[[3,5]]},"539":{"position":[[231,5]]},"561":{"position":[[38,6]]},"565":{"position":[[216,5]]}}}],["organ",{"_index":108,"t":{"9":{"position":[[1415,9]]},"133":{"position":[[1048,9]]},"303":{"position":[[115,8]]}}}],["organization'",{"_index":270,"t":{"27":{"position":[[159,14]]}}}],["origin",{"_index":856,"t":{"129":{"position":[[1201,8]]},"187":{"position":[[586,8]]},"291":{"position":[[51,8]]},"340":{"position":[[724,8]]},"473":{"position":[[2762,6],[3780,6],[4671,6]]}}}],["orm",{"_index":1949,"t":{"344":{"position":[[1154,3],[1601,3],[1757,3]]},"395":{"position":[[246,3]]},"397":{"position":[[10,3],[102,3]]},"399":{"position":[[67,3]]},"401":{"position":[[10,3]]},"405":{"position":[[79,3]]},"407":{"position":[[162,3],[715,3]]},"416":{"position":[[46,3]]},"461":{"position":[[487,4]]}}}],["orm.config.t",{"_index":2146,"t":{"403":{"position":[[314,13]]}}}],["orm/entitymanag",{"_index":1942,"t":{"344":{"position":[[565,17]]}}}],["orphanremov",{"_index":2251,"t":{"455":{"position":[[319,14]]}}}],["other'",{"_index":2499,"t":{"505":{"position":[[2574,7]]},"507":{"position":[[81,7]]}}}],["othermodul",{"_index":2656,"t":{"541":{"position":[[875,11],[974,12]]}}}],["out",{"_index":217,"t":{"19":{"position":[[595,3],[1043,3]]},"148":{"position":[[639,3]]},"316":{"position":[[527,3]]},"338":{"position":[[150,3]]},"356":{"position":[[199,3]]},"412":{"position":[[166,3]]},"445":{"position":[[17,3]]},"473":{"position":[[1408,3]]}}}],["outcom",{"_index":41,"t":{"5":{"position":[[341,8]]},"325":{"position":[[416,7]]}}}],["outdat",{"_index":662,"t":{"98":{"position":[[147,8]]},"418":{"position":[[177,8],[233,8]]}}}],["outer",{"_index":1794,"t":{"311":{"position":[[194,5]]},"329":{"position":[[966,5],[1071,5]]}}}],["outgo",{"_index":2646,"t":{"539":{"position":[[40,8]]}}}],["outlin",{"_index":57,"t":{"9":{"position":[[40,7],[134,7],[247,7],[525,8],[939,7],[1205,8]]},"11":{"position":[[24,9]]},"45":{"position":[[437,8]]}}}],["output",{"_index":862,"t":{"129":{"position":[[1465,6]]},"199":{"position":[[297,7]]},"295":{"position":[[1012,6]]},"414":{"position":[[406,6]]},"531":{"position":[[547,6]]}}}],["output.json",{"_index":650,"t":{"92":{"position":[[473,11]]}}}],["outsid",{"_index":928,"t":{"131":{"position":[[1055,7],[3258,7]]},"187":{"position":[[709,7]]},"305":{"position":[[153,7],[530,7]]},"323":{"position":[[333,8]]},"352":{"position":[[692,7]]},"514":{"position":[[191,7]]},"535":{"position":[[126,7]]},"537":{"position":[[67,7]]}}}],["over",{"_index":797,"t":{"123":{"position":[[145,4]]},"131":{"position":[[4776,4]]},"133":{"position":[[19,4]]},"340":{"position":[[2014,4]]},"375":{"position":[[260,4]]},"531":{"position":[[659,4]]}}}],["overal",{"_index":2046,"t":{"366":{"position":[[396,7]]}}}],["overengini",{"_index":2630,"t":{"531":{"position":[[944,15]]}}}],["overrid",{"_index":785,"t":{"119":{"position":[[399,8]]},"289":{"position":[[523,9]]},"338":{"position":[[442,11]]},"407":{"position":[[464,8],[476,8]]}}}],["overridden",{"_index":429,"t":{"45":{"position":[[70,10]]}}}],["overview",{"_index":2545,"t":{"519":{"position":[[130,8]]}}}],["overwrit",{"_index":752,"t":{"112":{"position":[[281,10]]},"123":{"position":[[49,9]]},"272":{"position":[[547,9]]}}}],["overwrite_setting_show_setup_wizard='complet",{"_index":2110,"t":{"375":{"position":[[1398,47]]}}}],["overwritten",{"_index":1892,"t":{"338":{"position":[[685,11]]}}}],["owner",{"_index":1614,"t":{"277":{"position":[[488,5]]},"455":{"position":[[306,6]]}}}],["p",{"_index":762,"t":{"115":{"position":[[389,2],[431,4],[527,2],[600,4]]},"358":{"position":[[422,1],[437,1],[640,1],[655,1]]},"360":{"position":[[499,1],[873,1]]},"362":{"position":[[188,1],[292,1]]},"375":{"position":[[1449,1]]},"473":{"position":[[1736,1]]}}}],["p27030:27017",{"_index":2065,"t":{"373":{"position":[[184,12]]}}}],["p>thi",{"_index":553,"t":{"63":{"position":[[216,7]]}}}],["packag",{"_index":384,"t":{"37":{"position":[[2009,7],[2033,7]]},"86":{"position":[[22,7]]},"245":{"position":[[134,8]]},"392":{"position":[[162,8]]},"521":{"position":[[1412,7]]}}}],["package.json",{"_index":387,"t":{"37":{"position":[[2057,12]]}}}],["pad",{"_index":618,"t":{"84":{"position":[[230,8]]},"439":{"position":[[248,4],[381,5],[453,3]]},"445":{"position":[[98,4]]}}}],["pad_options_show_chat=\"tru",{"_index":2276,"t":{"473":{"position":[[684,28]]}}}],["page",{"_index":153,"t":{"15":{"position":[[145,4],[176,4],[251,4],[590,5]]},"37":{"position":[[782,6],[791,4]]},"39":{"position":[[483,4]]},"45":{"position":[[461,4],[535,4]]},"51":{"position":[[21,4],[68,5]]},"55":{"position":[[24,5]]},"61":{"position":[[64,5]]},"63":{"position":[[265,4],[321,5]]},"65":{"position":[[92,4],[116,4],[127,4],[186,5]]},"76":{"position":[[121,6],[216,5]]},"151":{"position":[[476,4]]},"153":{"position":[[125,5]]},"163":{"position":[[200,5],[341,5],[356,4]]},"165":{"position":[[216,4]]},"183":{"position":[[856,7]]},"185":{"position":[[97,4],[112,4],[1054,4],[1080,4],[1138,5],[1146,4],[2166,4]]},"521":{"position":[[908,4]]}}}],["page.j",{"_index":544,"t":{"63":{"position":[[36,8],[64,7]]}}}],["page.md",{"_index":556,"t":{"65":{"position":[[39,8],[70,7]]},"76":{"position":[[172,9]]}}}],["page/vuetify.options.t",{"_index":753,"t":{"112":{"position":[[305,44]]}}}],["src/themes/bas",{"_index":750,"t":{"112":{"position":[[235,16]]}}}],["ssl",{"_index":2419,"t":{"486":{"position":[[902,3]]}}}],["sso",{"_index":1984,"t":{"356":{"position":[[195,3]]}}}],["stabil",{"_index":404,"t":{"41":{"position":[[153,10]]},"195":{"position":[[306,9]]}}}],["stabl",{"_index":406,"t":{"43":{"position":[[29,6]]},"157":{"position":[[453,7]]}}}],["stable_test",{"_index":405,"t":{"43":{"position":[[0,13]]},"45":{"position":[[133,12],[191,12],[676,12]]}}}],["stack",{"_index":1695,"t":{"293":{"position":[[819,6]]}}}],["stage",{"_index":294,"t":{"29":{"position":[[490,7],[664,7],[721,7]]},"43":{"position":[[247,7]]},"45":{"position":[[957,7]]},"266":{"position":[[398,7]]},"272":{"position":[[363,5]]}}}],["staging.env.json",{"_index":296,"t":{"29":{"position":[[597,16],[831,16]]}}}],["staging_test",{"_index":412,"t":{"43":{"position":[[210,14]]},"45":{"position":[[645,13],[933,13]]}}}],["stagingtemplate.env.json",{"_index":295,"t":{"29":{"position":[[538,24]]},"37":{"position":[[1233,24]]}}}],["standalon",{"_index":536,"t":{"61":{"position":[[53,10]]},"514":{"position":[[402,10]]}}}],["standard",{"_index":392,"t":{"39":{"position":[[173,10]]},"100":{"position":[[29,10],[234,10]]},"119":{"position":[[412,8]]}}}],["start",{"_index":239,"t":{"25":{"position":[[15,8]]},"138":{"position":[[120,5]]},"146":{"position":[[93,5]]},"148":{"position":[[420,5]]},"187":{"position":[[0,8]]},"199":{"position":[[1112,8]]},"207":{"position":[[187,5],[360,5]]},"245":{"position":[[205,5]]},"266":{"position":[[200,6]]},"316":{"position":[[31,5]]},"344":{"position":[[1983,7]]},"358":{"position":[[219,5]]},"360":{"position":[[418,5]]},"368":{"position":[[89,5],[261,5],[367,8],[581,5]]},"373":{"position":[[245,5]]},"384":{"position":[[183,5]]},"392":{"position":[[199,5],[289,5]]},"414":{"position":[[191,5]]},"473":{"position":[[587,6],[1687,5]]},"478":{"position":[[49,5]]},"480":{"position":[[3,5],[163,5],[332,5]]},"488":{"position":[[535,8]]},"491":{"position":[[393,6]]},"507":{"position":[[18,5]]}}}],["start:server:dev",{"_index":2609,"t":{"524":{"position":[[515,16]]}}}],["start:worker:dev",{"_index":2610,"t":{"524":{"position":[[556,16]]}}}],["startup",{"_index":2043,"t":{"366":{"position":[[17,7],[97,8]]}}}],["state",{"_index":685,"t":{"100":{"position":[[467,6]]},"129":{"position":[[3181,5]]},"131":{"position":[[2950,5],[3137,5]]},"185":{"position":[[272,8],[604,5],[1605,8]]},"344":{"position":[[340,5],[425,6],[740,5]]},"397":{"position":[[238,5]]},"414":{"position":[[517,6]]},"493":{"position":[[558,5]]},"505":{"position":[[2013,5],[2901,5]]},"514":{"position":[[1014,5]]},"517":{"position":[[208,6],[287,6]]},"519":{"position":[[297,6]]},"521":{"position":[[76,5],[323,6]]}}}],["stateless",{"_index":1238,"t":{"185":{"position":[[465,9],[1785,9]]}}}],["statement",{"_index":42,"t":{"7":{"position":[[7,10],[80,10],[155,10],[194,10],[292,11],[368,11]]},"567":{"position":[[194,9],[232,9],[302,10]]}}}],["static",{"_index":188,"t":{"17":{"position":[[37,6]]},"67":{"position":[[16,6],[92,6]]},"69":{"position":[[50,6]]},"78":{"position":[[93,6]]}}}],["static/img/docusaurus.png",{"_index":586,"t":{"78":{"position":[[110,28]]}}}],["statistics_reporting=fals",{"_index":2101,"t":{"375":{"position":[[1119,26]]}}}],["statu",{"_index":1448,"t":{"238":{"position":[[66,6]]},"243":{"position":[[264,7],[577,7],[633,7],[689,7]]},"252":{"position":[[326,7],[377,7],[433,7],[489,7]]},"403":{"position":[[68,6]]}}}],["stay",{"_index":814,"t":{"126":{"position":[[531,5]]},"146":{"position":[[382,4]]},"157":{"position":[[447,5]]},"314":{"position":[[205,4]]},"531":{"position":[[1097,4]]}}}],["step",{"_index":12,"t":{"2":{"position":[[121,4]]},"9":{"position":[[276,5],[964,5],[1261,5]]},"11":{"position":[[433,5]]},"13":{"position":[[0,4],[42,4],[612,4]]},"19":{"position":[[0,4],[42,4],[186,4],[330,5],[454,4],[569,4],[610,5],[687,4],[917,4],[1054,5]]},"21":{"position":[[141,4]]},"27":{"position":[[47,6]]},"37":{"position":[[865,4]]},"39":{"position":[[501,4]]},"126":{"position":[[160,4]]},"245":{"position":[[194,4]]},"275":{"position":[[76,4],[347,4],[380,4],[473,4],[493,4]]},"348":{"position":[[241,6]]},"360":{"position":[[107,5]]},"392":{"position":[[278,4]]},"432":{"position":[[13,5]]},"473":{"position":[[100,6]]},"505":{"position":[[3038,4],[3393,4],[3758,4]]}}}],["step_definit",{"_index":347,"t":{"37":{"position":[[845,17]]}}}],["stick",{"_index":1429,"t":{"232":{"position":[[161,5]]},"323":{"position":[[276,5]]}}}],["still",{"_index":860,"t":{"129":{"position":[[1429,5]]},"131":{"position":[[4696,5]]},"133":{"position":[[664,5]]},"268":{"position":[[24,5],[123,5]]},"299":{"position":[[83,5]]},"329":{"position":[[678,5]]},"414":{"position":[[354,5]]},"418":{"position":[[494,5]]},"439":{"position":[[438,5]]},"535":{"position":[[1087,5]]},"545":{"position":[[124,5]]}}}],["stop",{"_index":2398,"t":{"486":{"position":[[260,4]]}}}],["storag",{"_index":2411,"t":{"486":{"position":[[706,7],[859,7],[913,7]]},"488":{"position":[[915,8]]},"491":{"position":[[325,7]]},"493":{"position":[[692,7]]},"495":{"position":[[577,8]]},"500":{"position":[[140,8],[185,7],[275,8]]},"503":{"position":[[36,7],[139,7],[301,8],[404,7]]},"505":{"position":[[1497,7],[1541,8],[1633,7],[1673,7],[1927,7],[2053,8],[2628,7],[4580,7]]},"509":{"position":[[214,7],[314,8]]},"514":{"position":[[1062,8]]}}}],["storage:dev",{"_index":2608,"t":{"524":{"position":[[438,11]]}}}],["store",{"_index":395,"t":{"39":{"position":[[377,6]]},"155":{"position":[[366,10]]},"157":{"position":[[353,6]]},"161":{"position":[[75,5]]},"165":{"position":[[191,5],[702,10]]},"177":{"position":[[142,6]]},"185":{"position":[[1892,6],[2057,6]]},"195":{"position":[[149,7]]},"224":{"position":[[45,7]]},"228":{"position":[[15,5],[159,5],[550,6]]},"230":{"position":[[23,6]]},"234":{"position":[[527,7],[566,5],[585,6]]},"403":{"position":[[15,6]]},"447":{"position":[[28,6]]},"449":{"position":[[180,6]]},"455":{"position":[[486,5]]},"461":{"position":[[38,5],[222,6]]},"491":{"position":[[292,6]]},"493":{"position":[[104,6],[195,6],[654,6]]},"498":{"position":[[135,5]]},"505":{"position":[[750,5],[868,6],[1155,6],[1774,5],[1841,7],[2075,6],[2118,5],[2189,7],[2260,7],[3238,6],[3649,6],[4645,7]]},"507":{"position":[[213,6],[374,7]]},"514":{"position":[[920,7],[1124,5]]}}}],["store.t",{"_index":2549,"t":{"521":{"position":[[136,8]]}}}],["store/yourmodul",{"_index":1406,"t":{"228":{"position":[[197,21]]}}}],["stores/setup.t",{"_index":2541,"t":{"517":{"position":[[0,15]]}}}],["stori",{"_index":25,"t":{"5":{"position":[[91,5]]}}}],["straightforward",{"_index":866,"t":{"129":{"position":[[1799,16]]}}}],["strategi",{"_index":667,"t":{"100":{"position":[[17,11]]},"148":{"position":[[375,9]]},"309":{"position":[[180,10]]},"470":{"position":[[5,8],[264,8]]},"476":{"position":[[46,8]]}}}],["streamlin",{"_index":1566,"t":{"268":{"position":[[256,10]]}}}],["string",{"_index":134,"t":{"13":{"position":[[158,10],[240,10],[311,8],[418,8],[545,8]]},"126":{"position":[[290,7],[843,6]]},"129":{"position":[[853,6],[887,7]]},"131":{"position":[[5276,6]]},"197":{"position":[[460,8],[530,7],[554,7],[619,6]]},"243":{"position":[[1535,6]]},"258":{"position":[[209,7]]},"289":{"position":[[1002,7],[1017,6]]},"293":{"position":[[677,7],[713,7]]},"295":{"position":[[261,7]]},"299":{"position":[[72,7]]},"301":{"position":[[1157,7]]},"457":{"position":[[201,7]]},"473":{"position":[[1182,7]]},"486":{"position":[[440,6]]},"551":{"position":[[549,6]]},"561":{"position":[[130,7],[205,6],[264,6]]}}}],["strong",{"_index":1224,"t":{"183":{"position":[[399,6]]},"248":{"position":[[87,6]]}}}],["strongli",{"_index":1075,"t":{"148":{"position":[[1092,8]]},"318":{"position":[[3,8]]},"340":{"position":[[2151,8]]}}}],["structur",{"_index":202,"t":{"19":{"position":[[23,9]]},"35":{"position":[[187,9]]},"110":{"position":[[381,10]]},"129":{"position":[[920,9],[1215,9],[1379,9]]},"131":{"position":[[812,9],[2231,9]]},"133":{"position":[[1446,10]]},"205":{"position":[[85,9]]},"234":{"position":[[144,10]]},"281":{"position":[[30,9]]},"325":{"position":[[194,9]]},"329":{"position":[[20,10],[878,9],[1412,10]]},"344":{"position":[[850,9]]},"395":{"position":[[38,9]]},"457":{"position":[[72,9]]},"459":{"position":[[123,9]]},"461":{"position":[[497,9]]},"567":{"position":[[20,9]]}}}],["stub",{"_index":1400,"t":{"224":{"position":[[847,5]]}}}],["student",{"_index":1615,"t":{"277":{"position":[[604,7]]},"325":{"position":[[672,7]]},"470":{"position":[[43,9]]}}}],["studio",{"_index":1024,"t":{"144":{"position":[[20,6],[286,6]]}}}],["style",{"_index":399,"t":{"39":{"position":[[841,7]]},"84":{"position":[[160,8]]},"123":{"position":[[219,5]]},"131":{"position":[[586,8]]},"177":{"position":[[59,5]]},"332":{"position":[[214,5]]}}}],["sub",{"_index":942,"t":{"131":{"position":[[1853,3],[5525,3]]},"205":{"position":[[298,3]]},"234":{"position":[[215,3]]}}}],["sub)class",{"_index":2671,"t":{"549":{"position":[[365,11]]}}}],["subcompon",{"_index":900,"t":{"129":{"position":[[3977,13]]},"131":{"position":[[940,13]]}}}],["subfold",{"_index":1231,"t":{"183":{"position":[[822,9]]},"187":{"position":[[44,9]]}}}],["subject",{"_index":1057,"t":{"148":{"position":[[405,7]]},"316":{"position":[[16,7]]}}}],["submiss",{"_index":1618,"t":{"277":{"position":[[658,11]]}}}],["submissioncontainerelementrespons",{"_index":1459,"t":{"243":{"position":[[212,34]]}}}],["submodul",{"_index":1750,"t":{"303":{"position":[[47,10]]},"305":{"position":[[380,10],[520,9],[691,12]]},"307":{"position":[[177,11],[602,12]]}}}],["submoduleservicenam",{"_index":1758,"t":{"305":{"position":[[663,20]]}}}],["subpag",{"_index":1232,"t":{"185":{"position":[[140,7],[1179,7],[1300,8]]}}}],["subscrib",{"_index":2460,"t":{"491":{"position":[[400,11]]},"493":{"position":[[373,11]]},"505":{"position":[[1323,9],[1440,10]]},"514":{"position":[[606,11]]}}}],["subsequ",{"_index":2175,"t":{"414":{"position":[[36,10]]},"441":{"position":[[5,10]]}}}],["substitut",{"_index":74,"t":{"9":{"position":[[323,11]]},"531":{"position":[[463,12]]}}}],["subtask",{"_index":2172,"t":{"412":{"position":[[681,8]]}}}],["succe",{"_index":1871,"t":{"334":{"position":[[717,7]]}}}],["successfulli",{"_index":2352,"t":{"473":{"position":[[3092,13]]}}}],["such",{"_index":115,"t":{"11":{"position":[[114,4]]},"338":{"position":[[33,5]]},"348":{"position":[[248,4]]},"432":{"position":[[247,5]]}}}],["suffici",{"_index":1740,"t":{"301":{"position":[[452,11]]},"329":{"position":[[430,10]]},"551":{"position":[[246,12]]}}}],["suffix",{"_index":2702,"t":{"563":{"position":[[57,6]]}}}],["suggest",{"_index":1209,"t":{"177":{"position":[[310,11]]},"277":{"position":[[157,8]]},"386":{"position":[[48,9]]}}}],["suit",{"_index":678,"t":{"100":{"position":[[221,5]]},"195":{"position":[[109,6]]}}}],["suitabl",{"_index":2494,"t":{"505":{"position":[[2247,8]]}}}],["summari",{"_index":1453,"t":{"243":{"position":[[53,8],[1295,8]]},"252":{"position":[[225,8]]}}}],["super",{"_index":1691,"t":{"293":{"position":[[723,8]]}}}],["supertest",{"_index":1886,"t":{"338":{"position":[[140,9]]}}}],["support",{"_index":260,"t":{"25":{"position":[[306,7]]},"37":{"position":[[702,8]]},"39":{"position":[[447,9]]},"72":{"position":[[11,8]]},"76":{"position":[[27,10]]},"78":{"position":[[28,10]]},"80":{"position":[[25,9]]},"129":{"position":[[1962,7]]},"397":{"position":[[170,9]]},"503":{"position":[[62,7]]},"514":{"position":[[386,10]]}}}],["sure",{"_index":268,"t":{"27":{"position":[[109,4]]},"98":{"position":[[16,4]]},"129":{"position":[[1442,4]]},"148":{"position":[[600,4]]},"187":{"position":[[1270,4]]},"245":{"position":[[5,4]]},"283":{"position":[[229,4]]},"316":{"position":[[488,4]]},"352":{"position":[[70,4]]},"392":{"position":[[5,4]]},"473":{"position":[[2074,4]]},"505":{"position":[[4463,5]]}}}],["surfac",{"_index":783,"t":{"119":{"position":[[122,8],[196,8],[205,8],[424,7]]},"121":{"position":[[136,9]]}}}],["sv",{"_index":2465,"t":{"498":{"position":[[98,3]]}}}],["swagger",{"_index":1494,"t":{"243":{"position":[[1554,7]]}}}],["sync",{"_index":1877,"t":{"336":{"position":[[449,4]]},"470":{"position":[[26,4],[228,7]]},"476":{"position":[[41,4],[66,4],[321,4],[407,4],[505,4],[589,4],[711,4],[767,4]]},"480":{"position":[[92,4]]}}}],["synchron",{"_index":2171,"t":{"412":{"position":[[640,11]]},"478":{"position":[[59,15]]},"480":{"position":[[13,15],[173,15],[237,11],[307,15],[422,15]]},"482":{"position":[[8,15]]},"493":{"position":[[337,12]]},"503":{"position":[[107,16],[266,17]]},"505":{"position":[[260,12],[716,16],[760,11],[1580,15],[1907,15],[2492,15],[3340,13],[4148,12]]},"507":{"position":[[464,15]]},"509":{"position":[[115,15]]},"519":{"position":[[276,13]]}}}],["synchronisiert",{"_index":2281,"t":{"473":{"position":[[796,15]]}}}],["syncronis",{"_index":2597,"t":{"521":{"position":[[2224,14]]}}}],["synonym",{"_index":2391,"t":{"484":{"position":[[41,12]]}}}],["syntax",{"_index":142,"t":{"13":{"position":[[554,6]]},"15":{"position":[[95,7],[168,7],[264,7],[352,7],[433,7],[643,7]]},"80":{"position":[[40,6]]},"82":{"position":[[25,6]]},"241":{"position":[[86,7]]}}}],["system",{"_index":402,"t":{"41":{"position":[[34,6]]},"51":{"position":[[80,7]]},"112":{"position":[[23,6]]},"155":{"position":[[73,8]]},"258":{"position":[[35,6]]},"279":{"position":[[703,7]]},"338":{"position":[[201,6]]},"356":{"position":[[67,6]]},"375":{"position":[[350,7]]},"436":{"position":[[34,6]]},"473":{"position":[[291,7]]},"480":{"position":[[144,6],[218,7]]},"505":{"position":[[1386,6]]},"514":{"position":[[728,7]]},"521":{"position":[[369,6],[499,6]]},"529":{"position":[[156,6],[232,7],[340,6],[508,6]]},"535":{"position":[[184,6],[299,7]]},"537":{"position":[[79,7]]},"539":{"position":[[219,8],[262,7]]}}}],["systemid",{"_index":1690,"t":{"293":{"position":[[702,10],[871,9],[986,9],[1048,10]]}}}],["sébastien",{"_index":496,"t":{"53":{"position":[[265,9]]}}}],["t",{"_index":1612,"t":{"277":{"position":[[309,3]]}}}],["tab",{"_index":2125,"t":{"390":{"position":[[117,4]]},"432":{"position":[[63,4]]},"443":{"position":[[241,4]]},"526":{"position":[[127,3]]}}}],["tabl",{"_index":56,"t":{"9":{"position":[[19,6],[161,7],[365,6],[1029,5],[1110,5]]},"11":{"position":[[459,6]]},"131":{"position":[[294,5],[300,7],[499,8],[962,6],[1320,5],[1340,5],[1373,5],[1585,5],[1642,5],[1836,5],[2082,5],[2565,5],[3017,5],[3815,5],[3933,6],[4080,5],[4599,5],[4765,5],[4825,5],[5104,5],[5161,5]]},"133":{"position":[[628,6],[1411,6]]},"366":{"position":[[257,5]]}}}],["tablenam",{"_index":2244,"t":{"455":{"position":[[114,10]]}}}],["tag",{"_index":397,"t":{"39":{"position":[[604,5]]},"41":{"position":[[26,7],[78,4]]},"43":{"position":[[426,4],[494,6]]},"45":{"position":[[14,4],[1242,5]]},"51":{"position":[[76,3]]},"53":{"position":[[386,5]]},"183":{"position":[[619,4]]},"209":{"position":[[125,3]]}}}],["tags.md",{"_index":359,"t":{"37":{"position":[[1167,7]]}}}],["take",{"_index":607,"t":{"82":{"position":[[128,4],[206,4]]},"129":{"position":[[1013,4]]},"131":{"position":[[74,4]]},"165":{"position":[[158,5]]},"216":{"position":[[141,4]]},"268":{"position":[[184,5]]},"329":{"position":[[354,4]]},"336":{"position":[[35,4]]},"338":{"position":[[492,5]]},"412":{"position":[[129,4]]}}}],["taken",{"_index":343,"t":{"37":{"position":[[672,5]]},"205":{"position":[[565,5]]}}}],["talk",{"_index":791,"t":{"123":{"position":[[0,4]]},"148":{"position":[[160,4]]}}}],["target",{"_index":2133,"t":{"395":{"position":[[101,6]]},"470":{"position":[[166,6]]},"480":{"position":[[101,8],[116,8],[279,6],[356,7],[462,6]]},"521":{"position":[[642,9]]}}}],["task",{"_index":116,"t":{"11":{"position":[[122,4]]},"15":{"position":[[704,4]]},"277":{"position":[[476,4],[587,4]]},"410":{"position":[[65,5]]},"486":{"position":[[1411,4],[1486,4]]}}}],["task'",{"_index":1616,"t":{"277":{"position":[[628,6]]}}}],["tbd",{"_index":1424,"t":{"230":{"position":[[4,3]]},"234":{"position":[[718,5]]}}}],["tbodi",{"_index":912,"t":{"131":{"position":[[377,7],[490,8],[1571,7],[1653,8]]}}}],["td",{"_index":948,"t":{"131":{"position":[[2554,4],[2615,5],[4524,4],[4582,5],[4588,4],[4649,5],[5093,4],[5144,5],[5150,4],[5211,5]]}}}],["td>{{user.email}}{{user.id}}{{user.name}}emailidname{{user.email}} cd Make sure you have access to the repository using your organization's credentials.","s":"2. Cloning the Repository","u":"/docs/e2e-system-tests/GettingStarted","h":"#2-cloning-the-repository","p":1},{"i":8,"t":"Setting Up Environment Variables for Dev Environment/Cluster: Duplicate the file devTemplate.env.json and rename the duplicated file to local.env.json inside the env_variables folder. Include the required development namespace URLs for BRB/DBC/NBC. Test user data on development clusters are created using the school API. To retrieve the API keys for all three namespaces, navigate to 1Password (1PW). Contact QA team for the necessary 1Password links. Setting Up Environment Variables for Staging Environment/Cluster: Duplicate the file stagingTemplate.env.json and rename the duplicated file to staging.env.json in the env_variables folder. Include the required staging namespace URLs for BRB/DBC/NBC. Test data on the staging environment are fetched from the seed data on the server. Add the environment-specific credentials to staging.env.json from 1Password (1PW). Ensure all instances are included, as 1Password contains different vaults for each namespace with testing credentials. Contact QA team for the necessary 1Password links.","s":"3. Setting Up Environment Variables for the Testing User Credentials and URLs","u":"/docs/e2e-system-tests/GettingStarted","h":"#3-setting-up-environment-variables-for-the-testing-user-credentials-and-urls","p":1},{"i":10,"t":"Use the following command to install all necessary project dependencies: npm ci","s":"4. Installing Dependencies","u":"/docs/e2e-system-tests/GettingStarted","h":"#4-installing-dependencies","p":1},{"i":12,"t":"Once the setup is complete, you can run the tests: To run all tests in headless mode: npm run cy:headless:stable:local To run tests interactively in the Cypress UI: npm run cy:gui:stable:regression:staging:local For more details on additional configurations and test options, refer to the Executing Tests Guide section in README.","s":"5. Running Cypress Tests","u":"/docs/e2e-system-tests/GettingStarted","h":"#5-running-cypress-tests","p":1},{"i":14,"t":"This guide provides coding conventions and best practices for writing feature files, naming folders, files, methods, and step definitions. Following these conventions will ensure consistency and maintainability across the test framework.","s":"Code Conventions","u":"/docs/e2e-system-tests/CodeConventions","h":"","p":13},{"i":17,"t":"Use the following template when creating a feature file: Feature: Scenario: Given When Then ","s":"Template Structure","u":"/docs/e2e-system-tests/CodeConventions","h":"#template-structure","p":13},{"i":19,"t":"\"When\" statements describe actions or interactions with the application. \"Then\" statements describe the expected results of those actions. Multiple \"When\" statements can precede a single \"Then\" statement: When When Then It is acceptable to have multiple \"Then\" statements: When Then Then Avoid using \"And\" statements. Use only \"When\" and \"Then\" for clarity.","s":"Guidelines","u":"/docs/e2e-system-tests/CodeConventions","h":"#guidelines","p":13},{"i":21,"t":"Leveraging Example Tables with Scenario Outline When you need to run the same scenario with different sets of test data, use Scenario Outline along with Example Tables. This approach makes your test cases more efficient and maintainable. Scenario Outline allows you to define steps with placeholders () that are substituted with values from the Examples table. Example Usage Feature: Create and delete a class As a teacher, I want to create and delete classes so that I can manage my courses effectively. Scenario Outline: Teacher creates and deletes a class Given a user with the role \"\" When the user creates a class named \"\" Then the class \"\" should be visible in the list When the user deletes the class \"\" Then the class \"\" should no longer exist Examples: | role | className | | teacher | Math101 | | teacher | Science202 | | admin | History300 | Explanation The Scenario Outline defines the test steps using parameters like and . The Examples table provides multiple sets of data for each parameter. Each row in theExamples table will generate a separate test case with the specified values. Benefits of Using Scenario Outlines Reusability: Allows you to reuse the same test steps for multiple sets of test data. Maintainability: Reduces code duplication and makes it easier to update test data. Clarity: Keeps the .feature file organized and easy to read.","s":"2. Using Parameters in Feature Files","u":"/docs/e2e-system-tests/CodeConventions","h":"#2-using-parameters-in-feature-files","p":13},{"i":23,"t":"In addition to Scenario Outlines, you can directly use parameters within feature files to specify dynamic values, such as task titles, or other context-specific data. Example: Feature: User login Scenario: Valid user login Given a user with the username \"testUser\" and password \"Password123\" When the user logs in Then the user should be redirected to the dashboard In this case, parameters are directly embedded within the scenario steps without an Examples table.","s":"3. Using Parameters for Special Test Data","u":"/docs/e2e-system-tests/CodeConventions","h":"#3-using-parameters-for-special-test-data","p":13},{"i":25,"t":"Step Definitions Example Ensure that your step definitions are designed to handle the dynamic parameters from your feature files: Given('a user with the role {string}', role => { cy.loginAsRole(role) }) When('the user creates a class named {string}', className => { cy.createClass(className) }) Then('the class {string} should be visible in the list', className => { cy.verifyClassExists(className) }) Then('the class {string} should no longer exist', className => { cy.deleteClass(className) cy.verifyClassDeleted(className) }) Explanation The {string} syntax captures the parameter from the feature file. Each step is linked to the corresponding scenario, making your tests highly modular and reusable.","s":"4. Writing Step Definitions for Scenario Outlines","u":"/docs/e2e-system-tests/CodeConventions","h":"#4-writing-step-definitions-for-scenario-outlines","p":13},{"i":27,"t":"Folder Names Use snake_case for longer folder names. Example: common_logins Feature File Names Syntax: verb + noun Example: createCourse.feature Page Object File Names Syntax: page + noun Example: pageCourse.js, pageCommonCourse.js Class Names Inside Page Objects Syntax: FirstWord_SecondWord Example: Course, Course_Common Method Names Inside Classes Syntax: verb + noun (CamelCase) Example: fillCourseCreationForm() Variable Names Syntax: verb + noun or noun + verb (CamelCase) Example: userEmail, loginButton Data-testid Naming Convention Test IDs are used to select elements on the web page, including buttons, input fields, and sections. Syntax: firstword-secondword-thirdword Example: content-card-task-menu-edit-icon Usage: Assign test IDs to elements using: ","s":"5. Naming Conventions","u":"/docs/e2e-system-tests/CodeConventions","h":"#5-naming-conventions","p":13},{"i":29,"t":"Guidelines Assign test data IDs to a static variable with a # prefix so it indicate as a private variable within the class. Break down complex interactions into smaller methods within the class for better modularity. Refer to examples in the current end to end repository for guidance.","s":"6. Writing Classes and Methods","u":"/docs/e2e-system-tests/CodeConventions","h":"#6-writing-classes-and-methods","p":13},{"i":31,"t":"Step Definition Folder Structure Create a step definition file within cypress/support/step_definitions/ based on the module name (e.g., rooms, courses, teams). The naming convention for step definition files is based on the module name, followed by .spec.js. Example: editCourseSteps.spec.js commonCourseSteps.spec.js (for shared steps across multiple scenarios) Guidelines Follow the same sequence as the feature file for consistency. Create one common step definition file that can be reused across tests within the same module or across modules. For module-specific step definitions, comment out any common steps and include a reference to their location. Example 1: Dedicated Module Step Definitions // editCourseSteps.spec.js Given('a user is logged in', () => { ... }); When('the user edits the course details', () => { ... }); Then('the course should be updated', () => { ... }); Example 2: Referencing Common Step Definitions // commonCourseSteps.spec.js Given('a user is logged in', () => { ... }); // editCourseSteps.js // Commented out common steps with reference // Refer to: commonCourseSteps.spec.js When('the user edits the course details', () => { ... }); Then('the course should be updated', () => { ... });","s":"7. Writing Step Definitions","u":"/docs/e2e-system-tests/CodeConventions","h":"#7-writing-step-definitions","p":13},{"i":33,"t":"Use consistent folder and file names as per the naming conventions above. Keep the user journey sequence the same in both .feature files and step definition files to enhance readability.","s":"8. Additional Best Practices","u":"/docs/e2e-system-tests/CodeConventions","h":"#8-additional-best-practices","p":13},{"i":35,"t":"Understanding the project directory layout will help you navigate and manage the Cypress-Cucumber E2E test framework effectively. This section provides a detailed breakdown of the folder structure and the purpose of each component.","s":"Project Structure","u":"/docs/e2e-system-tests/ProjectStructure","h":"","p":34},{"i":37,"t":"(root) | |---- .github/ | |____ automatic-trigger.yml # GitHub Actions workflow for automatic triggers | |____ manual-trigger.yml # GitHub Actions workflow for manual runs | |____ scheduled-trigger.yml # GitHub Actions workflow for scheduled runs | |____ main.yml # GitHub Actions workflow for reusable jobs | |---- .vscode/ # Settings for recommended VS Code extensions | |---- env_variables/ | |____ template.env.json # Template for credentials & environment variables (rename as `local.env.json`) | |---- cypress/ | |___ downloads/ # Downloaded files during tests | |___ fixtures/ # Test data files | |___ e2e/ # Gherkin feature files | |___ screenshots/ # Screenshots taken on test failures | |___ support/ | |___ custom_commands/ # Custom Cypress commands used in tests | |___ pages/ # Page Object methods for better test modularity | |___ step_definitions/ # Step definitions for feature files | |___ commands.js # Custom Cypress commands configuration | |___ e2e.js # Global hooks and configurations | |___ videos/ # Recorded test run videos | |---- docs/ | |___ branch_activation.md | |___ folder_structure.md | |___ executing_tests.md | |___ setup.md | |___ tags.md | |---- env_variables/ | |___ devTemplate.env.json | |___ stagingTemplate.env.json | |---- reports/ # HTML reports and related assets | |---- logs/ # Logs generated during test runs | |---- node_modules/ # Project dependencies | |---- scripts/ | |____ aggregate-json-files.sh # Script to aggregate JSON files in CI | |____ runSchoolApi.js # Script to interact with the School API | |---- .editorconfig # Editor configuration for consistent formatting |---- .gitattributes # Git attributes for line endings and diff |---- .prettierignore # Files and folders ignored by Prettier |---- .prettierrc # Prettier configuration for code formatting |---- .gitignore # Git ignore rules |---- reporter.js # Custom reporter for generating HTML reports |---- cypress.config.json # Cypress configuration settings |---- LICENSE # License file |---- package-lock.json # npm package lock file |---- package.json # Project dependencies and scripts |---- README.md # Project documentation and setup guide","s":"Project Directory Layout","u":"/docs/e2e-system-tests/ProjectStructure","h":"#project-directory-layout","p":34},{"i":39,"t":".github/: Contains CI/CD workflows for automated, manual, and scheduled test executions. .vscode/: Recommended settings for VS Code extensions to maintain consistent coding standards. env_variables/: Holds environment configuration files. Duplicate template.env.json and rename it to local.env.json for local testing. cypress/: The main directory for Cypress tests. fixtures/: Stores reusable test data. e2e/: Contains all Gherkin .feature files. support/: Includes custom commands, page objects, and step definitions. videos/ & screenshots/: Captures test artifacts. docs/: Additional documentation for tags, configurations, and best practices. reports/: Contains HTML reports generated after test runs. scripts/: Helpful scripts for CI/CD and API interactions. .prettierrc & .editorconfig: Configuration files to enforce consistent coding styles. cypress.config.json: Central configuration file for Cypress test settings. reporter.js: Custom script to generate detailed HTML reports.","s":"Explanation of Key Directories and Files","u":"/docs/e2e-system-tests/ProjectStructure","h":"#explanation-of-key-directories-and-files","p":34},{"i":41,"t":"This section explains the tagging system used for Cypress and Cucumber tests. Tags help categorize and selectively run tests based on environments, test stability, or purpose.","s":"Tags","u":"/docs/e2e-system-tests/Tags","h":"","p":40},{"i":43,"t":"@stable_test: Tests that are stable and expected to pass in all environments. @regression_test: Tests run before a release to ensure core functionality. @school_api_test: Tests interacting with the school API. @staging_test: Tests specific to the staging environment. @pr: Tests run during the CI process for Pull Requests. @unstable_test: Tests that may fail intermittently due to environmental factors. @group-A / @group-B: Tags for grouping tests in parallel execution. @schedule_run: Tests tagged for scheduled runs in CI.","s":"Tag Descriptions","u":"/docs/e2e-system-tests/Tags","h":"#tag-descriptions","p":40},{"i":45,"t":"Feature-level tags apply to all scenarios within that feature, unless overridden at the scenario level. Examples: @regression_test & @stable_test @regression_test Feature: Account Management @stable_test Scenario: Edit email as an internal user Given I am logged in as an internal user When I navigate to the account settings Then I should see that my email is editable @unstable_test @unstable_test Feature: Add-ons Management Scenario Outline: Access Add-ons page Given I am logged in as '' When I navigate to the Add-ons page Then I should see the Add-ons interface Examples: | user_role | | admin | | teacher | @school_api_test & @staging_test @regression_test @stable_test Feature: User Management Scenario: Admin manages users Given I am logged in as an admin When I add a new user Then the user should appear in the list @school_api_test Examples: School API | user_role | user_email | | admin | admin@school.com | @staging_test Examples: Staging | user_role | user_email | | teacher | teacher@staging.com | @pr @pr Feature: Critical Paths Scenario: Verify homepage loads Given the application is deployed When I navigate to the homepage Then the homepage should load correctly For detailed information on the full usage of tags, please refer to the full documentation in README.","s":"Tag Hierarchy","u":"/docs/e2e-system-tests/Tags","h":"#tag-hierarchy","p":40},{"i":47,"t":"You have just learned the basics of Docusaurus and made some changes to the initial template. Docusaurus has much more to offer! Anything unclear or buggy in this tutorial? Please report it!","s":"Congratulations!","u":"/docs/How to update the docs/congratulations","h":"","p":46},{"i":49,"t":"Read the official documentation Modify your site configuration with docusaurus.config.js Add navbar and footer items with themeConfig Add a custom Design and Layout Add a search bar Find inspirations in the Docusaurus showcase Get involved in the Docusaurus Community","s":"What's next?","u":"/docs/How to update the docs/congratulations","h":"#whats-next","p":46},{"i":51,"t":"Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...","s":"Create a Blog Post","u":"/docs/How to update the docs/create-a-blog-post","h":"","p":50},{"i":53,"t":"Create a file at blog/2021-02-28-greetings.md: blog/2021-02-28-greetings.md --- slug: greetings title: Greetings! authors: - name: Joel Marcey title: Co-creator of Docusaurus 1 url: https://github.com/JoelMarcey image_url: https://github.com/JoelMarcey.png - name: Sébastien Lorber title: Docusaurus maintainer url: https://sebastienlorber.com image_url: https://github.com/slorber.png tags: [greetings] --- Congratulations, you have made your first post! Feel free to play around and edit this post as much you like. A new blog post is now available at http://localhost:3000/blog/greetings.","s":"Create your first Post","u":"/docs/How to update the docs/create-a-blog-post","h":"#create-your-first-post","p":50},{"i":55,"t":"Documents are groups of pages connected through: a sidebar previous/next navigation versioning","s":"Create a Document","u":"/docs/How to update the docs/create-a-document","h":"","p":54},{"i":57,"t":"Create a Markdown file at docs/hello.md: docs/hello.md # Hello This is my **first Docusaurus document**! A new document is now available at http://localhost:3000/docs/hello.","s":"Create your first Doc","u":"/docs/How to update the docs/create-a-document","h":"#create-your-first-doc","p":54},{"i":59,"t":"Docusaurus automatically creates a sidebar from the docs folder. Add metadata to customize the sidebar label and position: docs/hello.md --- sidebar_label: 'Hi!' sidebar_position: 3 --- # Hello This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in sidebars.js: sidebars.js module.exports = { tutorialSidebar: [ 'intro', 'hello', { type: 'category', label: 'Tutorial', items: ['tutorial-basics/create-a-document'], }, ], };","s":"Configure the Sidebar","u":"/docs/How to update the docs/create-a-document","h":"#configure-the-sidebar","p":54},{"i":61,"t":"Add Markdown or React files to src/pages to create a standalone page: src/pages/index.js → localhost:3000/ src/pages/foo.md → localhost:3000/foo src/pages/foo/bar.js → localhost:3000/foo/bar","s":"Create a Page","u":"/docs/How to update the docs/create-a-page","h":"","p":60},{"i":63,"t":"Create a file at src/pages/my-react-page.js: src/pages/my-react-page.js import React from 'react'; import Layout from '@theme/Layout'; export default function MyReactPage() { return (

    My React page

    This is a React page

    ); } A new page is now available at http://localhost:3000/my-react-page.","s":"Create your first React Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-react-page","p":60},{"i":65,"t":"Create a file at src/pages/my-markdown-page.md: src/pages/my-markdown-page.md # My Markdown page This is a Markdown page A new page is now available at http://localhost:3000/my-markdown-page.","s":"Create your first Markdown Page","u":"/docs/How to update the docs/create-a-page","h":"#create-your-first-markdown-page","p":60},{"i":67,"t":"Docusaurus is a static-site-generator (also called Jamstack). It builds your site as simple static HTML, JavaScript and CSS files.","s":"Deploy your site","u":"/docs/How to update the docs/deploy-your-site","h":"","p":66},{"i":69,"t":"Build your site for production: npm run build The static files are generated in the build folder.","s":"Build your site","u":"/docs/How to update the docs/deploy-your-site","h":"#build-your-site","p":66},{"i":70,"t":"Test your production build locally: npm run serve The build folder is now served at http://localhost:3000/. You can now deploy the build folder almost anywhere easily, for free or very small cost (read the Deployment Guide).","s":"Deploy your site","u":"/docs/How to update the docs/deploy-your-site","h":"#deploy-your-site-1","p":66},{"i":72,"t":"Docusaurus supports Markdown and a few additional features.","s":"Markdown Features","u":"/docs/How to update the docs/markdown-features","h":"","p":71},{"i":74,"t":"Markdown documents have metadata at the top called Front Matter: my-doc.md --- id: my-doc-id title: My document title description: My document description slug: /my-custom-url --- ## Markdown heading Markdown text with [links](./hello.md)","s":"Front Matter","u":"/docs/How to update the docs/markdown-features","h":"#front-matter","p":71},{"i":76,"t":"Regular Markdown links are supported, using url paths or relative file paths. Let's see how to [Create a page](/create-a-page). Let's see how to [Create a page](./create-a-page.md). Result: Let's see how to Create a page.","s":"Links","u":"/docs/How to update the docs/markdown-features","h":"#links","p":71},{"i":78,"t":"Regular Markdown images are supported. You can use absolute paths to reference images in the static directory (static/img/docusaurus.png): ![Docusaurus logo](/img/docusaurus.png) You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them: ![Docusaurus logo](./img/docusaurus.png)","s":"Images","u":"/docs/How to update the docs/markdown-features","h":"#images","p":71},{"i":80,"t":"Markdown code blocks are supported with Syntax highlighting. ```jsx title=\"src/components/HelloDocusaurus.js\" function HelloDocusaurus() { return (

    Hello, Docusaurus!

    ) } ``` src/components/HelloDocusaurus.js function HelloDocusaurus() { return

    Hello, Docusaurus!

    ; }","s":"Code Blocks","u":"/docs/How to update the docs/markdown-features","h":"#code-blocks","p":71},{"i":82,"t":"Docusaurus has a special syntax to create admonitions and callouts: :::tip My tip Use this awesome feature option ::: :::danger Take care This action is dangerous ::: My tip Use this awesome feature option Take care This action is dangerous","s":"Admonitions","u":"/docs/How to update the docs/markdown-features","h":"#admonitions","p":71},{"i":84,"t":"MDX can make your documentation more interactive and allows using any React components inside Markdown: export const Highlight = ({children, color}) => ( { alert(`You clicked the color ${color} with label ${children}`) }}> {children} ); This is Docusaurus green ! This is Facebook blue ! This is Docusaurus green ! This is Facebook blue !","s":"MDX and React Components","u":"/docs/How to update the docs/markdown-features","h":"#mdx-and-react-components","p":71},{"i":86,"t":"You can use following package and command to detect dependency cycles in code. https://www.npmjs.com/package/madge","s":"Detect Dependency Cycles","u":"/docs/Informations/detect-dependency-cycles","h":"","p":85},{"i":88,"t":"npx madge --extensions js,ts --circular .","s":"text export","u":"/docs/Informations/detect-dependency-cycles","h":"#text-export","p":85},{"i":90,"t":"apt-get install graphviz npx madge --extensions js,ts --circular --image graph.svg .","s":"image export (Ubuntu/Wsl)","u":"/docs/Informations/detect-dependency-cycles","h":"#image-export-ubuntuwsl","p":85},{"i":92,"t":"as graphic​ npx madge --image graph_server.svg dist/apps/server/apps/server.app.js npx madge --circular --image graph_server_circular.svg dist/apps/server/apps/server.app.js npx madge --exclude '^(?!.entity).$' --image graph_server_entities.svg dist/apps/server/apps/server.app.js npx madge --circular --exclude '^(?!.entity).$' --image graph_server_circular_entities.svg dist/apps/server/apps/server.app.js as text​ npx madge --json dist/apps/server/apps/server.app.js >> output.json","s":"examples","u":"/docs/Informations/detect-dependency-cycles","h":"#examples","p":85},{"i":94,"t":"https://github.com/jmcdo29/nestjs-spelunker https://sanyamaggarwal.medium.com/automate-circular-dependency-detection-in-your-node-js-project-394ed08f64bf","s":"more solutions","u":"/docs/Informations/detect-dependency-cycles","h":"#more-solutions","p":85},{"i":96,"t":"The schulcloud consists of many repositories.","s":"Schulcloud Documentation","u":"/docs/intro","h":"","p":95},{"i":98,"t":"We want to make sure that our product can be used by anyone. This includes people with disabilities as well as people that have a slow connection, outdated or broken hardware or people that just have an unfavorable environment.","s":"Accessibility (A11y)","u":"/docs/nuxt-client/Accessibility","h":"","p":97},{"i":100,"t":"The WAI develops strategies, standards, resources to make the web accessibile. An introduction to Accessibility can be found here: Introduction to Web Accessibilty The WAI ARIA is the Acessible Rich Internet Applications suite of web standards. It makes Web content and Web applications more accessible with adding attributes to identify features for user interaction and enable e.g. keyboard users to move among regions. A recommended approach using WAI-ARIA roles, states and properties can be found here: ARIA Authoring Practices Guide (APG)","s":"W3C Web Accessibility Initiative (WAI)","u":"/docs/nuxt-client/Accessibility","h":"#w3c-web-accessibility-initiative-wai","p":97},{"i":102,"t":"We want to use Vuetfiy Components in our project. They provide key interaction for all mouse-based-actions and utilize HTML5 semantic elements where applicable. See: Vuetify A11y There is also a Accessibility Chapter in the Best Practices of the Vue Docs: Vue A11y // TODO: link to good tutorial on how to test a11n // REMINDER: If we establish special a11n-components and/or tools - they should be described here","s":"Vuetify and Vue","u":"/docs/nuxt-client/Accessibility","h":"#vuetify-and-vue","p":97},{"i":104,"t":"Code Conventions data-testids ts-ignore-comments composables","s":"Code Conventions","u":"/docs/nuxt-client/CodeConventions","h":"","p":103},{"i":106,"t":"Please use
    in your HTML-code if you want to define a data-testid. do not use uppercase-characters only use one dash - right after data We also recommend to use refs instead of data-testids. But if you do that, you need to be careful when removing them... as they could be used in the component-code AND in tests: VueJs - template refs VueTestUtils - ref Also look here: Frontend Arc Group: Meeting Notes 2022-11-04","s":"data-testids","u":"/docs/nuxt-client/CodeConventions","h":"#data-testids","p":103},{"i":108,"t":"Everybody should try to avoid // @ts-ignore and try his/her best to define the types of variables in TypeScript files. Also look here: Frontend Arc Group: Meeting Notes 2022-10-28","s":"ts-ignore-comments","u":"/docs/nuxt-client/CodeConventions","h":"#ts-ignore-comments","p":103},{"i":110,"t":"Composables are a great way to make our code more reusable and to extract code from components. If you want to write a composable, consider using one of these well documented and well tested ones: VueUse - Collection of Vue Composition Utilities If you write a composable: it should have the extension .composable.ts should be placed in your feature folder (see section \"directory structure\" above), if it is only used inside of your feature should be placed in the global folder / src / composables, if it is used in multiple features","s":"composables","u":"/docs/nuxt-client/CodeConventions","h":"#composables","p":103},{"i":112,"t":"In the Material Design system (the foundation of our component library), colors and color schemes are used to create a visual hierarchy, direct focus, and enhance the user experience. You can find our custom defined theme colors under /src/themes/base-vuetify.options.ts and their overwrites per theme in /src/themes//vuetify.options.ts. You can find the colors provided by Vuetify here.","s":"Colors","u":"/docs/nuxt-client/Colors","h":"","p":111},{"i":115,"t":"All colors defined by Vuetify or in our Vuetify options generate CSS classes you can use. To apply a color variant like lighten-1, add it to the color like grey-lighten-1. Backgrounds have the bg-prefix and texts the text-prefix. Examples​ Using a color from Vuetify's color palette:
    Blue background
    Using a color defined in our vuetify options as text color:

    Text has a red color

    To use a variant of a color, you have to add the name of the variant seperated by hyphens:

    Text has a darken variant of the red color

    ","s":"Color Classes","u":"/docs/nuxt-client/Colors","h":"#color-classes","p":111},{"i":117,"t":"For colors defined in our Vuetify options, Vuetify generates CSS variables. Now, custom properties are an rgb list, so we need to use rgba() to access them. Examples​ .alert { background-color: rgba(var(--v-theme-primary-lighten-1)); color: rgba(var(--v-theme-primary)); } In Vuetify 2, we could only use hex values without the alpha property. With Vuetify 3, it's now possible: .example{ background-color: rgba(var(--v-theme-primary), 0.12); } Colors from Vuetify's colors palette (as of now) do not get generated as CSS variables. You will need to access them with map-get. .alert { background-color: map-get($grey, base); color: map-get($blue, lighten-3); }","s":"Use Colors in (S)CSS","u":"/docs/nuxt-client/Colors","h":"#use-colors-in-scss","p":111},{"i":119,"t":"\"On\" colors are important for making text, icons, and other elements recognizable and readable on various backgrounds. on-surface: Used for text, icons, and other elements that appear on top of a surface. Surfaces can include components like cards, dialogs, and menus. on-background: Used for text, icons, and other elements that appear on the primary background of an application or a component We override the standard on-surface and on-background vuetify colors in our vuetify options and define them for each theme.","s":"\"On-Surface\" and \"On-Background\" Colors","u":"/docs/nuxt-client/Colors","h":"#on-surface-and-on-background-colors","p":111},{"i":121,"t":"You can define more custom colors in our vuetify options like this: ... colors: { info: \"#0a7ac9\", \"icon-btn\": colors.grey.darken3, \"on-surface\": \"#0f3551\", } ...","s":"Definition Of Custom Colors","u":"/docs/nuxt-client/Colors","h":"#definition-of-custom-colors","p":111},{"i":123,"t":"Talk to UX before introducing a new color Do not overwrite vuetify colors Use a semantic name to represent the use case Prefer usage via map-get over new color definition, unless you introduce a new color Either define style in template or in SCSS","s":"Rules","u":"/docs/nuxt-client/Colors","h":"#rules","p":111},{"i":126,"t":"Note: Please don't use yarn !!! We decided to use npm across all of our repositories. In order to run this client, you need to have the legacy-client and schulcloud-server set up and running. See for documentation on how to do that in the respective repositories.","s":"Development Setup","u":"/docs/nuxt-client/GettingStarted","h":"#development-setup","p":124},{"i":128,"t":"Clone the repository git clone git@github.com:hpi-schul-cloud/nuxt-client.git Install the required dependencies: npm ci Start the development server: npm run serve By default the server will listen on the URL http://localhost:4000","s":"Start the Server","u":"/docs/nuxt-client/GettingStarted","h":"#start-the-server","p":124},{"i":130,"t":"# Run all (unit) tests npm run test","s":"Unit Tests","u":"/docs/nuxt-client/GettingStarted","h":"#unit-tests","p":124},{"i":132,"t":"npm run lint","s":"Lint","u":"/docs/nuxt-client/GettingStarted","h":"#lint","p":124},{"i":134,"t":"We are using Visual Studio Code as our default deveopment-IDE. In /.vscode you can find two templates to setup your IDE: launch.default.json (copy its content and us it in launch.json) settings.default.json (copy its content and us it in settings.json) For a list of recommended Visual Studio Code extensions please refer to extensions.json.","s":"Editor Setup","u":"/docs/nuxt-client/GettingStarted","h":"#editor-setup","p":124},{"i":136,"t":"Each change should be done in a Ticket (no matter how small). We use a Feature Branch model. Start a branch from main and make a PR to main. Branch naming: {{ PROJECT_ABBREVIATION }}-{{ NUMBER }}-word1-word2-word2 e.g.: BC-1234-course-copy We try to keep branch names small. The Ticket Number should be in Uppercase (e.g BC-1234) but the namespace should be in lowercase. It should stay below 64 letters.","s":"Git Conventions","u":"/docs/nuxt-client/GitConventions","h":"","p":135},{"i":138,"t":"Pull Requests must contain a relevant description (template provides useful information, when creating the PR). In case of UI changes also put a screenshot and talk to UX if thats fine like it is. All Pull Requests Criterias (as defined in deployment pipeline) must be green before merge, e.g. 1 approving review, unit tests or QA checkbox in PR template. We merge by squash strategy. The squashed commit subject should start with a ticket number and end with a PR number. Write commit messages in imperative and active. Example: BC-1993 - lesson lernstore and geogebra copy (#3532) In order to make sure developers in the future can find out why changes have been made, we would like some descriptive text here that explains what we did and why. - change some important things - change some other things - refactor some existing things # We dont need to mention tests, changes that didnt make it to main, linter, or other fixups # only leave lines that are relevant changes compared to main # comments like this will not actually show up in the git history Note for working with Windows: We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.","s":"Pull Requests","u":"/docs/nuxt-client/GitConventions","h":"#pull-requests","p":135},{"i":141,"t":"Imagine writing a basic component to add reusable buttons to your app. The first iteration might look like this when using it: The next step might be adding a way to set the button label. Careful! The label-prop is just a string. This will limit your Button to only being able to have text-based Labels in the future. It is a lot less flexible because the power of HTML was removed completely. Compare it to this button: MyButton The label stays within the realm of HTML and we don't lose any HTML capabilities: Two Line
    Button!
    Button with in the label! Both of these examples are (almost) impossible with a prop-based label. Rule: Readable text should be HTML and not a string-prop.","s":"HTML is not a string","u":"/docs/nuxt-client/ComponentGuidelines","h":"#html-is-not-a-string","p":139},{"i":144,"t":"Let's build a simple vertical-menu with two clickable options and add more and more requirements as we go. note Requirements Menu with two clickable options
    • Option 1
    • Option 2
    note Updated Requirements Menu with two clickable options Menu with any number of clickable options To make our menu reusable, one approach might be to add an options prop: This approach has two major problems: First you probably already notice that we demoted the label from HTML to being just a string again. (See: HTML is not a string) Second we abstracted the structure of our menu into an Array. This replaces perfectly good HTML with a datastructure. Take a look at this HTML-based approach: Option 1 Option 2 The original HTML-structure is preserved and only the default elements (ul, li, button) are abstracted in their own components. This leaves a lot of flexibility to interact with the structure (e.g. toggling options with v-if) while still making sure that the rendered output is valid. Additionally, this is much easier to test since we do not have to deal with datastructures. note Updated Requirements Menu with any number of clickable options Menu-Options can be colored Any number of Menu-Dividers can be placed at any position in the menu Adding these new requirements in the HTML approach is very straightforward. We just have to add a prop to each my-menu-option to pick a color. Then we create a new my-menu-divider component. Expanding the datastructure to support colors is easy, we just have to add a color-property. But the divider will be an actual problem. So far the datastructure was created to represent buttons. By adding the divider config object we will lose any uniformity of our config data. This will make it difficult to read, complicated to test und generally annoying to maintain. Compare the two solutions in code: Option 1 Option 2 We can already see the datastructure approach falling apart. For complex menus this will be completely ineligible and difficult to understand. Let's add more requirements to get closer to a real world menu. note New Requirements Menu with any number of clickable options Menu-Options can be colored Any number of Menu-Dividers can be placed at any position in the menu Menu-Options should have a disabled state Menu-Options can be a button or link Menu-Options can be nested dropdowns Option 1 Option 2 Link 1 😅 Rule: Use Slots and small subcomponents to create robust and flexible features. Rule: Do not use datastructures to represent HTML.","s":"Using slots for highly flexible ui components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#using-slots","p":139},{"i":146,"t":"We often have to deal with complex data that we want to show to the user. Take a look at this simplified example: const users: User[] = [ { id: 1, name: 'User 1', email: 'user1@example.com' } { id: 2, name: 'User 2', email: 'user2@example.com' } ] note Requirements Display the User Array in a table
    ID Name Email
    {{user.id}} {{user.name}} {{user.email}}
    This template will quickly get large and hard to understand if we were to add styling, more fields of our user object or even interactions like editing or deleting entries. How can we easily split up the template? Destructuring Data​ A rule of thumb can be to not handle more than one level of your data structure in a single component. The User object consists of three levels: Array Object Property We can use this list to create subcomponents for the table: UserTable the host component where all components come together This will also be the outside Api of our implementation UserTableBody Component responsible for the Array-level of our data UserTableRow Component responsible for the Object-level of our data UserTableHeader Encapsulate Splitting up the table into these sub-components keeps the template short and less complex. It also makes testing much easier since each level is only concerned about a certain part of the complexity in the data. note New Requirements Display the User Array in a table Deleting users should be possible To add the new interaction button we will have to place it at the end of each row. To have a more pronounced structure we will place this button in a new component UserTableActions. This creates a well defined place for adding more actions in the future. We also have to expand UserTableHead by one . Adding this action also reveals the biggest disadvantage of this approach: We have to pass the emit all the way up to UserTable component. Since all children of UserTable are ui-components they cannot access any state or the api. Updated Requirements Display the User Array in a table Deleting users should be possible Delete button should be disabled while an async request is pending Let's ignore state interactions for this example. But to fulfil this requirement we will add a disabled-prop to UserTable so that our outside logic can disable the buttons while requests are pending. But how do we deal with this internally. Option 1 - Passing the prop​ We can pass the disabled value through our whole component tree. That is a completely valid and comparatively easy solution but it can quickly create a lot of boilerplate. Option 2 - provide/inject​ Vue Docs: Prop-Drilling & provide/inject. This can safe some development time since we don't have to deal with all the boilerplate of Option 1 but it can also lead to a mess of injections if not used carefully. Since this table and it's children are already heavily dependant on each other (they serve one shared purpose: Displaying a User-Table) we can use prop-drilling if we keep the injection-key as a private property of the module. Updated Requirements Display the User Array in a table Deleting users should be possible Delete button should be disabled while an async request is pending The Email should be a mailto-Link Remember the three levels of our data: Array > Object > Property. So far we have destructured the Array and Object levels into separate components. The new requirement could be implemented like this: While this template is still easy to understand in this simplified example, if we imagine a table with over 10 columns and a few lines of HTML for each table-cell we can see that this will get messy quite quickly. A great possibility to keep the template clean is to destructure one more level, down to the Property: Note that we did not create components for the id and name properties. Since they are not handled any differently they can just be shown using interpolation. Rule: Create a sub-component for each meaningful level in your data Rule: Use provide/inject of props only in small and defined scopes","s":"Destructure data over multiple components","u":"/docs/nuxt-client/ComponentGuidelines","h":"#destructure-data-in-component-trees","p":139},{"i":148,"t":"Destructuring data over components can lead to many small components and picking meaningful names can become a challenge. To find a name without much effort whenever I am creating components I use a pattern-based approach: Let's analyze the naming in the UserTable example to illustrate the pattern: UserTable The root component of the implementation. Its name consists of the feature identifier UserTable and nothing else. UserTableHead, UserTableBody and UserTableRow The children of the root component are named by the feature identifier and their appriopriate levels in the table: Head, Body and Row. They are still quite general components and do not need a specific description since they are unique to their levels. UserTableCellEmail This is a highly specific component and therefore includes the feature identifier, the level and a specific description to reflect its specific usecase. Following this pattern makes it quite easy to name things while destructuring. It also leads to a well organized folder in the workspace explorer since components on the same level will be listed closely together - e.g. all UserTableCell-components have the same \"prefix\". The most difficult part in my experience is finding a good name for the level-part of the name. I usually try to use names that reflect the component as an HTML-Element: names of the part of a table, list and list-item for list-structures or option when dealing with dropdowns etc.","s":"Naming components in destructured component trees","u":"/docs/nuxt-client/ComponentGuidelines","h":"#naming-in-destructured-trees","p":139},{"i":151,"t":"As we work with Material Design Icons (MDI) UX regularly chooses the icons we are going to use. One example to find these icons and how they are represented can be found here. As UX works with Figma you can also easily find the name of the chosen icon directly in Figma with the so called Dev Mode. You can easily access this mode by using the toggle and then see the name of the icon in the left sidebar (see screenshot). Afterwards you can either search for the icon on the page above (by using hyphens between words) or directly import it from the library into your component.","s":"Working with Material Design Icons","u":"/docs/nuxt-client/HintsForWorking","h":"#working-with-material-design-icons","p":149},{"i":154,"t":"Circular depencies are a common issue when working with barrel-files (index.ts). Let's look at a common dependency pattern: In this example there are two Building-Blocks (e.g. folders that have a barrel file) which depend on each other. Using the SharedComponent in both Building-Blocks will result in a circular dependency. That basically means that the compiler can not resolve the order to load the Building-Blocks which causes an error.","s":"What is a circular dependency?","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#what-is-a-circular-dependency","p":152},{"i":156,"t":"The basic gist is: break the circle and separate the shared dependency in a separate module. In this configuration the compiler can find an order to resolve the building-blocks correctly.","s":"Resolving Circular Dependencies","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#resolving-circular-dependencies","p":152},{"i":158,"t":"I recreated the first example error in Vue. When Vue tries to render ComponentA I see the following Error in the console: This can be quite hard to decipher on a first glance but it contains all the info we need to identify the root cause of the circular dependency. Based on the info from the message we can learn that ComponentB \"closed the circle\" by importing SharedComponent. From there we can trace back to see where SharedComponent is exposed and why it depends on ComponentB. In this case it is because they are both imported in ComponentA. Keep in mind that the circular dependency can involve multiple building-blocks.","s":"How to identify Circular Dependencies in Vue","u":"/docs/nuxt-client/IdentifyingAndResolvingCircularDependencies","h":"#how-to-identify-circular-dependencies-in-vue","p":152},{"i":160,"t":"Collection of instructions on how to do certain things: Feature Flags Using generated API and it's types User-Permissions on Pages Exception handling inject - fallback throwing an error","s":"How To","u":"/docs/nuxt-client/HowTo","h":"","p":159},{"i":162,"t":"If there is a new functionality that should only be available on certain systems, we introduce new FEATURE-Flags into the SchulCloud-Backend and into the dof-repository, that contains the configuration for all our instances. Our Vue-Frontend requests all FEATURE-flags and provides global access to them by using this code (example): import { envConfigModule } from \"@/store\"; if (envConfigModule.getEnv.FEATURE_COPY_SERVICE_ENABLED) { ... }","s":"Feature Flags","u":"/docs/nuxt-client/HowTo","h":"#feature-flags-","p":159},{"i":164,"t":"We are using a generator script to create classes to access the Schulcloud-Backend-API - V3 (so Legacy-Backend endpoints (aka V1) are not covered). These generated classes and methods internally use axios to request data and use generated types - both for the input to the methods and for the returned types. HINT Please use the generated types in your stores and do not redefine the same types. This way consistency between Server and Api-Access stays stable.","s":"Using generated API and it's types","u":"/docs/nuxt-client/HowTo","h":"#using-generated-api-and-its-types-","p":159},{"i":166,"t":"Only if the server-api or the filestore-api has changed, you need to regenerate them using the following npm-scripts: For generating the files to access the server-api please use: npm run generate-client:server The same is implemented for generating the backend-api to our filestore-backend. For generating the files to access the filestore-api please use: npm run generate-client:filestorage Hint For regenerating the clients you need an up-to-date running backend-server running in your environment.","s":"Regenerating the clients","u":"/docs/nuxt-client/HowTo","h":"#regenerating-the-clients","p":159},{"i":168,"t":"The generated APIs can easily be used. Examples can be seen in any current store-implementation - like here: src/store/share-course.ts: import { ShareTokenApiFactory, ShareTokenApiInterface, ShareTokenBodyParams, ShareTokenBodyParamsParentTypeEnum, ShareTokenResponse, } from \"../serverApi/v3/api\"; ... export default class ShareCourseModule extends VuexModule { ... private get shareApi(): ShareTokenApiInterface { return ShareTokenApiFactory(undefined, \"v3\", $axios); } @Action async createShareUrl( payload: SharePayload ): Promise { const shareTokenPayload: ShareTokenBodyParams = { parentType: ShareTokenBodyParamsParentTypeEnum.Courses, parentId: this.courseId, expiresInDays: payload.hasExpiryDate ? 21 : null, schoolExclusive: payload.isSchoolInternal, }; ... const shareTokenResult = await this.shareApi.shareTokenControllerCreateShareToken( shareTokenPayload ); ... } ... }","s":"Using the generated api","u":"/docs/nuxt-client/HowTo","h":"#using-the-generated-api","p":159},{"i":170,"t":"The permissions are controlled by createPermissionGuard middleware method that receives two parameters. The first parameter should contain an array of the userPermission that is required to reach the page. The second parameter is an optional fallback route. If the second parameter isn't provided and the user has no permission to reach the page, an error page (401) is shown. // src/router/routes.ts // with a fallback route { path: \"/your/route\", component: () => import(\"../pages/your.page.vue\"), name: \"yourRouteName\", beforeEnter: createPermissionGuard([\"ADMIN_VIEW\"], \"/yourFallBackRoute\"), }, // without a fallback, // it shows a '401' file if the user doesn't have permissions { path: \"/your/route\", component: () => import(\"../pages/your.page.vue\"), name: \"yourRouteName\", beforeEnter: createPermissionGuard([\"ADMIN_VIEW\", \"SCHOOL_EDIT\"]), },","s":"User-Permissions on Pages","u":"/docs/nuxt-client/HowTo","h":"#user-permissions-on-pages-","p":159},{"i":172,"t":"useApplicationError is a composable providing a typed factory function for creating application errors. A global error handler for putting application errors takes those and puts them into a store and a global error page will display them. Exceptions should be thrown using them - like this: // src/pages/user-migration/UserMigration.page.vue import { useApplicationError } from \"@/composables/application-error.composable\"; const { createApplicationError } = useApplicationError(); throw createApplicationError(HttpStatusCode.BadRequest); // src/router/guards/permission.guard.ts import { useApplicationError } from \"@/composables/application-error.composable\"; import { applicationErrorModule } from \"@/store\"; const { createApplicationError } = useApplicationError(); applicationErrorModule.setError(createApplicationError(401)); Also look here: Meeting Notes 2022-11-25","s":"Exception handling","u":"/docs/nuxt-client/HowTo","h":"#exception-handling-","p":159},{"i":174,"t":"We want to provide a simple factory function that produces a unique, identifiable error, if an inject fails and we want to avoid adding code to your TypeScript-components only to prevent linter errors. The topic will be implemented with this ticket: Jira - BC-2813. It contains a lot of details on that issue. ... Details should be added here. soon... Also look here: Frontend Arc Group: Meeting Notes 2022-12-02","s":"inject - fallback throwing an error","u":"/docs/nuxt-client/HowTo","h":"#inject---fallback-throwing-an-error-","p":159},{"i":177,"t":"Files should be consistently named like this: file content style filename comment Components PascalCase YourComponent.vue best practice Pinia stores PascalCase BoardCard.store.ts placed in data- modules Composables CamelCase confirmationDialog.composable.ts current convention Utils CamelCase yourArea.util.ts suggestion: move from yourUtil.ts => your.util.ts other files CamelCase yourFilename.ts test files {{ basename }}.unit.ts e.g. YourComponent.unit.ts, yourArea.util.unit.ts","s":"Filenames","u":"/docs/nuxt-client/ProjectStructure","h":"#filenames","p":175},{"i":179,"t":"Folders are written in KebabCase (e.g. feature-board).","s":"Folders","u":"/docs/nuxt-client/ProjectStructure","h":"#folders","p":175},{"i":181,"t":"The project's code is separated into building blocks.","s":"Building Blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#building-blocks","p":175},{"i":183,"t":"A building-block is a \"container\" were we place most of our applications logic and components. Each building-block is defined by an index.ts (Barrel-File) describing it's exported content (public API of a building-block) and a type. Utilizing linting rules and the index.ts we can ensure that each building-block only exposes files which are meant to be used application-wide. This way we achieve a strong separation of concern across the whole application. Our linting rule is based on the following concept: Enforce Project Boundaries | Nx Note: in above documentation libraries are equivalent to building-blocks and tags represent the types defined below. near future: All newer modules, that already follow our naming convention (see link above), will move from \"components/\" into a central \"modules/\" folder with one subfolder for each type of module (page/, data/, feature/, ui/, util/ ).","s":"What is a building-block?","u":"/docs/nuxt-client/ProjectStructure","h":"#what-is-a-building-block","p":175},{"i":185,"t":"There are different types of building blocks each with a different purpose. type example comment Page modules / page / dashboard Contains a subpage of the application. Orchestrates Feature and UI building-blocks. Feature modules / feature / calendar Complex features with stateful / smart components. Usually specialized to fulfill specific roles in the App. Can also contain presentational components that are specialized for this feature. UI modules / ui / forms Stateless / presentational components which get their data via props and emit events. Usually less specialized. Data modules / data / auth State and API-access. Does not contain any visual components. They are the data-sources of all smart components. Util modules / util / form-validators Contains shared low-level code. Hint: currently the modules are all placed under /components/feature-... etc. and will be refactored to the above with this ticket: BC-5513 Matrix of allowed imports​ Each type is only allowed to import modules of some of the other types. Allowed to Import → It is ↓ page feature data ui util page ✔ ✔ ✔ ✔ feature ✔ ✔ ✔ ✔ data ✔ ✔ ui ✔ ✔ util ✔ Type: Page​ A page building-block represents a subpage of the application. It contains the layout component and orchestrates feature and ui building blocks to create a subpage. It can not be imported into any other type of building-block. It is only imported by the vue-router and should be lazy-loaded if possible. Type: Feature​ A feature building-block contains a set of files that represent a business use case in an application. Most of the components of features are stateful / smart components that interact with data sources. This type also contains most of the UI logic, form validation code, etc. Type: UI​ A ui building-block mainly contains Stateless / presentational components which are used all across the application. They don't have access to stores and do not use features in their templates. All data needed for components in ui building-blocks comes from props. Type: Data​ A data building-block contains stores and api-services. It does not contain any view components. They serve as data-sources for feature and page building blocks. Type: Util​ A utility building-block contains low level code used by many building-blocks. Often there is no framework-specific code and the building-block is simply a collection of types, utilities, pure functions, factories or composables. Placed in folder util","s":"Types of building-blocks","u":"/docs/nuxt-client/ProjectStructure","h":"#types-of-building-blocks","p":175},{"i":187,"t":"Starting a new Feature module​ create a new subfolder inside of modules/feature/-folder and give it a speaking name place any files of different purposes (for ui, data and/or util) inside of it. provide a barrel-file index.ts that defines it's exposed API - the functionality that other modules are allowed to use. This should only export the bare minimum. Another module needs access to the data of your feature​ do not export your data-functionality from your feature refactor your feature-module and extract the data part of it into a new data-module use this new data-module in the original feature and the other module that also needs access Note: if you know upfront, which parts will definitly be used outside your feature, implement the data-module upfront Another module needs access to some of your UI-Components that form your feature​ do not export these components simply from your feature if the components are simple / dumb and do not need data-access: consider extracting them into a separate ui-module (example: ui/preview-image) if the components are coupled with some data-functionality but this does not belong to the core of your feature: consider extracting those parts into a separate feature-module (example: feature/alert-list) Note: if you are sure that a part of your feature, will be reused in the project - consider extracting it from your ticket - before implementation - and implement it first.","s":"Life of a feature module","u":"/docs/nuxt-client/ProjectStructure","h":"#life-of-a-feature-module","p":175},{"i":189,"t":"To render this graph in VS-Code markdown preview install this extension: bierner.markdown-mermaid","s":"How to pick the correct type for my Task","u":"/docs/nuxt-client/ProjectStructure","h":"#how-to-pick-the-correct-type-for-my-task","p":175},{"i":191,"t":"How to write valuable, reliable tests, that are easy to maintain.","s":"Writing Tests","u":"/docs/nuxt-client/WritingTests","h":"","p":190},{"i":193,"t":"Writing good tests that cover all aspects of your code, leads to: confidence: to refactor your code higher code quality: as you review your code and identify problems when writing tests well documented code: as your tests describe how your code works and by that to: developer happiness :-)","s":"Basics","u":"/docs/nuxt-client/WritingTests","h":"#basics","p":190},{"i":195,"t":"Unit-Tests​ Unit-Tests are WhiteBox-Tests. So they may use knowledge of internals of the code. They are well suited for testing e.g. composables and stores. Component-Tests​ Component-Tests are BlackBox-Tests. So they are not allowed to use any knowledge of the internals of the component. They ensure the stability of the public interface of the component (aka its methods, props, events etc.). The enable us to refactor the internals of our components later on.","s":"Unit-Tests vs. Component-Tests","u":"/docs/nuxt-client/WritingTests","h":"#unit-tests-vs-component-tests","p":190},{"i":197,"t":"positive tests test the default cases of your code = how it should work negative tests test error-cases or exception-behaviour you need to write both to ensure your component works correctly think of edge-cases that might break your component e.g. when providing input to the component: numbers: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... dates: none existing dates e.g. 30th February 2023, far away future,... strings: umlauts, url-special-characters (?, &, =, \\/\\/: ), very long strings for names, long strings without linebreaks totally incorrect data: e.g. giving a string instead of a number","s":"Positive & negative Tests","u":"/docs/nuxt-client/WritingTests","h":"#positive--negative-tests","p":190},{"i":199,"t":"For testing our Vue-Components we use the Vue Test Utils. Vue Test Utils is a library that provides methods to help you write tests for your Vue components. It provides methods to mount, shallow mount, and render components, as well as methods to simulate events and find elements in the rendered output. Some functionality it provides: mount(): create a wrapper around the component and instantiate it shallowMount(): create a shallow wrapper of the component being tested with childcomponents being mocked setMethods(): mock function on the component setProps(): set a specific set of props on the component findComponent(): finds a component by it's class, name or ref findAllComponents(): finds all components by it's class, name or ref find() / findAll(): search for html elements using html-selectors deprecated for finding Components use findComponent() or findAllComponents() instead setData(): set specific data on the component trigger() + emit(): test events and the flow of data We think the Vue Test Utils-documentation is a valuable resource for learning how to test Vue-Components and a very good starting point on how to test certain aspects of your component. Please have a look at https://test-utils.vuejs.org/guide","s":"Use Vue-Test-Utils","u":"/docs/nuxt-client/WritingTests","h":"#use-vue-test-utils","p":190},{"i":201,"t":"Use TypeScript for your components and for your unit-tests. This way many errors can be prevented early on, as you can detect them already in your IDE.","s":"Use TypeScript","u":"/docs/nuxt-client/WritingTests","h":"#use-typescript","p":190},{"i":203,"t":"Tests should be named after their Component using .unit.ts as the extension: HelloWorld.vue HelloWorld.unit.ts","s":"Name your tests like your components","u":"/docs/nuxt-client/WritingTests","h":"#name-your-tests-like-your-components","p":190},{"i":205,"t":"Especially in large test-files it is very helpful for the reader to have a tree-like structure grouping the tests. So use describe blocks to group tests that are related to the same aspect of your code/the functionality. describe block that contains the filename in the root-level of the test-file sub-describe-blocks for groups of tests focussing the same aspects of your code Example: describe('@components/share/ImportModal', () => { describe('when action button is clicked', () => { ... }); ... describe(\"when backend returns an error\", () => { }); }); Example taken from here Vue NYC - Component Tests with Vue.js - Matt O'Connell describe('@components/something/AddButton', () => { describe(':props', () => { it(':label - should render a button with the passed-in label text', () => { ... }) }); ... describe(\"@events\", () => { it('@add - should emit an \"add\" event when the button is clicked', () => { ... }) }); }); Hint: maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file","s":"Structure your tests using (multiple) \"describe\"-blocks","u":"/docs/nuxt-client/WritingTests","h":"#structure-your-tests-using-multiple-describe-blocks","p":190},{"i":207,"t":"There is a reason we use the it-alias for writing our code and not the test-method: we want to describe the aspect that is tested in a natural sentence. That's why it is best practice to start your test with: it('should ...'); Example: Bad: it('name changes on button click') ... Good: it('should display the info text', ... ); it('should not render migration start button', ... ); it('should return the translation', ... );","s":"Name the test like a sentence \"it should...\"","u":"/docs/nuxt-client/WritingTests","h":"#name-the-test-like-a-sentence-it-should","p":190},{"i":209,"t":"Data-testids are attributes to HTML-elements that are solely used to enable tests to find and check a certain aspect of that tag (often to check the contained text against some expected value). We decided to unify the way data-testid's should be named in Frontend Arch Group: Meeting 2022-11-04 Please use
    in your HTML-code if you want to define a data-testid. do not use uppercase-characters only use one dash - right after data You can later on check this using: // CopyResultModal.unit.ts expect( wrapper.find('[data-testid=\"copy-result-notifications\"]').text() ).toContain( wrapper.vm.$i18n.t(\"components.molecules.copyResult.fileCopy.error\") ); We also recommend to use refs instead of data-testids. But if you do that you ensure not to remove them once they are in the code... as they can be used in the component-code and for testing: VueJs - template refs VueTestUtils - ref","s":"data-testids","u":"/docs/nuxt-client/WritingTests","h":"#data-testids","p":190},{"i":211,"t":"Separate your setup from your actual tests: If you need a more complex setup to test something - write a scope method called \"setup\" for it. Write it in a reusable and configurable way, in order to reuse most of it in several groups of tests. You will get small and easily readable tests and no redudant setup-code inside your tests that contains small differences that are hard to detect.","s":"Setup-methods","u":"/docs/nuxt-client/WritingTests","h":"#setup-methods","p":190},{"i":214,"t":"Use the trigger()-method to simulate a events Testing Key, Mouse and other DOM events Mouse-Click: VueTestUtils - trigger events Keyboard-Input: VueTestUtils - keyboard example Drag & Drop: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that Event from a child component: VueTestUtils - emitting from child component","s":"Events","u":"/docs/nuxt-client/WritingTests","h":"#events","p":190},{"i":216,"t":"You can test asynchronous behavior by using Vue.nextTick(): await Vue.nextTick(); ... OR by triggering an effect and awaiting this effect to take place: const btnNext = wrapper.find(`[data-testid=\"dialog-next\"]`); await btnNext.trigger(\"click\"); ... see also: VueTestUtils - Testing Asynchronous Behavior","s":"Testing Asynchronous Behavior","u":"/docs/nuxt-client/WritingTests","h":"#testing-asynchronous-behavior","p":190},{"i":218,"t":"await expect(() => copyModule.copy(payload)).rejects.toThrow( `CopyProcess unknown type: ${payload.type}` );","s":"Exceptions","u":"/docs/nuxt-client/WritingTests","h":"#exceptions","p":190},{"i":220,"t":"// UserMigration.page.unit.ts const consoleErrorSpy = jest .spyOn(console, \"error\") .mockImplementation(); ... expect(consoleErrorSpy).toHaveBeenCalledWith( expect.any(ApplicationError) ); consoleErrorSpy.mockRestore();","s":"console.error","u":"/docs/nuxt-client/WritingTests","h":"#consoleerror","p":190},{"i":222,"t":"VueTestUtils - Testing composables","s":"Testing Composables","u":"/docs/nuxt-client/WritingTests","h":"#testing-composables","p":190},{"i":224,"t":"Replaces methods, instances of classes (e.g. stores) with some functionality, that e.g. simply returns a value you want to use in your test. By mocking you can easily simulate certain scenarios like failing requests or certain return values from any \"external\" (as in \"not part of the code i am currently testing\") functionality. Jest provides very helpful methods for that. Examples from our codebase: const mock = jest.fn().mockReturnValue(expectedTranslation); copyModuleMock.copyByShareToken = jest.fn() .mockResolvedValue(copyResults); They can easily be tested like this: expect(copyModuleMock.copyByShareToken).toHaveBeenCalled(); Or more specific like this: expect(addFileMetaDataSpy).toHaveBeenCalledWith( expect.objectContaining({ size: 2 } as FileMetaListResponse) ); See also here: VueTestUtils mount - mocks and stubs are now in global","s":"Mocking","u":"/docs/nuxt-client/WritingTests","h":"#mocking","p":190},{"i":226,"t":"Vue.js - Mocking injections VueTestUtils - provide / inject","s":"Mocking injections","u":"/docs/nuxt-client/WritingTests","h":"#mocking-injections","p":190},{"i":228,"t":"Mocking a vuex-store in a component​ Example file: src/components/administration/AdminMigrationSection.unit.ts import { createModuleMocks } from \"@/utils/mock-store-module\"; import YourModule from \"@/store/YourModule\"; let yourModule: jest.Mocked; schoolsModule = createModuleMocks(YourModule, { yourMethodName: { ... }, ...yourGetters, }) as jest.Mocked; mount(YourComponentToBeTested, { ...createComponentMocks({ ... }), provide: { yourModule, }, }); expect(yourModule.).toHaveBeenCalledWith(...); Testing a store​ import YourModule from \"./your-module\"; const yourModule = new YourModule({}); ... // using `jest.spyOn()` it(\"should call something\", () => { const yourActionNameMock = jest.spyOn(yourModule, \"yourActionName\"); yourModule.yourActionName(); expect(yourActionNameMock).toHaveBeenCalled(); }); // or using a method directly it(\"should set something\", () => { yourModule.setLoading(true); expect(yourModule.getLoading).toBe(true); });","s":"Mocking Vuex-Store","u":"/docs/nuxt-client/WritingTests","h":"#mocking-vuex-store","p":190},{"i":230,"t":"*{{ tbd }} (when Pinia-Stores are enabled for the project)*","s":"Mocking Pinia-Stores","u":"/docs/nuxt-client/WritingTests","h":"#mocking-pinia-stores","p":190},{"i":232,"t":"Sometimes - if a composable is simple and does not create sideeffects - it is okay to use it in the tests and avoid mocking it. That's beneficial as it let's us stick to the BlackBox-Idea: we should not know what the component is using internally. If you need to mock a composable, you can simple do this like in the following example. You only have to ensure to return everything the composable returns... but mocked versions of it. ... jest.spyOn(ourExampleComposable, \"useExample\").mockReturnValue({ // return mocks of what the composable would have returned }); ...","s":"Mocking Composables","u":"/docs/nuxt-client/WritingTests","h":"#mocking-composables","p":190},{"i":234,"t":"If you ever get into trouble to write good tests for your compents or code in general - this might be an indicator, that maybe your code is not structured good enough. Consider: spliting your component into smaller sub-components with a small API extracting functionality into one or mutliple composables using an existing composable (from VueUse or an existing one in the project) using an existing vuetify-component instead of writing it all yourself reshaping the communication workflow (parameters, events, inject/provide, stores, composables) (replacing a Vuex-store with a Pinia-store) For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd)","s":"Components that are hard to test","u":"/docs/nuxt-client/WritingTests","h":"#components-that-are-hard-to-test","p":190},{"i":236,"t":"(aka Integration/Acceptance/System-Tests) End-to-End-Tests are developed in a seperate repository end-to-end-tests Documentation of e2e tests","s":"End-To-End-Tests","u":"/docs/nuxt-client/WritingTests","h":"#end-to-end-tests","p":190},{"i":238,"t":"For monitoring our code-coverage we are using Codacy. The current status can be seen on this Dashboard.","s":"Code-Coverage","u":"/docs/nuxt-client/WritingTests","h":"#code-coverage","p":190},{"i":241,"t":"In nest.js all apis are defined in controllers. Usually the api follows the following syntax: /api/v3/ Each controller is responsible for a specific resource. The controller is responsible for the routing and the validation of the request. The controller calls a service to handle the request. The service is responsible for the business logic. The service calls a repository to access the database. The repository is responsible for the database access.","s":"nest.js","u":"/docs/schulcloud-server/Api","h":"#nestjs","p":239},{"i":243,"t":"When returning a response like this: @ApiOperation({ summary: 'Create a new element on a card.' }) @ApiExtraModels( ExternalToolElementResponse, FileElementResponse, LinkElementResponse, RichTextElementResponse, SubmissionContainerElementResponse ) @ApiResponse({ status: 201, schema: { oneOf: [ { $ref: getSchemaPath(ExternalToolElementResponse) }, { $ref: getSchemaPath(FileElementResponse) }, { $ref: getSchemaPath(LinkElementResponse) }, { $ref: getSchemaPath(RichTextElementResponse) }, { $ref: getSchemaPath(SubmissionContainerElementResponse) }, ], }, }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 403, type: ForbiddenException }) @ApiResponse({ status: 404, type: NotFoundException }) @Post(':cardId/elements') async createElement( @Param() urlParams: CardUrlParams, @Body() bodyParams: CreateContentElementBodyParams, @CurrentUser() currentUser: ICurrentUser ): Promise { const { type, toPosition } = bodyParams; const element = await this.cardUc.createElement(currentUser.userId, urlParams.cardId, type, toPosition); const response = ContentElementResponseFactory.mapToResponse(element); return response; } We want to use decorators to explain the intent of the response. The @ApiOperation decorator is used to define the summary. The @ApiResponse decorator is used to define the response. The @ApiExtraModels decorator is used to define the response models. The final response should either be an javascript Object or an array. We do not return primitives like string or boolean. Swagger will automatically generate the response schema from the response object.","s":"Responses","u":"/docs/schulcloud-server/Api","h":"#responses","p":239},{"i":246,"t":"Our architecture aims to achieve the following goals: Maintainability it should be easy as possible to make changes that do not change the behaviour of the system (refactoring) it should be easy to exchange entire components of the system, without impact on other components. Extendability it should be easy to add new functionality to the system Agility it should be easy to react to changing requirements during our development process Change Security it should be easy to determine the correctness of the system after making any changes","s":"Goals","u":"/docs/schulcloud-server/architecture","h":"#goals","p":244},{"i":248,"t":"In order to achieve these goals, we try to follow the principles detailed below. These principles apply to all layers of our software, from lines of code and methods to modules and architectural layers. Single Responsibility / Seperation of Concerns each piece of code should have a single layer of abstraction/detail each piece of code should have a single reason to change Open/Closed Principle design to be open to extension, but closed to modification Liskov Substitution the specific input may be more generic than its interface the specific output may be more specialized than its interface Interface Segregation multiple small interfaces are preferred over big interfaces Dependency Inversion Principle always depend on interfaces, not implementations higher level parts should not depend on lower level parts. Keep It Simple (KISS) any piece of code should be simple and readable any logic should be broken down to be trivial beware of overenginiering and premature optimisation You Aint Gonna Need It (YAGNI) keep decisions open for as long as possible build only what you need to build, stay flexible for future requirements Do Not Repeat Yourself (DRY) do not solve the same responsability or concern in multiple places beware of things that look similar, but are not. for example, things that change for different reasons should not be combined, even if their code looks the same","s":"Principles","u":"/docs/schulcloud-server/architecture","h":"#principles","p":244},{"i":250,"t":"We generally distinguish three different layers in our server architecture: The API Layer, the Repository Layer, and the Domain Layer. Note that based on the Dependency Inversion Principle, the Domain Layer does not have any dependencies. Instead, both the API and Repository Layer depend on its abstractions.","s":"Server Layer Architecture","u":"/docs/schulcloud-server/architecture","h":"#server-layer-architecture","p":244},{"i":252,"t":"The Domain Layer contains the business logic of the application. As mentioned above, it is not allowed to know about anything outside the domain layer itself. Any operation within the system is defined by a usecase (UC). It describes how an external actor, for example a user, can interact with the system. Each usecase defines what needs to be done to authorize it, and what needs to be done to fulfill it. To this end, it orchestrates services. A service is a public part of a domain module, that provides an interface for logic. It might be a simple class doing simple calculations, an interface to a complex hierarchy of classes within a module, or anything in between. The domain layer might also define other classes, types, and interfaces to be used internally by its services, as well as the interface definitions for the repository layer. That way, the domain does not have to depend on the repositories, and the repositories have to depend on the domain instead (dependency inversion) TODO: the exact way of implementing the interfaces between repositories and domain layer is still in active discussion and development within the architecture chapter","s":"Domain Layer","u":"/docs/schulcloud-server/architecture","h":"#domain-layer","p":244},{"i":254,"t":"The API Layer is responsible for providing the API that is exposed outside the system, and to map the various incoming requests into domain DTOs. The params.dto and response.dto are used to automatically generate the API Documentation based on openAPI. The params.dto also contains information that is used for input validation. The controller is responsible for sanitizing and authenticating incoming requests, and to map to and from the format that the domain usecase implementations expect. To this end, mappers are being used.","s":"API Layer","u":"/docs/schulcloud-server/architecture","h":"#api-layer","p":244},{"i":256,"t":"The Repository Layer is responsible for outgoing requests to external services. The most prominent example is accessing the database, but the same principles apply for sending emails or other interactions with external systems. In order to access these external systems without knowing them, the domain layer may define interfaces that describe how it would like to use external services in its own domain language. The repositories implement these interfaces, recieving and returning exclusively objects or dtos defined in the domain. The datamodel itself is defined through Entities, that have to be mapped into domain objects before they can be returned to the domain layer. We use MikroORM to create, persist and load the entities and their references among each other.","s":"Repository Layer","u":"/docs/schulcloud-server/architecture","h":"#repository-layer","p":244},{"i":258,"t":"The codebase is broken into modules, each dealing with a part of the businesslogic, or seperated technical concerns. These modules define what code is available where, and ensure a clean dependency graph. All Code written should be part of exactly one module. Each module contains any services, typedefinitions, interfaces, repositories, mappers, and other files it needs internally to function. When something is needed in more than one module, it needs to be explicitly exported by the module, to be part of its public interface. It can then be imported by other modules. Services are exported published via the dependency injection mechanism provided by Nestjs. @Module({ providers: [InternalRepo, InternalService, PublicService], exports: [PublicService], }) export class ExampleModule {} @Module({ imports: [ExampleModule] providers: [SomeOtherService], }) export class OtherModule {} Notice that in the above example, the PublicService can be used anywhere within the OtherModule, including in the SomeOtherService, whereas the InternalRepo and InternalService can not. Things that cant be injectables, like types and interfaces, are exported via the index file at the root of the module. Code that needs to be shared across many modules can either be put into their own seperate module, if there is a clearly defined seperate concern covered by it, or into the shared module if not.","s":"Modules","u":"/docs/schulcloud-server/architecture","h":"#modules","p":244},{"i":260,"t":"The controllers and the corresponding usecases, along with the api tests for these routes, are seperated into api modules @Module({ imports: [ExampleModule] providers: [ExampleUc], controllers: [ExampleController], }) export class ExampleApiModule {} This allows us to include the domain modules in different server deployments, without each of them having all api definitions. This also means that no usecase can ever be imported, as only services are ever exported, enforcing a seperation of concerns between logic and orchestration.","s":"Api Modules","u":"/docs/schulcloud-server/architecture","h":"#api-modules","p":244},{"i":262,"t":"The application is split into different modules that implement different parts of our domain. The exact split of modules is still work in progress, or left open as implementation detail. Some important considerations are: things with high cohesion and coupling should be in the same module things with low coupling should be in seperate modules the modules define an explicit public interface of usecases and types they expose to other modules no module should ever try to access a class of a different module that is not explicitly exported no injectable should ever be defined in more than one module a module should only export services to be used by other modules. a module that other modules might need to import, especially in another mikroservice, should not contain controllers.","s":"Horizontal Architecture","u":"/docs/schulcloud-server/architecture","h":"#horizontal-architecture","p":244},{"i":266,"t":"The name of a function should clearly communicate what it does. There should be no need to read the implementation of a function to understand what it does. There are a few keywords that we use with specific meaning: \"is...\"​ isTask(), isPublished(), isAuthenticated(), isValid() A function with the prefix \"is...\" is checking wether the input belongs to a certain (sub)class, or fulfils a specific criteria. The function should return a boolean, and have no sideeffects. \"check...\"​ checkPermission(), checkInputIsValid() A function with the prefix \"check...\" is checking the condition described in its name, throwing an error if it does not apply. \"has...\"​ hasPermission(), similar to \"is...\", the prefix \"has...\" means that the function is checking a condition, and returns a boolean. It does NOT throw an error.","s":"Naming functions","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-functions","p":263},{"i":268,"t":"avoid directly returning the result of some computation. Instead, use a variable to give the result of the computation a name. Exceptions can be made when the result of the computation is already clear from the function name, and the function is sufficiently simple to not occlude its meaning. public doSomething(): FileRecordParams[] { // ... more logic here const fileRecordParams = fileRecords.map((fileRecord) => Mapper.toParams(fileRecord)); // hint: this empty line can be increase the readability return fileRecordParams; } public getName(): String { return this.name; } public getInfo(): IInfo { // ... more logic here return { name, parentId, parentType }; // but if the return include many keys, please put it first to a const }","s":"Avoid direct returns of computations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-direct-returns-of-computations","p":263},{"i":270,"t":"function badExample(): void { doSomething(this.extractFromParams(params), this.createNewConfiguration()); } function goodExample(): void { const neededParams = this.extractFromParams(params); const configuration = this.createNewConfiguration(); doSomething(neededParams, configuration); }","s":"avoid directly passing function results as parameters","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-directly-passing-function-results-as-parameters","p":263},{"i":272,"t":"public doSomething(): FileRecords[] { //... return fileRecords }","s":"explicit return type","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#explicit-return-type","p":263},{"i":275,"t":"In most cases, it should not matter to the reader/external code wether something is an interface or an implementation. Only prefix the \"I\" when necessary, or when its specifically important to the external code to know that its an interface, for example when external code is required to implement the interface. interface CourseRepo { getById(id): Course // ... } class InMemoryCourseRepo implements CourseRepo { getById(id): Course { return new Course() } }","s":"Avoid the \"I\" for interfaces","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#avoid-the-i-for-interfaces","p":263},{"i":278,"t":"Classes are declared in the following order: properties constructor methods Example: export class Course { // 1. properties name: string; // more properties... // 2. constructor constructor(props: { name: string }) { // ... } // 3. methods public getShortTitle(): string { // ... } // more methods... }","s":"Order of declarations","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#order-of-declarations","p":263},{"i":280,"t":"Classes should be named in CamelCase. They should have a Suffix with the kind of Object they represent, and from the beginning to the end go from specific to general. CourseController CourseCreateBodyParam CourseCreateQueryParam CourseCreateResponse CourseDtoMapper CourseUc CourseAuthorisationDto CourseDo CourseService CourseRepo CourseEntity CourseEntityMapper","s":"Naming classes","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#naming-classes","p":263},{"i":282,"t":"You should always try to write code in a way that does not require further explanation to understand. Use proper names for functions and variables, and extract code and partial results into functions or variables in order to name them. If you feel like a function needs a JsDoc, treat that as a codesmell, and try to rewrite the code in a way that is more self-explanatory.","s":"Do NOT use JsDoc","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-not-use-jsdoc","p":263},{"i":284,"t":"empty lines help to structure code. Use them wherever you want to seperate parts of a function from each other (and think about further extracting functions). Common uses include: before return statement before and after an if/else statement between test sections (arrange, act, assert) between \"it()\" statements in tests","s":"Do use empty lines","u":"/docs/schulcloud-server/Coding-Guidelines/code-style","h":"#do-use-empty-lines","p":263},{"i":286,"t":"Make sure to have the following software installed node 18 git clone git@github.com:hpi-schul-cloud/schulcloud-client.git Install the packages cd schulcloud-client npm ci npm run build The last step is to start the dev server with. npm run watch If you want to change the instance you need to set an env variable declare -x SC_THEME=\"n21\" node node_modules/gulp/bin/gulp.js clear-cache && node node_modules/gulp/bin/gulp.js npm run watch","s":"Getting started","u":"/docs/schulcloud-client/Getting started","h":"","p":285},{"i":288,"t":"A modules api layer is defined within of controllers. The main responsibilities of a controller is to define the REST API interface as openAPI specification and map DTO's to match the logic layers interfaces. @ApiOperation({ summary: 'some descriptive information that will show up in the API documentation' }) @ApiResponse({ status: 200, type: BoardResponse }) @ApiResponse({ status: 400, type: ApiValidationError }) @ApiResponse({ status: 403, type: ForbiddenException }) @ApiResponse({ status: 404, type: NotFoundException }) @Post() async create(@CurrentUser() currentUser: ICurrentUser, @Body() params: CreateNewsParams): Promise { const news = await this.newsUc.create( currentUser.userId, currentUser.schoolId, NewsMapper.mapCreateNewsToDomain(params) ); const dto = NewsMapper.mapToResponse(news); return dto; }","s":"Controller","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"","p":287},{"i":290,"t":"For authentication, use guards like JwtAuthGuard. It can be applied to a whole controller or a single controller method only. Then, the authenticated user can be injected using the @CurrentUser() decorator.","s":"JWT-Authentication","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#jwt-authentication","p":287},{"i":292,"t":"Global settings of the core-module ensure request/response validation against the api definition. Simple input types might additionally use a custom pipe while for complex types injected as query/body are validated by default when parsed as DTO class.","s":"Validation","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#validation","p":287},{"i":294,"t":"All data that leaves or enters the system has to be defined and typed using DTOs. export class CreateNewsParams { @IsString() @SanitizeHtml() @ApiProperty({ description: 'Title of the News entity', }) title!: string; // ... }","s":"DTOs","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dtos","p":287},{"i":296,"t":"Complex input DTOs are defined like [create-news].params.ts (class-name: CreateNewsParams). When DTO's are shared between multiple modules, locate them in the layer-related shared folder. Security: When exporting data, internal entities must be mapped to a response DTO class named like [news].response.dto. The mapping ensures which data of internal entities are exported.","s":"DTO File naming","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#dto-file-naming","p":287},{"i":298,"t":"Defining the request/response DTOs in a controller will define the openAPI specification automatically. Additional validation rules and openAPI definitions can be added using decorators. For simplification, openAPI decorators should define a type and if a property is required, while additional decorators can be used from class-validator to validate content.","s":"openAPI specification","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#openapi-specification","p":287},{"i":300,"t":"You should define a mapper to easily create dtos from the uc responses, and the datatypes expected by ucs from params.","s":"Mapping","u":"/docs/schulcloud-server/Coding-Guidelines/controllers","h":"#mapping","p":287},{"i":303,"t":"The FeathersModule provides functionality to access legacy code. In order to introduce strong typing, it is necessary to write an adapter service for the feathers service you want to access. Place this adapter within your module, and use the FeathersServiceProvider to access the service you need // inside your module, import the FeathersModule @Module({ imports: [FeathersModule], providers: [MyFeathersServiceAdapter], }) export class MyModule {} // inside of your service, inject the FeathersServiceProvider @Injectable() export class MyFeathersServiceAdapter { constructor(private feathersServiceProvider: FeathersServiceProvider) {} async get(): Promise { const service = this.feathersServiceProvider.getService(`path`); const result = await service.get(...) return result; }","s":"Access Feathers Service from NestJS","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-feathers-service-from-nestjs","p":301},{"i":305,"t":"To access a NestJS service from a legacy Feathers service you need to make the NestJS service known to the Feathers service-collection in main.ts. This possibility should not be used for new features in Feathers, but it can help if you want to refactor a Feathers service to NestJs although other Feathers services depend on it. // main.ts async function bootstrap() { // (...) feathersExpress.services['nest-rocket-chat'] = nestApp.get(RocketChatService); // (...) } Afterwards you can access it the same way as you access other Feathers services with app.service('/nest-rocket-chat');","s":"Access NestJS injectable from Feathers","u":"/docs/schulcloud-server/Coding-Guidelines/access-legacy-code","h":"#access-nestjs-injectable-from-feathers","p":301},{"i":307,"t":"The Configuration Management consists of three layers working together. Application Layer: Defines the Variables the application needs as well es their type, and validates their values on application start. Configmap: Provides the Configuration as environment variables, making them available to the application. dof-configuration: defines configuration values for various instances and deployment stages. Each of those layers is described in more detail below.","s":"Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"","p":306},{"i":309,"t":"WORK IN PROGRESS we are still working to refine how the configuration should work within the application. Specifically, we still want to: remove the hpi-common library which currently takes care of defining a configuration schema and validating the values streamline each modules definition of necessary configuration values in NestJs consider how to implement runtime configuration and configuration changes improve the workflow of introducing (and removing!) feature flags TODO: document how config files in nest work TODO: document how the config schema works","s":"Application Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#application-configuration","p":306},{"i":311,"t":"Each of our code repositories contains the ansible files necessary for deploying the application(s) within. The configmap can be found under ansible/roles/:application-name:/templates/:application-name-configmap.yml.j2. apiVersion: v1 kind: ConfigMap metadata: name: application-name-configmap namespace: {{ NAMESPACE }} labels: app: application-label data: SOME_CONFIGURATION_KEY: \"{{ SOME_CONFIGURATION_KEY }}\" SOME_RENAMED_CONFIGURATION_KEY: \"{{ SOME_ORIGINAL_CONFIGURATION_KEY }}\" HARDCODED_VALUE: \"value\" COMPOSED_VALUE: \"https://{{ DOMAIN }}/some/thing\" under ansible/roles/:application-name:/defaults we define default values to be used when no values are provided from the environment.","s":"Config Maps","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#config-maps","p":306},{"i":313,"t":"The actual configuration values used for our deployments can be found in the dof_app_deploy repository under ansible/group_vars. Here, you will find various yml files containing configuration values. Kubernetes will apply each of these files in a defined order, beginning with the folder all, then applying the files in the folder corresponding to the deployment stage (eg. development, reference, production), and finally the files in the folder corresponding to the instance (eg. brb, dbc, thr, nbc.) Values in files that are applied later will overwrite earlier values. All of these values are gathered as kubernetes facts, and can theoretically be used by all config maps, ensuring the configurations of different applications are consistent with each other. Do note that a value must be mapped in a config map to be available to a given application.","s":"dof Configuration","u":"/docs/schulcloud-server/Coding-Guidelines/configuration","h":"#dof-configuration","p":306},{"i":315,"t":"If you need to validate a domain object, please write an independent class, so that the domain object itself, its repo and services can reuse it. Eric Evans suggests using the specification pattern. A specification fulfills the following interface: public interface Specification { boolean isSatisfiedBy(T t); } A specification checks if a domain object fulfills the conditions of the specification. A specification can simply specify that a domain object is valid. E.g. a Task has an owner and a description. A specification can specify more complex and specialized conditions. E.g. Task where every student assigned to the task's course has handed in a submission. The specification pattern in its full extend describes how to use logic operators to combine multiple specifications into combined specifications as well. Please don't build this as long as you don't need it. YAGNI. More about full specification pattern","s":"Domain Object Validation","u":"/docs/schulcloud-server/Coding-Guidelines/domain-object-validation","h":"","p":314},{"i":318,"t":"When we are replacing code which is used in other modules we should use a 2 step migration. This is meant to prevent merge conflicts and to make it easier to review the changes. Also it makes it easier to find the code which needs to be changed in the other modules. Please note that this is not always possible, but should be used when possible. Step 1: Add new alternative code Step 2: Mark the old code with \"@deprecated\" add a hint in the comments to use the new code. Step 3: Inform team Step 4: Remove deprecated code (when is hard to say, once all dependencies are removed)","s":"When to use 2 step migration","u":"/docs/schulcloud-server/Coding-Guidelines/deprection-workflow","h":"#when-to-use-2-step-migration","p":316},{"i":320,"t":"For logging use the Logger, exported by the logger module. It encapsulates a Winston logger. Its injection scope is transient, so you can set a context when you inject it. For better privacy protection and searchability of logs, the logger cannot log arbitrary strings but only so called loggables. If you want to log something you have to use or create a loggable that implements the Loggable interface. The message should be fixed in each loggable. If you want to log further data, put in the data field of the LogMessage, like in the example below. export class YourLoggable implements Loggable { constructor(private readonly userId: EntityId) {} getLogMessage(): LogMessage { return { message: 'I am a log message.', data: { userId: this.userId, }, }; } } import { Logger } from '@src/core/logger'; export class YourUc { constructor(private logger: Logger) { this.logger.setContext(YourUc.name); } public sampleUcMethod(user) { this.logger.log(new YourLoggable(userId: user.id)); } } This produces a logging output like [NestWinston] Info - 2023-05-31 15:20:30.888 [YourUc] { message: 'I am a log message.', data: { userId: '0000d231816abba584714c9e' }}","s":"Logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"","p":319},{"i":322,"t":"The logger exposes the methods log, warn, debug and verbose. It does not expose an error method because we don't want errors to be logged manually. All errors are logged in the exception filter.","s":"Log levels and error logging","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#log-levels-and-error-logging","p":319},{"i":324,"t":"While transitioning to the new logger for loggables, the old logger for strings is still available as LegacyLogger.","s":"Legacy logger","u":"/docs/schulcloud-server/Coding-Guidelines/logging","h":"#legacy-logger","p":319},{"i":326,"t":"Internal Events are used as a mechanism for Dependency Inversion. If you are implementing an operation in a module that needs to trigger an operation in another module, that is simple if you can simply import a service. However, if that other module already has a dependency on your module, that would lead to a dependency cycle. In this case, you need to inverse one of the dependencies via events. The main thing you need to think about, is which module should know about which module(s). This is the dependency, and it only ever can point into one direction. As a general rule of thumb, the module that is more specific, or is changing more frequently, or is less central to the functionality of the system, should have the dependency on the other. In the following example, the course module has a dependency on the user module, but NOT vice versa.","s":"Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"","p":325},{"i":328,"t":"consider the following folder structure - users - api - domain - services - events - user-deleted.event.ts - repo - courses - api - domain - services - handlers - user-deleted.handler.ts - repo each of the modules needs to import the CqrsModule // users.module.ts import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; @Module({ imports: [CqrsModule], providers: [/* some things here */], exports: [/* some things here */], }) export class GroupModule {}","s":"How to implement Event Handling","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#how-to-implement-event-handling","p":325},{"i":330,"t":"The event is in the end simply a class, containing any data required to handle the event // users/domain/events/user-deleted.event.ts export class UserDeletedEvent { id: EntityId constructor(id: EntityId) { this.id = id } } Make sure to make your event public in the index file of your module","s":"Defining an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#defining-an-event","p":325},{"i":332,"t":"// users/domain/services/service.ts import { EventBus } from '@nestjs/cqrs'; import { UserDeletedEvent } from '../events'; @Injectable() export class Service { constructor(private readonly eventBus: EventBus) {} public async delete(userId: EntityId): Promise { doStuffForDeletion() await this.eventBus.publish(new UserDeletedEvent(userId)); } }","s":"Sending an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#sending-an-event","p":325},{"i":334,"t":"// courses/domain/handler/user-deleted.handler.ts import { UserDeletedEvent } from '@modules/users'; import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { SomeService } from '../services' @Injectable() @EventsHandler(UserDeletedEvent) export class GroupDeletedHandlerService implements IEventHandler { constructor(private readonly someService: SomeService) {} public async handle(event: GroupDeletedEvent): Promise { await someService.doSomeStuff() } } Note that the handler should not contain any logic, but only the orchestration of what needs to be done.","s":"Recieving an Event","u":"/docs/schulcloud-server/Coding-Guidelines/event-handling","h":"#recieving-an-event","p":325},{"i":336,"t":"We separate our business exceptions from technical exceptions. While for technical exceptions, we use the predefined HTTPExceptions from NestJS, business exceptions inherit from abstract BusinessException. By default, implementations of BusinessException must define code: 500 type: \"CUSTOM_ERROR_TYPE\", title: \"Custom Error Type\", message: \"Human readable details\", // additional: optionalData There is a GlobalErrorFilter provided to handle exceptions, which cares about the response format of exceptions and logging. It overrides the default NestJS APP_FILTER in the core/error-module. In client applications, for technical errors, evaluate the http-error-code, then for business exceptions, the type can be used as identifier and additional data can be evaluated. For business errors we use 409/conflict as default to clearly have all business errors with one error code identified. Sample: For API validation errors, 400/Bad Request will be extended with validationError: ValidationError[{ field: string, error: string }] and a custom type API_VALIDATION_ERROR. Pipes can be used as input validation. To get errors reported in the correct format, they can define a custom exception factory when they should produce api validation error or other exceptions, handled by clients.","s":"Exception Handling","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"","p":335},{"i":338,"t":"If you catch an error and throw a new one, put the original error in the cause property of the new error. See example: try { someMethod(); } catch(error) { throw new ForbiddenException('some message', { cause: error }); }","s":"Chaining errors with the cause property","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#chaining-errors-with-the-cause-property","p":335},{"i":340,"t":"If you want the error log to contain more information than just the exception message, use or create an exception which implements the Loggable interface. Don't put data directly in the exception message! A loggable exception should extend the respective Built-in HTTP exception from NestJS. For the name just put in \"Loggable\" before the word \"Exception\", e.g. \"BadRequestLoggableException\". Except for logging a loggable exception behaves like any other exception, specifically the error response is not affected by this. See example below. export class UnauthorizedLoggableException extends UnauthorizedException implements Loggable { constructor(private readonly username: string, private readonly systemId?: string) { super(); } getLogMessage(): ErrorLogMessage { const message = { type: 'UNAUTHORIZED_EXCEPTION', stack: this.stack, data: { userName: this.username, systemId: this.systemId, }, }; return message; } } export class YourService { public sampleServiceMethod(username, systemId) { throw new UnauthorizedLoggableException(username, systemId); } }","s":"Loggable exceptions","u":"/docs/schulcloud-server/Coding-Guidelines/exception-handling","h":"#loggable-exceptions","p":335},{"i":342,"t":"When defining entities with MikroORM (Version 5), the following should be considered: The property decorator requires explicit assignment of the type to the property and may not work correctly when working with type inference or assigning union types to a property. In these cases, the metadata may not be set correctly, which can lead to exceptions, for example, when using the em.assign() or em.aggregate() functions. Therefore, the following is not sufficient: @Property() termsAccepted = false; @Property() createdAt = new Date(); The following works: @Property() termsAccepted: boolean = false; @Property() createdAt: Date = new Date(); The better way is to provide the type through the decorator: @Property({ type: 'boolean' }) termsAccepted = false; @Property({ type: Date }) createdAt = new Date(); Errors can also occur when specifying multiple types (union types): @Poperty({ nullable: true }) dueDate: Date | null; To set the metadata correctly, do the following: @Property({ type: Date, nullable: true }) dueDate: Date | null; If type inference is not used, specifying the type through the property decorator is not necessary: @Property() name: string;","s":"Defining Entities","u":"/docs/schulcloud-server/Coding-Guidelines/micro-orm","h":"","p":341},{"i":344,"t":"The repository is responsible to provide domain objects for the domain layer. Typically, it does so by accessing a database. Since the domain layer should be isolated (not have knowledge of the outer layers), the domain layer should have an interface definition for each repository it wants to access. This naturally means that the interface can only mention domain objects, without any knowledge about database entities. // somewhere in the domain layer... export interface SchoolRepo { getSchoolById(schoolId: EntityId): Promise; } The repository itself can now implement this interface, in this example using MikroOrm to get data from a database @Injectable() export class SchoolMikroOrmRepo implements SchoolRepo { constructor(private readonly em: EntityManager) {} public async getSchoolById(schoolId: EntityId): Promise { const entity = await this.em.findOneOrFail( SchoolEntity, { id: schoolId }, ); const school = SchoolEntityMapper.mapToDo(entity); return school; } } Note that the internal entity manager uses a different datatype, the SchoolEntity, to represent schooldata. This type includes information that is specific to mikroorm, and should not be mixed into the domain object definition. A mapper is used to explicitly map the entity to the domain object, and vice versa.","s":"Repositories","u":"/docs/schulcloud-server/Coding-Guidelines/repositories","h":"","p":343},{"i":346,"t":"In this guide, we'll cover how to use modules, submodules and barrel files in our project. These concepts help you organize your code into separate files and directories, making it easier to manage and maintain.","s":"Implementation and usage of modules, submodule and barrel files in our project","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"","p":345},{"i":348,"t":"In our project, modules are a way to create separate scopes. This means that interfaces, use cases, services, etc., declared in a module are not visible outside the module unless they are explicitly exported using the export keyword. To import a module, you use the import keyword followed by the module name. Here's an example: import { ModuleName } from '@modules/module-name'; Submodules are modules that are part of a larger module. They should be used within the main module. If it is necessary to use parts of the submodule outside the main module, the main module should export this via its barrel file(index.ts): // @modules/module-name/index.ts export { SubmoduleServiceName } from './submodule-name/service.ts';","s":"Modules and Submodules","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#modules-and-submodules","p":345},{"i":350,"t":"Barrel files are a way to rollup exports from several folders into a single convenient nest-module. The barrel itself is a module file that re-exports selected exports of other submodules. If you have several related service/interface files in a directory/module that should be publicly accessible, you can create a barrel file to export all these files from the main module again. Here's an example of a barrel file: // @modules/module-name/index.ts export { PublicService } from './services/public-service.ts'; export { ServiceInterfaceA, InterfaceB } from './interfaces'; export { InterfaceC } from './submodule-name/interfaces'; !!! Please don't export everything from a module in the barrel file. Only export the public API of the module. This will make it easier to understand what the module provides and avoid unnecessary dependencies. And don't use wildcard exports like export * from './services' in the barrel file. And here's how you can import from the barrel: // @modules/other-module-name/service.ts import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/module-name';","s":"Barrel Files","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#barrel-files","p":345},{"i":352,"t":"Circular dependencies occur when Module A depends on Module B, and Module B also depends on Module A. This can lead to unexpected behavior and hard-to-diagnose bugs. Here are some strategies to handle circular dependencies: Refactor Your Code: The best way to handle circular dependencies is to refactor your code to remove them. This might involve moving some code to a new module to break the dependency cycle. // @modules/moduleC/service.ts import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleA'; import { PublicService, InterfaceOfModule, InterfaceOfSubmodule } from '@modules/moduleB'; Use Interfaces: If the circular dependency is due to types, you can use interfaces and type-only imports to break the cycle. // @modules/moduleC/service.ts import { type PublicService } from '@modules/moduleA'; import { type PublicService } from '@modules/moduleB'; Use Events: If you have a circular dependency between two modules that need to communicate with each other, consider using events to decouple them. This way, one module can emit an event that the other module listens to, without directly importing it. https://documentation.dbildungscloud.dev/docs/schulcloud-server/Coding-Guidelines/event-handling https://docs.nestjs.com/recipes/cqrs Remember, circular dependencies are usually a sign of tightly coupled code and can lead to maintenance issues down the line. It's best to refactor your code to avoid them if possible.","s":"Handling Circular Dependencies","u":"/docs/schulcloud-server/Coding-Guidelines/modules-submodules","h":"#handling-circular-dependencies","p":345},{"i":355,"t":"Each change should be done in a ticket (no matter how small) The ticket does not need to be refined for very small things Might be relevant for reporting later Folder (feature/..) should no longer be used Stay below 64 letters Do not simply use ticket title, usually we need a shorter description :-) Ticket number needs to be uppercase (BC-1234) Related to matching with Jira Careful: namespace is lowercase BC-XXXX-kebab-case-short-description","s":"Branch name conventions","u":"/docs/schulcloud-server/Development/git","h":"#branch-name-conventions","p":353},{"i":357,"t":"Squashed commit subject should start with a ticket number, and end with a PR number Clean body (contains all commits by default) Only leave changes relevant for main Remove commits likes 'fix for linter', 'add tests', 'fix review comments' See example below Write commit messages in imperative and active Good: \"make the code better\" Bad: \"made the code better\", \"makes the code better\" Feel free to write actual text BC-1993 - lesson lernstore and geogebra copy (#3532) In order to make sure developers in the future can find out why changes have been made, we would like some descriptive text here that explains what we did and why. - change some important things - change some other things - refactor some existing things # I dont need to mention tests, changes that didnt make it to main, linter, or other fixups # only leave lines that are relevant changes compared to main # comments like this will not actually show up in the git history","s":"Commit message conventions","u":"/docs/schulcloud-server/Development/git","h":"#commit-message-conventions","p":353},{"i":359,"t":"We strongly recommend to let git translate line endings. Please set git config --global --add core.autocrlf input when working with windows.","s":"Windows","u":"/docs/schulcloud-server/Development/git","h":"#windows","p":353},{"i":361,"t":"Automated testing is the essential part of the software development process. It improves the code quality and ensure that the code operates correctly especially after refactoring.","s":"Testing","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"","p":360},{"i":364,"t":"The tests should be as simple to read and understand as possible. They should be effortless to write and change, in order to not slow down development. Wherever possible: avoid complex logic cover only one case per test only use clearly named and widely used helper functions stick to blackbox testing: think about the unit from the outside, not its inner workings. its okay to duplicate code for each test","s":"Lean Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#lean-tests","p":360},{"i":366,"t":"When a test fails, the name of the test is the first hint to the developer (or any other person) to what went wrong where. (along with the \"describe\" blocks the test is in). Thus, your describe structure and testcase names should be designed to enable a person unfamiliar with the code to identify the problem as fast as possible. It should tell him: what component is being tested under what condition the expected outcome To facilitate this, your tests should be wrapped in at least two describe levels. // Name of the unit under test describe(\"Course Service\", (() => { // method that is called describe('createCourse', () => { // a \"when...\" sentence describe(\"When a student tries to create a course\", (() => { const setup = () => { // testsetup for the situation that was described } // a \"should...\" sentence it(\"should return course\", async () => { ... }); }); }); });","s":"Naming Convention","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#naming-convention","p":360},{"i":368,"t":"Each test should be able to run alone, as well as together with any other tests. To ensure this, it is important that the test does not depend on any preexisting data. Each test should generate the data it needs, and ensure that its data is deleted afterwards. (this is usually done via mocha's \"afterEach\" function. When you create objects with fields that have to be globally unique, like the account username, you must ensure the name you choose is unique. This can be done by including a timestamp. Never use seeddata.","s":"Isolation","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#isolation","p":360},{"i":370,"t":"Your test should be structured in three seperate areas, each distinguished by at least an empty line: Arrange - set up your testdata Act - call the function you want to test Assert - check the result this is known as the AAA-pattern. The tests for a unit should cover as much scenarios as possible. Parameters and the combination of parameters can often take numerous values. Therefore it largely differs from case to case what a sufficient amount of scenarios would be. Parameter values that contradict the typescript type definition should be ignored as a test case. The test coverage report already enforces scenarios that test every possible if/else result in the code. But still some scenarios are not covered by the report and must be tested: All error scenarios: That means one describe block for every call that can reject. We use different levels of describe blocks to structure the tests in a way, that the tested scenarios could easily be recognized. The outer describe would be the function call itself. Every scenario is added as another describe inside the outer describe. All of the data and mock preparation should happen in a setup function. Every describe scenario only contains one setup function and is called in every test. No further data or mock preparation should be added to the test. Often there will be only one test in every describe scenario, this is perfectly fine with our desired structure. describe('[method]', () => { describe('when [senario description that is prepared in setup]', () => { const setup = () => { // prepare the data and mocks for this scenario }; it('...', () => { const { } = setup(); }); it('...', () => { const { } = setup(); }); }); describe('when [senario description that is prepared in setup]', () => { const setup = () => { // prepare the data and mocks for this scenario }; it('...', () => { const { } = setup(); }); }); });","s":"Test Structure","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#test-structure","p":360},{"i":373,"t":"When assigning a value to an expect, separate the function call from the expectation to simplify debugging. This later helps when you not know about the return value type or if it's an promise or not. This is good style not only for tests. // doSomethingCrazy : retValue it('bad sample', () => { expect(doSomethingCrazy(x,y,z)).to... }) it('good sample', () => { const result = doSomethingCrazy(x,y,z) expect(result).to... // here we can simply debug })","s":"Handling of function return values","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#handling-of-function-return-values","p":360},{"i":375,"t":"When using asynchronous functions and/opr promises, results must be awaited within of an async test function instead of using promise chains. While for expecting error conditions it might be helpful to use catch for extracting a value from an expected error, in every case avoid writing long promise chains. Instead of using done callback, use async test functions. Use await instead of (long) promise chains never manually set a timeout // doSomethingCrazy : Promise it('bad async sample', async function (done) => { return doSomethingCrazy(x,y,z).then(result=>{ expect(result).to... done() // expected done }).catch(()=>{ logger.info(`Could not ... ${error}`); done() // unexpected done, test will always succeed which is wrong }) }, 10000 /* timeout in ms */) it('good async sample', async () => { // no timeout set const result = await doSomethingCrazy(x,y,z) expect(result).to... }) Timeouts must not be used, when async handling is correctly defined!","s":"Promises and Timouts in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#promises-and-timouts-in-tests","p":360},{"i":377,"t":"When expecting an error, you might take values from an error, test for the error type thrown and must care of promises. // doSomethingCrazy : Promise it('bad async sample expecting an error', () => { expect(doSomethingCrazy(x,y,z)).to... }) it('good async sample expecting an error value', async () => { const code = await doSomethingCrazy(x,y,z).catch(err => err.code) expect(code).to... }) it('good sample expecting an error type from a sync function', () => { expect(() => doSomethingCrazySync(wrong, param)).toThrow(BadRequestException); }) it('good sample expecting an error type from an async function', async () => { await expect(doSomethingCrazySync(wrong, param)).rejects.toThrow(BadRequestException); })","s":"Expecting errors in tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#expecting-errors-in-tests","p":360},{"i":379,"t":"NestJS: provides default tooling (such as test runner that builds an isolated module/application loader) provides integration with Jest and Supertest out of the box makes the Nest dependency injection system available in the testing environment for mocking components The @nestjs/testing.Test class provides an execution context that mocks the full Nest runtime, but gives hooks that can help to manage class instances, including mocking and overriding. The method Test.createTestingModule() takes module metadata as argument it returns TestingModule instance. The TestingModule instance provides method compile() which bootstraps a module with its dependencies. Every provider can be overwritten with custom provider implementation for testing purposes. beforeAll(async () => { const moduleRef = await Test.createTestingModule({ controllers: [SampleController], providers: [SampleService], }).compile(); sampleService = moduleRef.get(SampleService); sampleController = moduleRef.get(CatsController); });","s":"Testing Utilities","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#testing-utilities","p":360},{"i":381,"t":"Using the utilities provided by NestJs, we can easily inject mocks into our testing module. The mocks themselves, we create using a library by @golevelup. You can create a mock using createMock(). As result you will recieved a DeepMocked let fut: FeatureUnderTest; let mockService: DeepMocked; beforeAll(async () => { const module = await Test.createTestingModule({ providers: [ FeatureUnderTest, { provide: MockService, useValue: createMock(), }, ], }).compile(); fut = module.get(FeatureUnderTest); mockService = module.get(MockService); }); afterAll(async () => { await module.close(); }); afterEach(() => { jest.resetAllMocks(); }) The resulting mock has all the functions of the original Class, replaced with jest spies. This gives you code completion and type safety, combined with all the features of spies. createTestingModule should only be calld in beforeAll and not in beforeEach to keep the setup and teardown for each test as simple as possible. Therefore module.close should only be called in afterAll and not in afterEach. To generally reset specific mock implementation after each test jest.resetAllMocks can be used in afterEach. jest.restoreAllMocks should not be used, because in some cases it will not properly restore mocks created by ts-jest. describe('somefunction', () => { describe('when service returns user', () => { const setup = () => { const resultUser = userFactory.buildWithId(); mockService.getUser.mockReturnValueOnce(resultUser); return { resultUser }; }; it('should call service', async () => { setup(); await fut.somefunction(); expect(mockService.getUser).toHaveBeenCalled(); }); it('should return user passed by service', async () => { const { resultUser } = setup(); const result = await fut.somefunction(); expect(result).toEqual(resultUser); }); }); }); For creating specific mock implementations the helper functions which only mock the implementation once, must be used (e.g. mockReturnValueOnce). With that approach more control over mocked functions can be achieved. If you want to mock a method that is not part of a dependency you can mock it with jest.spyOn. We strongly recommend the use of jest.spyOn and not jest.fn, because jest.spyOn can be restored a lot easier.","s":"Mocking","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mocking","p":360},{"i":383,"t":"In Unit Tests we access directly only the component which is currently testing. Any dependencies should be mocked or are replaced with default testing implementation. Especially the database access and database calls should be mocked. In contrast to unit tests the integration tests use access to the database and execute real queries using repositories.","s":"Unit Tests vs Integration Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#unit-tests-vs-integration-tests","p":360},{"i":385,"t":"For the data access layer, integration tests can be used to check the repositories base functionality against a database. For Queries care DRY principle, they should be tested very carefully. Use a in-memory database for testing to allow parallel test execution and have isolated execution of tests. A test must define the before and after state of the data set clearly and cleanup the database after execution to the before state. Instead of using predefined data sets, all preconditions should be defined in code through fixtures. Our repository layer uses mikro-orm/EntityManager to execute the queries. By testing repositories we want to verify the correct behaviour of the repository functions. It includes verifying expected database state after executed repository function. Therefore, the *.repo.integration.spec.js should be used. The basic structure of the repo integration test: Preconditions (beforeAll)​ Create Nest JS testing module: 1.1 with MongoMemoryDatabaseModule defining entities which are used in tests. This will wrap MikroOrmModule.forRoot() with running a MongoDB in memory. 1.2 provide the repo which should be tested Get repo, orm and entityManager from testing module import { MongoMemoryDatabaseModule } from '@src/modules/database'; let repo: NewsRepo; let em: EntityManager; let testModule: TestingModule; beforeAll(async () => { testModule: TestingModule = await Test.createTestingModule({ (1) imports: [ MongoMemoryDatabaseModule.forRoot({ (1.1) entities: [News, CourseNews, ...], }), ], providers: [NewsRepo], (1.2) }).compile(); repo = testModule.get(NewsRepo); (2) orm = testModule.get(MikroORM); em = testModule.get(EntityManager); }) Post conditions (afterAll), Teardown​ After all tests are executed close the app and orm to release the resources by closing the test module. afterAll(async () => { await testModule.close(); }); When Jest reports open handles that not have been closed, ensure all Promises are awaited and all application parts started are correctly closed. Entity Factories​ To fill the in-memory-db we use factories. They are located in \\apps\\server\\src\\shared\\testing\\factory. If you create a new one, please add it to the index.ts in that folder. Accessing the in-memory-db​ While debugging the tests, the URL to the in-memory-db can be found in the EntityManager instance of your repo in em.config.options.clientUrl. Copy paste this URL to your DB Tool e.g. MongoDB Compass. You will find a database called 'test' with the data you created for your test.","s":"Repo Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#repo-tests","p":360},{"i":387,"t":"Mapping tests are Unit Tests which verify the correct mapping between entities and Dto objects. These tests should not have any external dependencies to other layers like database or use cases.","s":"Mapping Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#mapping-tests","p":360},{"i":389,"t":"Since a usecase only contains orchestration, its tests should be decoupled from the components it depends on. We thus use unittests to verify the orchestration where necessary All Dependencies should be mocked. Use Spies to verify necessary steps, such as authorisation checks. to be documented","s":"Use Case Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#use-case-tests","p":360},{"i":391,"t":"Controllers do not contain any logic, but exclusively information to map and validate between dataformats used on the network, and those used internally, as well as documentation of the api. Most of these things can not be covered by unit tests. Therefore we do not write specific unittests for them, and only cover them with api tests.","s":"Controller Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#controller-tests","p":360},{"i":393,"t":"The API tests are plumbing or integration tests. Their job is to make sure all components that interact to fulfill a specific api endpoint are wired up correctly, and fulfil the expectation set up in the documentation. API tests should be located in the folder controller/api-test of each module. They should call the endpoint like a external entity would, treating it like a blackbox. It should try all parameters available on the API, users with different roles, as well as relevant error cases. During the API test, all components that are part of the server, or controlled by the server, should be available. This includes an in-memory database. Any external services or servers that are outside our control should be mocked away via their respective adapters.","s":"API Tests","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#api-tests","p":360},{"i":395,"t":"This guide is inspired by javascript-testing-best-practices by goldbergyoni","s":"References","u":"/docs/schulcloud-server/Coding-Guidelines/testing","h":"#references","p":360},{"i":397,"t":"ErWIn-IDM, namely Keycloak, will be the future Identity Management System (IDM) for the dBildungscloud. Keycloak provides OpenID Connect, SAML 2.0 and other identity related functionalities like SSO out of the box. It can also act as identity broker or aggregate identities from third party services which can be an active directory or LDAP.","s":"ErWIn-IDM (Keycloak)","u":"/docs/schulcloud-server/Development/keycloak","h":"","p":396},{"i":399,"t":"To run Keycloak locally for development purpose use the following Bash or PowerShell command. You can log into Keycloak here http://localhost:8080. If you don't want to block your terminal, you can add the -d option to start the container in the background. Execute these commands in the repository root or the data seeding will fail, and you can not log into Keycloak with any user. Bash: docker run \\ --name erwinidm \\ -p 8080:8080 \\ -p 8443:8443 \\ -v \"$PWD/backup/idm/keycloak:/tmp/realms\" \\ ghcr.io/hpi-schul-cloud/erwin-idm/dev:latest \\ \"&& /opt/keycloak/bin/kc.sh import --dir /tmp/realms\" PowerShell: docker run ` --name erwinidm ` -p 8080:8080 ` -p 8443:8443 ` -v \"$PWD/backup/idm/keycloak:/tmp/realms\" ` ghcr.io/hpi-schul-cloud/erwin-idm/dev:latest ` \"&& /opt/keycloak/bin/kc.sh import --dir /tmp/realms\"","s":"Docker","u":"/docs/schulcloud-server/Development/keycloak","h":"#docker","p":396},{"i":401,"t":"To add ErWIn-IDM identity broker feature via OpenID Connect (OIDC) Identity Provider (IdP) mock follow the steps below. Execute these commands in the repository root. Set env vars (or in your .env file) 'OIDCMOCK__BASE_URL' to http://\\. To make it work with the nuxt client set the env var HOST=http://localhost:4000 re-trigger npm run setup:db and npm run setup:idm to reset and apply seed data. start the 'oidc-server-mock' as follows: docker run \\ --name oidc-server-mock \\ -p 4011:80 \\ -e ASPNETCORE_ENVIRONMENT='Development' \\ -e SERVER_OPTIONS_PATH='/tmp/config/server-config.json' \\ -e USERS_CONFIGURATION_PATH='/tmp/config/users-config.json' \\ -e CLIENTS_CONFIGURATION_PATH='/tmp/config/clients-config.json' \\ -v \"$PWD/backup/idm/oidcmock:/tmp/config\" \\ ghcr.io/soluto/oidc-server-mock:0.6.0 PowerShell: docker run ` --name oidc-server-mock ` -p 4011:80 ` -e ASPNETCORE_ENVIRONMENT='Development' ` -e SERVER_OPTIONS_PATH='/tmp/config/server-config.json' ` -e USERS_CONFIGURATION_PATH='/tmp/config/users-config.json' ` -e CLIENTS_CONFIGURATION_PATH='/tmp/config/clients-config.json' ` -v \"$PWD/backup/idm/oidcmock:/tmp/config\" ` ghcr.io/soluto/oidc-server-mock:0.6.0","s":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering","p":396},{"i":403,"t":"The broker feature can be setup in conjunction with LDAP provisioning for local testing purpose. Therefore, run the sc-openldap-single container: docker run \\ --name sc-openldap-single \\ -p 389:389 \\ ghcr.io/hpi-schul-cloud/sc-openldap-single:latest docker run ` --name sc-openldap-single ` -p 389:389 ` ghcr.io/hpi-schul-cloud/sc-openldap-single:latest The LDAP provisioning is trigger as follows: curl -X POST \\ 'http://localhost:3030/api/v1/sync?target=ldap' \\ --header 'Accept: */*' \\ --header 'X-API-KEY: example' Invoke-RestMethod ` -Uri 'http://localhost:3030/api/v1/sync?target=ldap' ` -Method Post ` -Headers @{ \"Accept\" = \"*/*\"; \"X-API-KEY\" = \"example\" } See '/tmp/config/users-config.json' for the users details.","s":"Setup OpenID Connect Identity Provider mock for ErWIn-IDM brokering with LDAP provisioning","u":"/docs/schulcloud-server/Development/keycloak","h":"#setup-openid-connect-identity-provider-mock-for-erwin-idm-brokering-with-ldap-provisioning","p":396},{"i":405,"t":"You may test your local setup executing 'keycloak-identity-management.integration.spec.ts': npx jest apps/server/src/shared/infra/identity-management/keycloak/service/keycloak-identity-management.service.integration.spec.ts","s":"Test local environment","u":"/docs/schulcloud-server/Development/keycloak","h":"#test-local-environment","p":396},{"i":407,"t":"During container startup Keycloak will always create the Master realm with the admin user. After startup, we use the Keycloak-CLI to import the dBildungscloud realm, which contains some seed users, groups and permissions for development and testing. In the table below you can see the username and password combinations for the Keycloak login. Username Password Description keycloak keycloak The overall Keycloak administrator with all permissions. dbildungscloud dBildungscloud The dBildungscloud realm specific administrator.","s":"Seeding Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#seeding-data","p":396},{"i":409,"t":"Run Keycloak and make the desired changes Use docker container exec -it keycloak bash to start a bash in the container Use the Keycloak-CLI to export all Keycloak data with /opt/keycloak/bin/kc.sh export --dir /tmp/realms Save your changes with a commit If you start your container with a command from the docker section, your changes will be directly applied to the starting Keycloak container IMPORTANT: During the export process there will be some errors, that's because the export process will be done on the same port as the Keycloak server. This leads to Keycloak failing to start the server in import/export mode. Due to the transition from WildFly to Quarkus as application server there is currently no documentation on this topic. In order to re-apply the seeding data for a running keycloak container, you may run following commands (to be executed in the repository root): docker cp ./backup/idm/keycloak keycloak:/tmp/realms docker exec erwinidm /opt/keycloak/bin/kc.sh import --dir /tmp/realms","s":"Updating Seed Data","u":"/docs/schulcloud-server/Development/keycloak","h":"#updating-seed-data","p":396},{"i":411,"t":"A list of available NPM commands regarding Keycloak / IDM. Command Description setup:idm:seed Seeds users for development and testing purpose into the IDM setup:idm:configure Configures identity and authentication providers and other details in the IDM","s":"NPM Commands","u":"/docs/schulcloud-server/Development/keycloak","h":"#npm-commands","p":396},{"i":414,"t":"It makes sense for Rocket.Chat to launch its own mongodb in Docker. Reason for this is Rocket.Chat requires Mongodb as replicaSet setup. docker run --name rocket-chat-mongodb -m=256m -p27030:27017 -d docker.io/mongo --replSet rs0 --oplogSize 10 Start mongoDB console and execute rs.initiate({\"_id\" : \"rs0\", \"members\" : [{\"_id\" : 0, \"host\" : \"localhost:27017\"}]})","s":"Start Mongodb","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-mongodb","p":412},{"i":416,"t":"(check the latest settings https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/roles/rocketchat/templates/configmap.yml.j2#L9) Please not that the displayed //172.29.173.128 is the IP address of the mongoDB docker container. You can get the ip over the command: docker inspect rocket-chat-mongodb | grep \"IPAddress\" (dependent on our system) docker run\\ -e CREATE_TOKENS_FOR_USERS=true \\ -e MONGO_URL=mongodb://172.29.173.128:27030/rocketchat \\ -e ADMIN_PASS=huhu \\ -e API_Enable_Rate_Limiter_Limit_Calls_Default=255 \\ -e Accounts_iframe_enabled=true \\ -e Accounts_iframe_url=http://localhost:4000/rocketChat/Iframe \\ -e Accounts_Iframe_api_url=http://localhost:4000/rocketChat/authGet \\ -e Accounts_AllowRealNameChange=false \\ -e Accounts_AllowUsernameChange=false \\ -e Accounts_AllowEmailChange=false \\ -e Accounts_AllowAnonymousRead=false \\ -e Accounts_Send_Email_When_Activating=false \\ -e Accounts_Send_Email_When_Deactivating=false \\ -e Accounts_UseDefaultBlockedDomainsList=false \\ -e Analytics_features_messages=false \\ -e Analytics_features_rooms=false \\ -e Analytics_features_users=false \\ -e Statistics_reporting=false \\ -e API_Enable_CORS=true \\ -e Discussion_enabled=false \\ -e FileUpload_Enabled=false \\ -e UI_Use_Real_Name=true \\ -e Threads_enabled=false \\ -e Accounts_SetDefaultAvatar=false \\ -e Iframe_Restrict_Access=false \\ -e Accounts_Iframe_api_method=GET \\ -e OVERWRITE_SETTING_Show_Setup_Wizard='completed' \\ -p 3000:3000 docker.io/rocketchat/rocket.chat:4.7.2","s":"Start rocketChat","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#start-rocketchat","p":412},{"i":418,"t":"You must also configure you server and legacy client application. Use the .env file in top of the project folders.","s":"ENVS","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#envs","p":412},{"i":420,"t":"ROCKETCHAT_SERVICE_ENABLED=true ROCKET_CHAT_URI=\"http://localhost:3000\" ROCKET_CHAT_ADMIN_USER=admin ROCKET_CHAT_ADMIN_PASSWORD=huhu","s":"dBildungscloud Backend Server","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-backend-server","p":412},{"i":422,"t":"ROCKETCHAT_SERVICE_ENABLED=true ROCKET_CHAT_URI=\"http://localhost:3000\"","s":"dBildungscloud Legacy Client","u":"/docs/schulcloud-server/Development/rocket-chat","h":"#dbildungscloud-legacy-client","p":412},{"i":424,"t":"Make sure to have the following software installed node 18 MongoDb 4.2+ Redis RabbitMQ git clone git@github.com:hpi-schul-cloud/schulcloud-server.git Install the packages cd schulcloud-server npm ci Start MongoDb, Redis & RabbitMQ. Then seed the database npm run setup The last step is to start the dev server with. npm run nest:start:dev","s":"Getting started","u":"/docs/schulcloud-server/Getting started","h":"","p":423},{"i":427,"t":"Migration mean to change the database structure by updating a model and/or updating user data to the target model. The documentation about migration concept and tool on npm can be found at MikroORM documentation. Please be aware of current mikro-orm version. Here, we explain only how we create migrations currently. We use the cli commands provided by MikroORM.","s":"Migrations for server database","u":"/docs/schulcloud-server/Migrations","h":"#migrations-for-server-database","p":425},{"i":429,"t":"npx mikro-orm migration:create will create a new migration file in ./apps/server/src/migrations/mikro-orm folder. please note that argument --name=SOME_MIGRATION_NAME is supported in 5.9 only do log all database changes (before and after state of documents or at least all modified document id's)","s":"Create a new migration","u":"/docs/schulcloud-server/Migrations","h":"#create-a-new-migration","p":425},{"i":431,"t":"To run migraitons you can use one of the commands below: npx mikro-orm migration:up will apply all migrations that are not applied yet npm run migration:up will run the compiled js file. This command is safer, and used in production. you can also apply any specific migration. Look at the documentation for more details.","s":"Apply migrations","u":"/docs/schulcloud-server/Migrations","h":"#apply-migrations","p":425},{"i":433,"t":"npx mikro-orm migration:down will undo the last migration that has been applied.","s":"Undo migration","u":"/docs/schulcloud-server/Migrations","h":"#undo-migration","p":425},{"i":435,"t":"migrations are stored in database migrations collection there is no status for a migration. Once the migration has been applied, a record is added in the database. the command will compare migrations files to database records to know which migrations to apply migration configuration is in ./apps/server/src/mikro-orm.config.ts file. MikroORM uses the config file to connect to the database and to find the migrations folder.","s":"Notes about setup","u":"/docs/schulcloud-server/Migrations","h":"#notes-about-setup","p":425},{"i":437,"t":"to check migrations to be executed: in local development you can use npx mikro-orm migration:pending command npm run migration:pending to run the compiled js file. The second command is safer, and used in CI and should be used in K8 clusters. The CI job ./.github/workflows/migrations.yml will check that the migrations are already included in the seed data. If not, it will fail. This is to ensure that the seed data is always up to date with the migrations.","s":"Test migration","u":"/docs/schulcloud-server/Migrations","h":"#test-migration","p":425},{"i":439,"t":"npm run setup:db:seed - to update the whole database or use npm run nest:start:console -- database --help to see how to import/export single collection npx mikro-orm migration:up - to apply the migration update the migrations collection npm run backup (copies the collections to folder backup/DATE, move the changed files of this folder to backup/setup and commit the updated files) or use npm run nest:start:console -- database export --collection --override to override seed data directly. The updated collections, modified by your migration added in backup/setup folder Commit the changes: git add . git commit -m \"migration: \" git push test that the migration was applied npx mikro-orm migrations:pending (or better use npm run migration:pending) should return nothing","s":"Committing a migration","u":"/docs/schulcloud-server/Migrations","h":"#committing-a-migration","p":425},{"i":442,"t":"consider if a migration is the right tool for this job recurring tasks are better placed in a script/console test the migration well if a migration should be deleted, do not delete the migration file itself, but remove the content of the up and down method and log a skip message consider if the migration can be written to be idempotent (can be executed multiple times without problems)","s":"General","u":"/docs/schulcloud-server/Migrations","h":"#general","p":425},{"i":444,"t":"think about the size of the collection. On production, it can be that the collection has a lot of data, and your migration might take a long time or can lead to time-out errors. use methods already provided by mongo for bulk operations (e.g. updateMany, deleteMany etc.) but think about handle items separately with extra logging and separate error handling load the data chunkwise and process them asynchronously query the data with skip and limit (example migration) or use async iterators with mongoose queries (blog post, example migration) migrations should in general not be executed in parallel (on multiple pods) Use for loops with synchronous execution and logging. Avoid subtasks awaiting Promise.all() Beside loading data in chunks or cursors, performance must not be an issue.","s":"Performance","u":"/docs/schulcloud-server/Migrations","h":"#performance","p":425},{"i":446,"t":"if a migration throws an error, the subsequent migrations are not executed as well catch errors if the errors are acceptable for the next release, so it will not become a release blocker log start and end of a migration (so that we know which migration currently is running) log intermediate results for long-running migrations (so that we know if it is still running) use log level alert or above, so the output can be seen on production logging might be difficult to fix, instead it might be helpful to keep before-states in a separate collection","s":"Error Handling/Logging","u":"/docs/schulcloud-server/Migrations","h":"#error-handlinglogging","p":425},{"i":448,"t":"use env MIKRO_ORM_CLI_VERBOSE=1 can add mikro-orm debug info in config file debug: true will log all queries logger: console.log will log all queries can add debug breakpoints in migration files run with typical debug options for your IDE (e.g. in Webstorm create a Run/Debug npm command and run it with debug options)","s":"Debugging","u":"/docs/schulcloud-server/Migrations","h":"#debugging","p":425},{"i":450,"t":"using entity manager​ According to documentation, the entity manager should not be used in migrations. Instead, the migration should use the mongoClient to access the database. Outdated Migrations​ By their nature, migrations become outdated as the database model changes. You are never expected to update migrations due to any changes in the code that are made. If needed, for example because the migration shows errors due to a code (model) change, migrations can be deleted, since they will still be accessible in the git history.","s":"Caveats","u":"/docs/schulcloud-server/Migrations","h":"#caveats","p":425},{"i":452,"t":"The Room Member module manages the association between users and rooms, handling permissions and roles within rooms. This module is designed to be injected into the Room module for managing user access and roles within rooms.","s":"Room Member Module","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"","p":451},{"i":454,"t":"Room have one RoomMembers RoomMember have one UserGroup UserGroup have many Users Each User in UserGroup have Role This make room membership easy manage. Can give different roles to users in same room.","s":"Model Relationships","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#model-relationships","p":451},{"i":456,"t":"The core entity of this module is the RoomMemberEntity, which represents a user's membership in a room. @Entity({ tableName: 'room-members' }) export class RoomMemberEntity extends BaseEntityWithTimestamps implements RoomMemberProps { @Property() @Index() roomId!: ObjectId; @OneToOne(() => GroupEntity, { owner: true, orphanRemoval: true }) userGroup!: GroupEntity; @Property({ persist: false }) domainObject: RoomMember | undefined; } The important part is the userGroup property. We store the the members using the Group module. Why? => Because that allows us to assign each user a separate role within the room. e.g. ROOM_EDITOR, ROOM_VIEWER, ... These roles are not related to the user's global role.","s":"RoomMemberEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#roommemberentity","p":451},{"i":458,"t":"The userGroup property uses the GroupEntity from the Group module. This structure allows for multiple users to be associated with a room through a single group. class GroupEntity { id: EntityId; name: string; users: GroupUserEmbeddable[]; // other properties... }","s":"GroupEntity","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupentity","p":451},{"i":460,"t":"Each user in the group is represented by a GroupUserEmbeddable: class GroupUserEmbeddable { user: User; role: Role; } This structure allows for flexible assignment of roles to users within the context of a room.","s":"GroupUserEmbeddable","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#groupuserembeddable","p":451},{"i":462,"t":"The RoomMemberEntity doesn't directly store user IDs or roles. Instead, it uses a GroupEntity to manage this information. This design allows for easy management of multiple users and roles for a single room. The roomId is stored directly on the RoomMemberEntity for efficient querying of members for a specific room. The domainObject property facilitates the separation between the database entity and the domain object. This is an optimization to not loose the unit of work using mikro-orm. This structure provides a flexible and scalable way to manage room memberships, allowing for complex permission and role scenarios within rooms.","s":"Key Points","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#key-points","p":451},{"i":464,"t":"The RoomMemberService is a service for the RoomMember entity. It provides methods for creating, updating, and deleting RoomMember entities. class RoomMemberService { constructor(private readonly roomMembersRepo: RoomMemberRepo) {} }","s":"Service","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#service","p":451},{"i":466,"t":"The RoomMemberService is designed to be injected into the Room module for managing user access and roles within rooms.","s":"Usage","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#usage","p":451},{"i":468,"t":"There is no API for now. Member specific writes/reads can be implemented by adding an API to the RoomMember module. Like adding/removing users to a room.","s":"API","u":"/docs/schulcloud-server/our nextjs modules/room-member-module","h":"#api","p":451},{"i":471,"t":"FEATURE_ETHERPAD_ENABLED - Used to enable etherpad feature in feathers backend FEATURE_COLUMN_BOARD_COLLABORATIVE_TEXT_EDITOR_ENABLED - Enables etherpad feature on column boards ETHERPAD__COOKIE_EXPIRES_SECONDS - Time in seconds after which a session expires ETHERPAD__COOKIE_RELEASE_THRESHOLD - Time in seconds after which a session is not returned to the user ETHERPAD__API_KEY - Api key used for authentication of schulcloud server requests ETHERPAD__URI - Uri of etherpad api for all calls like create, delete etc. Used as base path for api client in nest and feathers backend. ETHERPAD__PAD_URI - URI to etherpad client api. Used in backend in collaborative text editor and lesson to build return url. Used in legacy client to build url. ETHERPAD_OLD_PAD_URI - Used in feathers backend to restrict access to old etherpad urls to lesson context. Only defined in default.schema.json and not in dof-app-deploy ETHERPAD__PAD_PATH - Path to etherpad client api. Used in legacy client to set path on cookie. ETHERPAD__NEW_DOMAIN - Etherpad Domain. Used in legacy client to validate url ETHERPAD__OLD_DOMAIN - Old Etherpad Domain. Used in legacy client to identify old urls in lessons and transform them to urls with new domain.","s":"Configuration","u":"/docs/services/etherpad/How it works","h":"#configuration","p":469},{"i":474,"t":"The user initiates the process by creating an Etherpad element on a column board. Vue then sends a request to the board module in the Schulcloud server for a new Etherpad element. The server responds by returning an Etherpad element which is then displayed on the board.","s":"Creating an Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#creating-an-etherpad-element","p":469},{"i":476,"t":"When the user clicks on the Etherpad element, the Vue client sends a request to the collaborative text editor module in the Schulcloud server for a new Etherpad. The Schulcloud server is capable of creating Etherpads based on parent types. The parent type is further used for handling authorisation of the etherpad. Currently, the only parent type is a column board element.","s":"Interacting with the Etherpad Element","u":"/docs/services/etherpad/How it works","h":"#interacting-with-the-etherpad-element","p":469},{"i":478,"t":"The Schulcloud server first creates a group that clusters several Etherpads together. Each group shares a session and a new group is created for every column board element. This requires sending a request to the Etherpad server. Once the group is created, the server can send a request to create an Etherpad within that group.","s":"Grouping and Creating Etherpads","u":"/docs/services/etherpad/How it works","h":"#grouping-and-creating-etherpads","p":469},{"i":480,"t":"For session creation, the server first needs to request an Etherpad author and then the session for that author. With that session, the server can set a session cookie in the client's browser and return the Etherpad URL to Vue.","s":"Session Creation and Cookie Setting","u":"/docs/services/etherpad/How it works","h":"#session-creation-and-cookie-setting","p":469},{"i":482,"t":"In the final step, Vue opens the Etherpad URL in a new browser tab, enabling the user to interact directly with the Etherpad. It's important to note that the Etherpad client interface displayed to the user is served by the Etherpad server, and as such, it is not a part of our own codebase.","s":"Opening the Etherpad","u":"/docs/services/etherpad/How it works","h":"#opening-the-etherpad","p":469},{"i":484,"t":"For the communication with the Etherpad server, the Schulcloud server uses an adapter. This adapter is responsible for handling all requests to the Etherpad server and ensuring that the correct data is sent and received. This adapter uses a client that is generated by open api generator. Client generation can be triggered with generate-client:etherpad and should be executed after update of etherpad server.","s":"Etherpad Adapter","u":"/docs/services/etherpad/How it works","h":"#etherpad-adapter","p":469},{"i":486,"t":"The authentication process in our system works via a token. This token is sent with each request as a parameter to ensure secure communication. The token is defined by the ETHERPAD_API_KEY environment variable. This variable holds the key used for authentication, ensuring that only authorized requests are processed. In the Schulcloud server, this API key is passed to the etherpad adapter on its initialization in the collaborative text editor module.","s":"Authentication","u":"/docs/services/etherpad/How it works","h":"#authentication","p":469},{"i":489,"t":"Each interaction with an Etherpad element initiates the creation of a new session. The lifespan of this session is determined by the ETHERPAD_COOKIE__EXPIRES_SECONDS environment variable. Once this time period elapses, the user loses access to the pad. This loss of access is indicated by the display of a non-translated English message: \"You do not have permission to access this pad.\" However, even after the session expires, users can still view the pad content as long as they do not interact with it.","s":"Etherpad Session Creation and Expiration","u":"/docs/services/etherpad/How it works","h":"#etherpad-session-creation-and-expiration","p":469},{"i":491,"t":"Upon subsequent interactions with the Etherpad element, an existing session for that element may be reused. Whether an old session is reused or a new one is created depends on the environment variable settings. The ETHERPAD_COOKIE_RELEASE_THRESHOLD variable determines the remaining validity period of a session for it to be delivered to the user. In the current production environment, both ETHERPAD_COOKIE_RELEASE_THRESHOLD and ETHERPAD_COOKIE__EXPIRES_SECONDS are set to the same value of 2 hours. This configuration results in the creation of a new session for each interaction with an Etherpad element.","s":"Session Reuse","u":"/docs/services/etherpad/How it works","h":"#session-reuse","p":469},{"i":493,"t":"Currently there is no automatic session renewal upon interaction. Etherpad provides the COOKIE_SESSION_REFRESH_INTERVAL configuration variable, which specifies the time period after which a user's session is automatically renewed in an open tab. However, this variable is currently unset, and the default value of 1 day has no effect because it exceeds the ETHERPAD_COOKIE__EXPIRES_SECONDS value. Therefore, sessions are not automatically renewed.","s":"Session Renewal","u":"/docs/services/etherpad/How it works","h":"#session-renewal","p":469},{"i":495,"t":"When a user logs out of Schulcloud, all sessions are terminated, and the user loses access to the pads upon interaction. However, Etherpad sessions are not automatically removed upon user auto-logout in Schulcloud. As long as ETHERPAD_COOKIE__EXPIRES_SECONDS and JWT_TIMEOUT_SECONDS are set to the same value, all sessions should theoretically become invalid after auto-logout.","s":"Session Removal","u":"/docs/services/etherpad/How it works","h":"#session-removal","p":469},{"i":497,"t":"A cookie named sessionID is stored for each session. These cookies are not programmatically removed after the ETHERPAD_COOKIE__EXPIRES_SECONDS period or upon Schulcloud logout.","s":"Cookie Management","u":"/docs/services/etherpad/How it works","h":"#cookie-management","p":469},{"i":499,"t":"Same session behavior also applies to legacy code. Env vars are used by both implementations. In contrast to the nest code in legacy code a session is created for every course and stored as a cookie for every etherpad.","s":"Legacy Topics","u":"/docs/services/etherpad/How it works","h":"#legacy-topics","p":469},{"i":502,"t":"To run the Etherpad service for the local development and resting purposes you can follow the below steps: Create a new dir that will contain all the needed files that we'll want to use when running the Etherpad service. Create a directory called sc-etherpad and then enter it, in Unix-like systems you can use the following command: `mkdir sc-etherpad && cd sc-etherpad` Create a new file called APIKEY.txt in it, with the following content: 381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd We'll use this file to set a fixed Etherpad's API key on the Etherpad server's start. Create also a file called settings.env with the following content: REQUIRE_SESSION=\"true\" PAD_OPTIONS_SHOW_CHAT=\"true\" DISABLE_IP_LOGGING=\"true\" DEFAULT_PAD_TEXT=\"Schreib etwas!\\n\\nDieses Etherpad wird synchronisiert, während du tippst, so dass alle Betrachter jederzeit den selben Text sehen. So könnt ihr auf einfache Weise gemeinsam an Dokumenten arbeiten.\" DB_TYPE=mongodb DB_URL=mongodb://host.docker.internal:27017/etherpad We'll use this file to provide all the needed environment variables to the Etherpad's server. Please note the last line, that contains the MongoDB connection string: DB_URL=mongodb://host.docker.internal:27017/etherpad Here we're using the host.docker.internal hostname which should make it possible for the Etherpad's container to connect to the host's local network and should work out of the box e.g. on macOS. But please modify it accordingly to your needs and your Docker's network configuration. An alternative configuaration would be to use DB_URL=mongodb://localhost:27017/etherpad and than add --network=\"host\" to the following docker run command. Next, start the Etherpad's container: docker run -d \\ -p 9001:9001 \\ --env-file ./settings.env \\ -v ./APIKEY.txt:/opt/etherpad-lite/APIKEY.txt \\ --name sc-etherpad \\ docker.io/etherpad/etherpad:2.0.0 Please note we're using the docker.io/etherpad/etherpad:2.0.0 image in the command above which might be not the one that is being used anytime in the future when you read this article. To make sure you're using the current version (the one that is currently being used in the SchulCloud platform), please refer to the https://github.com/hpi-schul-cloud/dof_app_deploy/blob/main/ansible/group_vars/infra/dof_etherpad.yml file. Now we should have the Etherpad service running locally on port 9001, we can verify it's working properly e.g. by fetching the current Etherpad's API version: ~ curl -v http://127.0.0.1:9001/api * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 200 OK < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 26 < ETag: W/\"1a-2HmNLqF3wPt+KQRlEmGwH4O+xu4\" < Date: Fri, 29 Mar 2024 13:27:00 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"currentVersion\":\"1.3.0\"} We can also verify that the API key has been set successfully, let's use the example API call from the Etherpad's documentation ( https://etherpad.org/doc/v2.0.0/#_example_1 ): ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\\?apikey\\=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd\\&name\\=Michael\\&authorMapper\\=7 * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api/1/createAuthorIfNotExistsFor?apikey=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd&name=Michael&authorMapper=7 HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 200 OK < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 66 < ETag: W/\"42-bg92QA1xRFO6QmkNRbNXhfsFBUM\" < Date: Fri, 29 Mar 2024 13:40:05 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"code\":0,\"message\":\"ok\",\"data\":{\"authorID\":\"a.7BgkAuzbHXR1G8Cv\"}} In case of an unsuccessful result (e.g. improperly set or invalid API key) we would receive the following response: ~ curl -v http://127.0.0.1:9001/api/1/createAuthorIfNotExistsFor\\?apikey\\=secret\\&name\\=Michael\\&authorMapper\\=7 * Trying 127.0.0.1:9001... * Connected to 127.0.0.1 (127.0.0.1) port 9001 > GET /api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7 HTTP/1.1 > Host: 127.0.0.1:9001 > User-Agent: curl/8.4.0 > Accept: */* > < HTTP/1.1 401 Unauthorized < X-Powered-By: Express < X-UA-Compatible: IE=Edge,chrome=1 < Referrer-Policy: same-origin < Content-Type: application/json; charset=utf-8 < Content-Length: 54 < ETag: W/\"36-dbJd0O+vdNi3zPpwRXE+1EGLTho\" < Date: Fri, 29 Mar 2024 13:39:44 GMT < Connection: keep-alive < Keep-Alive: timeout=5 < * Connection #0 to host 127.0.0.1 left intact {\"code\":4,\"message\":\"no or wrong API Key\",\"data\":null}","s":"Running the Etherpad server","u":"/docs/services/etherpad/Local setup","h":"#running-the-etherpad-server","p":500},{"i":505,"t":"This strategy handles the sync of schools, students, teachers and classes from TSP and replaces both the legacy TspBaseSyncer and TspSchoolSyncer. It responds to the target \"tsp\". The flow looks like this (some of the logic for syncing is done in the provisioning strategy which is shared with the login):","s":"TspSyncStrategy","u":"/docs/syncronizations/TSP/Architecture","h":"#tspsyncstrategy","p":503},{"i":508,"t":"FEATURE_TSP_SYNC_ENABLED - Activates the sync strategy inside the sync console WITH_TSP_SYNC - Activates the cronjob in Kubernetes TSP_API_CLIENT_BASE_URL - Base URL for the TSP API TSP_API_TOKEN_LIFETIME_MS - Lifetime of the access token for the TSP API in milliseconds TSP_SYNC_SCHOOL_LIMIT - The amount of schools the sync handles at once TSP_SYNC_SCHOOL_DAYS_TO_FETCH - The amount of days for which the sync fetches schools from the TSP API TSP_SYNC_DATA_LIMIT - The amount of school data updates the sync handles at once TSP_SYNC_DATA_DAYS_TO_FETCH - The amount of days for which the sync fetches school data from the TSP API FEATURE_TSP_MIGRATION_ENABLED - Activates the migration of TSP users within the sync TSP_SYNC_MIGRATION_LIMIT - The amount of users the sync migrates at once","s":"Configuration","u":"/docs/syncronizations/TSP/How it works","h":"#configuration","p":506},{"i":510,"t":"This is a console application that allows you to start the synchronization process for different sources.","s":"Sync console","u":"/docs/syncronizations/TSP/How it works","h":"#sync-console","p":506},{"i":512,"t":"To start the synchronization process, run the following command: npm run nest:start:console sync run Where is the name of the system you want to start the synchronization for. The currently available systems are: tsp - Synchronize Thüringer Schulportal. If the target is not provided, the synchronization will not start and the available targets will be displayed in an error message. { message: 'Either synchronization is not activated or the target entered is invalid', data: { enteredTarget: 'tsp', availableTargets: { TSP: 'tsp' }} }","s":"Usage","u":"/docs/syncronizations/TSP/How it works","h":"#usage","p":506},{"i":514,"t":"The TSP synchronization is controlled with the feature flag FEATURE_TSP_SYNC_ENABLED.","s":"TSP synchronization","u":"/docs/syncronizations/TSP/How it works","h":"#tsp-synchronization","p":506},{"i":516,"t":"The terms Redis and Valkey are used here synonymously and should describe the used in-memory-database.","s":"How it works","u":"/docs/tldraw-server/How it works","h":"","p":515},{"i":518,"t":"AUTHORIZATION_API_HOST - host address of the authorization endpoint (schuldcloud-server) FEATURE_TLDRAW_ENABLED - flag determining if tldraw is enabled LOGGER_LOG_LEVEL - logging level LOGGER_EXIT_ON_ERROR - flag whether an error will cause the application to stop METRICS_COLLECT_DEFAULT - flag whether the default metrics shall be collected REDIS_CLUSTER_ENABLED - flag whether a redis cluster or used or not REDIS_URL - redis connection string REDIS_SENTINEL_SERVICE_NAME - name of the redis sentinel service REDIS_PREFIX - prefix to be used with redis database REDIS_SENTINEL_NAME - name of the redis sentinel REDIS_SENTINEL_PASSWORD - password for the redis sentinel S3_ACCESS_KEY - access key for S3 storage S3_BUCKET - name of the S3 bucket S3_ENDPOINT - URL of the S3 service S3_PORT - port number for the S3 service S3_SECRET_KEY - secret key for S3 storage S3_SSL - flag to enable or disable SSL for S3 storage TLDRAW_ASSETS_ENABLED - enables uploading assets to tldraw board TLDRAW_ASSETS_MAX_SIZE_BYTES - maximum asset size in bytes TLDRAW_ASSETS_ALLOWED_MIME_TYPES_LIST - list of allowed assets MIME types TLDRAW_WEBSOCKET_PATH - path for the tldraw websocket connection TLDRAW_WEBSOCKET_URL - URL for the tldraw websocket connection WORKER_MIN_MESSAGE_LIFETIME - minimal lifetime of a update message consumed by the worker WORKER_TASK_DEBOUNCE - minimum idle time (in milliseconds) of the pending task messages to be claimed WORKER_TRY_CLAIM_COUNT - the maximum number of task messages to claim X_API_ALLOWED_KEYS - list of allowed xAPI keys In order to have deletion functionality fully working locally you have to fill those feature flags, e.g.: tldraw-server : X_API_ALLOWED_KEYS=\"7ccd4e11-c6f6-48b0-81eb-abcdef123456\" schulcloud-server : TLDRAW_ADMIN_API_CLIENT_API_KEY=\"7ccd4e11-c6f6-48b0-81eb-abcdef123456\" TLDRAW_ADMIN_API_CLIENT_BASE_URL=\"http://localhost:3349\"","s":"Configuration","u":"/docs/tldraw-server/How it works","h":"#configuration","p":515},{"i":520,"t":"The Tldraw board can be created by the user on the courses ColumnBoard. It has a representation in ColumnBoard as DrawingElement inside a card (BoardNode in db). After creating representation as DrawingElement we can enter actual Tldraw SPA client on click. User enters ColumnBoard and creates Representation of whiteboard (tldraw) in Card. Data is saved and feedback with proper creation is given - user can see Representation and can enter whiteboard. By entering whiteboard user is redirected to SPA tldraw-client. Tldraw-client is starting WS connection with tldraw-server. Tldraw-server first checks if user has permission to this resource (by checking if user has a permission to Representation of whiteboard - BoardNode). Id of Representation is same as drawingName, which is visible in tldraw-client url. If user has permission tldraw-server is allowing to remain connected and getting drawing data from S3 storage. If there is no drawing data available, the tldraw-server will create a new document automatically.","s":"Create","u":"/docs/tldraw-server/How it works","h":"#create","p":515},{"i":523,"t":"User joins tldraw board. Tldraw-client connects to one of the tldraw-server pods and tries to establish websocket connection. Tldraw-server calls schulcloud-server via HTTP requests to check user permissions. If everything is fine, the websocket connection is established. Tldraw-server gets stored tldraw board data from S3 storage and sends it via websocket to connected user. Tldraw-server starts subscribing to Redis PUBSUB channel corresponding to tldraw board name to listen to changes from other pods.","s":"Connection","u":"/docs/tldraw-server/How it works","h":"#connection","p":515},{"i":525,"t":"Tldraw-client sends user's drawing changes to the tldraw-server via websocket connection. Tldraw-server stores the board update in the valkey db - basically creates a diff between what's already stored and what's being updated. Tldraw-server pushes the update to the boards Redis channel so that connected clients on different pods have synchronized board data. Other pods subscribing to Redis channel send updates to their connected clients via websocket whenever they see a new message on Redis channel. Finally the worker will run and persist the current state of the drawing data by applying all currently available updates from valkey on top of the stored drawing data and update the S3 storage accordingly.","s":"Sending updates/storing data","u":"/docs/tldraw-server/How it works","h":"#sending-updatesstoring-data","p":515},{"i":527,"t":"User from schulcloud app in ColumnBoard deletes whiteboard (tldraw) instance form Card. Schulcloud-server is removing representation data in schulcloud-database - BoardNodes collection. Schulcloud-server is calling tldraw-server to delete all data that has given id. Tldraw-server sends a delete action via websocket to inform connected clients about deletion. Clients redirect away from Tldraw-board to ensure that no new messages are added to valkey database. Finally the worker will run, clear all updates and data from the valkey db and delete the drawing data from the S3 storage.","s":"Delete","u":"/docs/tldraw-server/How it works","h":"#delete","p":515},{"i":530,"t":"Images/GIFs can be uploaded into tldraw whiteboard by every user with access to the board. We use SVS FileStorageService to physically store uploaded assets while tldraw only holds URL to a resource. The files are uploaded by calling schulcloud-api's fileController upload endpoint. This is possible because tldraw is represented as a boardnode called drawing-element. Mongo id of this drawing-element is a roomId used in URL param when connecting to a specific board.","s":"files upload","u":"/docs/tldraw-server/How it works","h":"#files-upload","p":515},{"i":532,"t":"The deletion of files is handled directly by the tldraw-client itself. On deletion in the UI, the client sends a delete request to the file storage. While awaiting the answer from file storage the editing of the Tldraw-board is blocked to prevent race conditions to the file storage.","s":"files deletion","u":"/docs/tldraw-server/How it works","h":"#files-deletion","p":515},{"i":535,"t":"Run redis i.e. in a docker container, it will work on localhost:6379 by default which is what the REDIS_URI env var is set to, for example on wsl: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-windows/ . In the tldraw-server make a copy of .env.default and rename it to .env, in order to use the default configuration. To run npm run nest:start:dev (schulcloud-server) npm run nest:start:files-storage:dev (schulcloud-server with s3, if you want to upload files) npm run start:server:dev (tldraw-server) npm run start:worker:dev (tldraw-server) npm run dev (schulcloud-client) npm run servce (nuxt-client) npm run dev (tldraw-client)","s":"To run tldraw locally:","u":"/docs/tldraw-server/Local setup","h":"#to-run-tldraw-locally","p":533},{"i":537,"t":"Go to a course. Go to 'Column board'. Create a new card and a new 'Whiteboard' element within it, then click it. A new browser tab with URL like: http://localhost:4000/tldraw?roomName=65c37329b2f97cc714d31c00 will open. Change the port part from 4000 to 3046, which is the default port of tldraw-client app. You should see a working tldraw whiteboard now.","s":"Create new whiteboard:","u":"/docs/tldraw-server/Local setup","h":"#create-new-whiteboard","p":533},{"i":540,"t":"In tldraw-server, Yjs, Redis and S3 storage are integrated to support collaborative drawing features, data synchronization, and persistent storage in a scalable and efficient manner. The combination of Yjs (used for collaborative editing), Redis (for real-time data synchronization), and S3 (for file storage) enables the platform to handle complex collaborative interactions and large-scale, persistent storage of drawing data.","s":"Introduction","u":"/docs/tldraw-server/Technical details","h":"#introduction","p":538},{"i":542,"t":"Real-time Collaboration with Yjs: Yjs is the backbone of real-time collaboration in tldraw. It provides a CRDT-based (Conflict-free Replicated Data Type) framework for handling shared documents where changes made by one user are automatically and consistently synchronized across all other users in the same session. In tldraw, the drawing canvas or document is represented as a Yjs document. Users can draw shapes, lines, text, or modify the canvas in real time. The Yjs document tracks all changes in the form of small, incremental edits. These edits could be changes to the position of objects, the creation of new objects (e.g., shapes or lines), or modification of existing elements. . Redis for Real-Time Data Synchronization: Redis is used to store and synchronize these Yjs documents across multiple users in real time. Redis, being a fast in-memory key-value store, provides low-latency updates that are crucial for real-time collaboration. It is used for: Broadcasting updates: When one user makes a change, Yjs sends that change to the Redis server, which then distributes the change to all other connected users. Data persistence: Changes are stored in Redis and can be fetched by other users at any time to maintain consistency. The use of Redis Pub/Sub allows different instances of the tldraw application to subscribe to channels. When one user makes a change, the Redis system publishes the change, and other users (who are subscribed to that document) get updated immediately. S3 Storage for Persistent and Large-Scale File Storage: While Redis ensures real-time synchronization and collaboration, S3 (Amazon Simple Storage Service) is used for persistent storage of larger files or data that need to be saved across sessions. S3 is highly scalable and can store large amounts of data. For tldraw, S3 is primarily used for: Storing canvases and drawings: While Redis handles real-time data synchronization and storage of changes, the worker service is responsible for periodically persisting the state of the collaborative canvas to S3 storage. S3 as a file store: Unlike Redis, which is an in-memory store designed for fast access and transient data, S3 is optimized for storing larger data in a persistent manner. This makes it suitable for storing media files, large canvas snapshots, and other assets that don't need to be constantly updated in real-time. Integration Between Redis and S3: Redis and S3 serve different but complementary purposes: Redis handles real-time synchronization of drawing changes and interactions, ensuring that users see each other's edits in near real-time. S3 handles long-term storage and backup of the drawings or canvases themselves. For example, when a user closes the app or saves their session, the drawing data is saved to S3. The worker service will save snapshots of the collaborative document or canvas to S3 periodically, ensuring that the state of the canvas is preserved even if a user disconnects or the server restarts. How tldraw's Workflow Would Look Using Redis and S3: Step 1: Collaborative Drawing Session Users interact with the tldraw canvas, making real-time changes (drawing shapes, text, etc.). The changes are immediately propagated through Yjs and Redis. Redis stores these changes in memory and broadcasts them to other users. Each user's drawing operations are synchronized, so everyone sees the same live canvas. Step 2: Saving the Canvas On a regular basis the worker service will send a snapshot of the canvas (or the entire Yjs document) to S3. This can include data about the shapes, their positions, colors, and any other relevant canvas data. The snapshot can be stored as a JSON object, an image, or another format, depending on how tldraw chooses to serialize the data. Step 3: Retrieving Saved Data When a user returns to the drawing session or opens the application at a later time, the application queries S3 for the last saved canvas snapshot. Once retrieved, the snapshot is loaded back into the application, allowing users to continue editing from where they left off. Redis can be used in the background to ensure that any new changes made by users are synchronized in real time while they are working on the canvas. Scalability and Fault Tolerance: Redis Scaling: As we are using Valkey, by default there will only be one primary Valkey instance and we will have two replica instances ready to jump in once the primary instance fails. But these two replica sets make sure, that Valkey is always available. S3 Scaling: S3 is designed to scale automatically and handle large amounts of storage without performance degradation. This makes it ideal for storing large or numerous drawing assets, like high-resolution images or full snapshots of large canvases. Tldraw-server Scaling: So far the number of pods the tldraw-server is deployed on is fixed and no load based scaling is applied so far.","s":"How tldraw Uses Redis and S3 Storage:","u":"/docs/tldraw-server/Technical details","h":"#how-tldraw-uses-redis-and-s3-storage","p":538},{"i":544,"t":"User A and User B start a collaborative session in tldraw, and they can see each other's updates in real time (thanks to Redis). After some time, the worker service saves the drawing to S3, and now the drawing is stored in S3 as a persistent snapshot. User B, who was not connected when the session ended, can later load the canvas from S3, where the most recent version is stored. Meanwhile, as new users join the session, Redis continues to handle the real-time synchronization of the drawing, ensuring smooth interaction.","s":"Example Scenario:","u":"/docs/tldraw-server/Technical details","h":"#example-scenario","p":538},{"i":546,"t":"In tldraw, Redis and S3 are integrated to deliver a collaborative and scalable experience. Redis ensures real-time synchronization of drawing changes among multiple users, while S3 provides persistent and scalable storage for canvas data. This combination allows tldraw to offer seamless collaboration, persistent storage, and fault-tolerant handling of large-scale data.","s":"Conclusion:","u":"/docs/tldraw-server/Technical details","h":"#conclusion","p":538},{"i":549,"t":"Tldraw is deployed as a separate application from schoulcloud-server and consists of the following deployments : tldraw-server-deployment - deployment for tldraw-server's instances. tldraw-worker-deployment - deployment for worker's instances. tldraw-client-deployment - deployment for tldraw-client's instances.","s":"Deployments","u":"/docs/tldraw-server/Technical details","h":"#deployments","p":538},{"i":551,"t":"tldraw-config.controller.ts - controller that exposes tldraw server configuration to be used by the tldraw client. tldraw-document.controller.ts - controller that expose HTTP deletion method outside the tldraw-server application. tldraw-document.service.ts - service used by TldrawDocumentController. redis.service.ts - encapsulates the logic for creating and managing Redis instances, supporting both standalone and sentinel configurations, and integrates seamlessly with the NestJS framework. ioredis.adapter.ts - encapsulates the logic for interacting with Redis, including defining custom commands and subscribing to channels. It leverages the ioredis library and integrates with the application's configuration and logging systems to provide a robust and flexible Redis adapter. api.service.ts - API service for Redis. ws.service.ts - Responsibe for Redis communication. metrics.service.ts - service resonsible for storing application-level metrics. worker.service.ts - responsible for persisting the current state of changed tldraw documents into the file storage. On the backend side we are also using Yjs library to store tldraw board in memory and to calculate diffs after the board is changed.","s":"Tldraw-server code structure","u":"/docs/tldraw-server/Technical details","h":"#tldraw-server-code-structure","p":538},{"i":554,"t":"stores/setup.ts – this file provides a real-time collaboration environment for a drawing application using the WebSocket and Yjs libraries. hooks/useMultiplayerState.ts – custom hook for managing multiplayer state. App.tsx – main application component integrating Tldraw and multiplayer state.","s":"Key Files","u":"/docs/tldraw-server/Technical details","h":"#key-files","p":538},{"i":556,"t":"The frontend of the project is built using React and leverages various libraries and tools for enhanced functionality. Here is an overview of the key frontend technologies: React: A JavaScript library for building user interfaces. Yjs: A real-time collaboration framework for synchronizing shared state. Tldraw: A library for drawing functionalities in the application. We use the old version of tldraw: https://github.com/tldraw/tldraw-v1, after the tldraw team releases the official update of the new version, we will work on the new version and integrate it with the needs of our users.","s":"Frontend Technologies","u":"/docs/tldraw-server/Technical details","h":"#frontend-technologies","p":538},{"i":558,"t":"Yjs is integrated into the project for real-time collaboration. The central state (shapes, bindings, assets) is managed using Yjs maps. store.ts handles the configuration of Yjs, WebSocket connections, and provides centralized maps for shapes, bindings, and assets useMultiplayerState.ts -This hook manages the multiplayer state, including loading rooms, handling file system operations, and updating Yjs maps: Mounting and handling changes in Tldraw App. Presence management and user updates. File system operations like opening and saving projects. useTldrawUiSanitizer.ts​ This hook is designed to observe changes in the DOM, specifically targeting certain buttons and a horizontal rule (< hr>), and hides them if they match a specific ID pattern. We hide this elements and left just only Language and Keyboard shortcuts. Event Handling​ onMount: Handles mounting of the Tldraw app. onChangePage: Manages page changes and updates Yjs maps. onUndo and onRedo: Handle undo and redo operations. onChangePresence: Manages presence changes in the collaborative environment. onAssetCreate: This function is triggered when a user attempts to upload an asset (like an image or a file). Useful links​ https://tldraw.dev/ - documentation for the new version of tldraw https://old.tldraw.com/ - tldraw live application https://github.com/tldraw/tldraw-v1 - tldraw v1 repo https://github.com/yjs/y-redis - code from this package was used to add Redis as a persistence to tldraw https://discord.com/invite/SBBEVCA4PG discord channel with open questions and answers https://grafana.dbildungscloud.dev/d/b6b28b2b-3129-4772-8102-e32981d2c2e3/devops-tldraw-metrics?orgId=1&refresh=1m&var-source=sc-dev-dbc&var-env=main&var-env=tldraw-debugging - grafana v https://grafana.dbildungscloud.org/d/b6b28b2b-3129-4772-8102-e32981d2c2e0/devops-tldraw?orgId=1&from=now-6h&to=now&refresh=1m - grafana metrics https://github.com/nimeshnayaju/yjs-tldraw - yjs with tldraw POC https://github.com/yjs/y-websocket/tree/master/bin - yjs/y-websocket repo https://github.com/erdtool/yjs-scalable-ws-backend/tree/main - Yjs scalable WS backend with redis example https://teamchat.dbildungscloud.de/channel/G9hJWv92zXEESKK3X - rocketchat discussion \"tldraw syncronisation for release again\" https://teamchat.dbildungscloud.de/group/SagK4sCyujhu6yZr8 - rocketchat discussion \"Tldraw deployment\"s","s":"State Managment","u":"/docs/tldraw-server/Technical details","h":"#state-managment","p":538},{"i":561,"t":"In the file ./vscode/launch.default.json you find following actions: Attach to NestJS will allow to attach VSCode debugger to an already running application Deubg NestJS via NPM will start the application and attach the debugger","s":"Launch scripts","u":"/docs/schulcloud-server/Development/vs-code","h":"#launch-scripts","p":559},{"i":563,"t":"In the file ./vscode/settings.default.json find suggested settings.","s":"Settings","u":"/docs/schulcloud-server/Development/vs-code","h":"#settings","p":559},{"i":565,"t":"See ./vscode/extensions.json for recommendations.","s":"Recommended extensions","u":"/docs/schulcloud-server/Development/vs-code","h":"#recommended-extensions","p":559},{"i":567,"t":"Jest is used to care of unit- and end2end tests on all *.spec.ts files. Allows to just see failing tests in Problems tab. and get small icons like ✔️ or a cross beside it-definitions inside of test files.","s":"Jest","u":"/docs/schulcloud-server/Development/vs-code","h":"#jest","p":559}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/2",[0,5.048,1,2.528,2,6.84,3,3.13,4,4.483,5,5.441,6,5.979,7,1.971,8,3.99,9,3.439,10,6.84,11,7.588,12,4.739]],["t/4",[6,4.598,9,2.645,13,3.541,14,4.881,15,2.69,16,2.407,17,5.321,18,4.023,19,5.835,20,7.797,21,4.598,22,5.835,23,4.685,24,6.082,25,5.25,26,5.835,27,6.958,28,4.147,29,4.598,30,6.082,31,2.51,32,3.361,33,4.881,34,5.835,35,3.644,36,3.644,37,4.881,38,4.372,39,3.541,40,4.881,41,5.26]],["t/6",[16,2.947,23,4.336,42,3.924,43,2.07,44,4.461,45,3.68,46,5.629,47,4.711,48,3.838,49,5.629,50,3.037,51,2.903,52,4.221,53,2.947,54,0.956,55,7.144,56,5.976]],["t/8",[3,2.458,4,3.521,7,1.841,8,4.117,9,1.829,43,2.268,48,3.202,50,2.534,54,0.54,56,4.986,57,3.274,58,2.893,59,5.96,60,6.168,61,3.637,62,4.696,63,3.375,64,2.449,65,4.986,66,3.511,67,3.351,68,3.01,69,6.168,70,5.96,71,1.297,72,2.333,73,3.179,74,1.252,75,2.782,76,2.387,77,3.637,78,2.384,79,3.023,80,3.179,81,8.351,82,5.96,83,5.96,84,5.373,85,4.696,86,3.837,87,3.965,88,5.31,89,3.637,90,5.96,91,3.179,92,2.598,93,1.64,94,1.926,95,1.715,96,2.384,97,1.64,98,2.079,99,4.035,100,1.413]],["t/10",[16,3.265,18,5.457,42,4.348,54,1.059,86,5.096,101,4.163,102,3.473,103,3.779,104,5.457]],["t/12",[0,4.218,5,4.546,7,2.553,32,3.652,103,3.89,105,4.082,106,3.848,107,4.75,108,3.882,109,6.34,110,5.303,111,6.34,112,2.874,113,4.371,114,6.34,115,2.922,116,3.564,117,4.546,118,3.141,119,4.082,120,3.406,121,4.371,122,5.715]],["t/14",[1,2.313,7,1.803,9,3.146,16,2.863,31,2.257,43,2.5,45,3.576,50,2.951,121,4.785,123,5.948,124,4.617,125,4.785,126,3.576,127,2.741,128,2.285,129,3.146,130,3.813,131,3.902,132,4.468,133,4.977]],["t/17",[16,2.638,43,1.853,54,0.855,71,2.633,74,1.984,112,2.899,127,3.236,134,3.881,135,4.841,136,6.395,137,2.456,138,1.271,139,2.805,140,5.35,141,4.973,142,4.792,143,3.994,144,5.35,145,5.275,146,5.35,147,4.409,148,5.765,149,2.761,150,3.513,151,5.765]],["t/19",[54,1.078,112,2.825,141,5.935,149,2.691,150,3.423,152,9.028,153,4.53,154,5.308,155,4.469,156,4.427,157,6.232,158,4.012,159,4.642,160,4.891,161,4.911,162,3.892,163,5.618]],["t/21",[1,1.015,3,2.456,7,2.127,31,0.99,43,0.882,45,3.067,51,1.953,54,0.796,60,2.399,71,1.914,72,2.226,74,2.098,98,1.569,100,1.683,108,1.202,115,1.404,127,1.898,128,1.002,132,3.094,138,1.44,139,1.336,145,5.272,147,2.099,156,2.64,163,2.745,164,2.547,165,2.514,166,5.515,167,6.544,168,1.138,169,2.427,170,2.547,171,2.183,172,2.427,173,2.547,174,2.427,175,1.846,176,3.045,177,3.961,178,2.745,179,2.476,180,2.281,181,4.104,182,3.145,183,5.656,184,1.636,185,1.754,186,2.399,187,4.104,188,7.051,189,2.399,190,1.902,191,2.399,192,2.026,193,3.045,194,3.045,195,2.547,196,3.045,197,2.547,198,2.547,199,3.045,200,1.404,201,1.636,202,2.281,203,3.045,204,2.026,205,2.183,206,3.045,207,2.099,208,1.601,209,1.96,210,2.547,211,2.026,212,2.099]],["t/23",[43,1.626,45,2.891,54,0.751,71,3.037,72,1.847,95,2.385,117,4.023,127,2.97,145,5.461,147,3.868,165,2.417,166,4.204,167,4.694,172,2.834,177,5.003,179,2.891,202,4.204,213,4.443,214,3.656,215,5.058,216,4.422,217,4.204,218,4.023,219,4.204,220,6.291,221,3.082,222,4.422,223,5.611,224,4.694,225,5.611,226,2.951,227,4.694,228,4.694,229,5.611,230,3.315]],["t/25",[7,1.239,9,2.162,43,1.949,45,4.014,51,1.939,71,2.162,74,1.48,87,3.174,100,1.671,127,2.656,128,1.57,130,3.695,138,1.585,145,3.071,165,1.533,177,4.475,182,2.992,187,4.082,188,7.023,189,3.759,190,2.979,191,3.759,192,3.174,197,3.991,204,3.174,215,4.3,231,2.895,232,2.319,233,4.3,234,4.691,235,4.771,236,4.3,237,4.771,238,6.063,239,4.771,240,4.771,241,4.771,242,3.574,243,4.3,244,3.759,245,3.991,246,3.991]],["t/27",[0,2.626,7,1.523,28,3.151,43,1.699,50,2.493,54,0.936,57,2.169,64,3.559,66,1.956,72,1.93,123,2.722,127,1.559,128,3.036,129,1.789,138,1.284,165,2.885,180,2.958,182,2.252,185,3.378,191,3.111,217,2.958,242,6.497,247,3.948,248,3.948,249,7.745,250,8.276,251,3.948,252,4.073,253,3.297,254,3.948,255,3.948,256,3.948,257,3.948,258,4.906,259,3.948,260,3.948,261,3.948,262,3.111,263,3.302,264,3.084,265,3.559,266,4.043,267,2.332,268,2.958,269,3.948,270,3.948,271,3.948,272,2.542,273,2.831,274,3.302,275,2.626,276,2.831,277,2.722,278,3.948,279,3.948,280,3.948]],["t/29",[7,1.661,28,3.436,47,3.168,57,4.501,72,2.105,112,2.899,119,4.117,129,2.899,138,0.992,165,2.055,182,3.147,214,3.984,246,5.35,277,4.409,281,5.765,282,5.039,283,4.586,284,5.35,285,5.039,286,5.039,287,4.586,288,3.779,289,5.765,290,4.409,291,3.23,292,4.719,293,6.395]],["t/31",[7,0.84,16,2.078,43,2.024,45,4.689,50,1.374,66,1.602,71,2.246,74,1.563,85,2.548,95,1.374,116,2.832,119,3.984,123,2.229,127,1.277,128,2.037,130,4.602,131,1.818,138,1.65,145,2.081,156,1.776,159,1.862,160,1.962,165,1.989,169,2.544,185,4.362,205,2.318,208,2.649,214,2.449,226,2.649,233,4.541,236,4.541,238,4.541,275,3.351,281,2.914,294,1.7,295,3.233,296,2.706,297,2.92,298,1.49,299,2.019,300,3.233,301,5.038,302,6.189,303,2.019,304,2.914,305,1.465,306,5.506,307,3.473,308,3.351,309,2.548,310,3.233,311,3.233,312,3.233]],["t/33",[43,2.72,45,3.576,50,2.951,54,0.928,71,2.231,123,4.785,127,2.741,128,2.84,130,3.813,131,3.902,169,3.505,209,4.468,304,6.257,313,5.806,314,4.468,315,6.941,316,4.212,317,5.806,318,4.785]],["t/35",[0,4.617,1,2.313,5,4.977,6,5.469,7,1.803,42,3.813,50,2.951,80,5.469,100,2.43,116,3.902,133,4.977,184,3.729,186,5.469,294,3.65,319,4.785,320,4.785,321,5.469,322,3.902,323,5.469,324,6.941,325,4.212,326,2.665]],["t/37",[3,1.349,5,3.645,7,1.732,8,1.004,20,2.949,23,1.985,25,1.159,30,1.505,31,1.064,33,4.782,36,1.193,37,1.597,39,1.159,40,1.597,41,2.949,42,2.357,43,2.037,45,0.984,50,0.812,54,0.255,56,1.597,57,1.049,61,1.721,62,1.505,63,1.597,65,2.736,72,0.629,75,1.317,76,0.765,89,1.721,101,1.72,102,1.435,103,0.912,104,1.317,106,1.159,108,2.008,112,0.866,118,2.832,121,1.317,127,1.292,129,0.866,130,1.049,131,1.074,134,1.159,138,1.676,141,3.085,200,1.508,204,1.27,226,1.72,246,1.597,252,1.72,253,1.074,290,1.317,292,1.1,323,1.505,327,1.27,328,6.667,329,2.106,330,4.291,331,3.809,332,1.229,333,2.578,334,2.736,335,1.91,336,1.505,337,1.505,338,1.721,339,8.933,340,2.346,341,1.597,342,2.578,343,1.721,344,1.91,345,1.91,346,2.472,347,1.91,348,1.91,349,1.91,350,1.193,351,1.505,352,2.949,353,1.721,354,1.369,355,1.91,356,1.91,357,1.91,358,1.91,359,1.91,360,3.505,361,2.043,362,1.317,363,1.431,364,1.91,365,4.006,366,2.949,367,2.736,368,1.91,369,1.91,370,1.505,371,2.578,372,1.91,373,1.597,374,1.1,375,1.597,376,1.91,377,2.451,378,1.721,379,1.91,380,1.27,381,1.721,382,1.721,383,3.271,384,2.451,385,1.91,386,1.91,387,1.91,388,1.91,389,0.788]],["t/39",[3,2.419,5,5.017,7,2.332,8,2.076,25,2.396,30,3.111,31,2.276,33,3.302,36,2.465,37,3.302,39,2.396,40,3.302,43,2.244,44,2.465,45,2.034,60,3.111,62,3.111,63,3.302,65,3.302,66,1.956,72,1.299,76,1.581,97,2.844,101,2.076,108,1.559,112,1.789,116,2.219,117,2.831,118,3.836,120,2.121,124,2.626,125,2.722,127,1.559,130,2.169,131,3.297,132,2.542,138,0.91,200,2.703,204,2.626,243,3.559,252,2.076,253,2.219,320,2.722,322,2.219,323,3.111,331,2.958,333,3.111,334,3.302,337,3.111,338,3.559,341,3.302,342,3.111,346,3.378,352,3.559,354,2.831,360,4.824,361,3.662,365,5.514,378,3.559,381,3.559,382,3.559,389,1.628,390,5.865,391,3.559,392,3.302,393,3.302,394,2.542,395,1.851,396,3.948,397,2.626,398,3.111,399,2.831,400,3.111]],["t/41",[0,4.753,5,5.122,6,5.629,7,2.47,8,3.757,54,0.956,108,2.821,263,5.976,296,3.838,322,4.016,325,4.336,397,5.843,401,5.353,402,3.608,403,7.144,404,6.44]],["t/43",[7,2.592,8,3.818,9,2.414,12,3.326,13,3.232,75,3.672,76,2.133,88,3.99,95,2.264,104,5.005,108,3.262,112,2.414,120,2.861,137,2.045,138,0.826,150,2.925,334,4.455,340,3.819,397,4.83,405,4.801,406,4.801,407,3.99,408,4.801,409,3.99,410,4.197,411,4.801,412,4.801,413,3.99,414,4.801,415,2.69,416,4.801,417,3.429,418,5.326,419,4.197,420,5.326,421,5.326,422,4.757,423,4.455,424,4.455,425,5.326]],["t/45",[3,1.334,38,3.775,53,1.334,71,2.578,75,2.229,76,1.295,80,4.877,88,2.423,94,3.616,116,1.818,119,2.081,122,2.914,127,3.168,138,1.57,145,5.165,147,4.817,149,1.396,165,2.246,167,2.705,180,2.423,183,4.214,184,3.754,190,2.019,195,5.845,214,1.572,226,3.255,252,2.649,275,3.351,305,3.167,389,1.334,397,3.351,405,5.579,408,5.579,411,4.541,412,4.541,413,3.775,416,4.541,426,3.057,427,1.737,428,2.705,429,3.233,430,4.541,431,4.214,432,3.057,433,2.283,434,6.988,435,1.666,436,1.162,437,2.914,438,5.038,439,3.233,440,3.233,441,3.233,442,2.318,443,2.423,444,6.189,445,3.351,446,2.019,447,2.151,448,2.019,449,1.602]],["t/47",[115,3.359,134,4.422,360,5.023,449,3.609,450,6.095,451,5.224,452,5.396,453,5.023,454,3.248,455,5.741,456,5.224,457,6.568,458,6.568,459,7.286,460,7.286,461,6.095]],["t/49",[94,4.407,118,3.313,212,4.61,231,4.059,321,5.27,346,3.852,389,2.758,452,5.116,462,6.028,463,5.011,464,5.594,465,6.687,466,6.687,467,6.687,468,5.27,469,6.687,470,5.594,471,6.687,472,3.517,473,6.028,474,6.687,475,5.594,476,4.61]],["t/51",[74,2.354,100,2.657,252,4.792,397,5.048,402,3.832,452,4.605,477,7.622,478,5.441,479,5.979,480,7.588,481,7.588]],["t/53",[43,1.521,48,3.862,74,1.629,128,2.366,132,3.379,159,3.023,218,5.878,275,3.492,397,3.492,436,1.886,452,4.363,453,3.619,456,3.764,477,4.391,478,5.878,482,7.189,483,6.48,484,6.48,485,7.189,486,4.732,487,8.198,488,3.764,489,5.249,490,5.249,491,5.249,492,5.249,493,5.249,494,7.189,495,5.249,496,5.249,497,5.249,498,5.249,499,5.249,500,5.249,501,3.023,502,4.391,503,4.136,504,5.249,505,4.732,506,3.101,507,2.82,508,5.249]],["t/55",[80,6.238,252,4.163,389,3.265,422,4.559,509,4.348,510,5.457,511,6.622,512,7.916,513,5.266]],["t/57",[43,2.154,74,2.306,138,1.153,389,3.712,436,2.671,452,4.512,501,4.282,506,4.392,507,3.994,514,4.642,515,8.112,516,6.701,517,7.434]],["t/59",[50,2.385,74,2.333,94,2.679,138,1.567,329,3.612,346,3.232,354,4.023,389,3.102,452,4.564,461,6.291,468,4.422,501,3.232,511,7.096,515,5.058,516,6.779,518,4.204,519,5.393,520,4.422,521,5.611,522,5.611,523,5.611,524,4.204,525,3.082,526,4.204,527,7.521,528,5.611,529,5.611,530,5.611,531,2.281,532,5.611,533,5.611]],["t/61",[43,2.111,74,2.26,94,3.478,138,1.489,252,3.832,514,4.55,534,5.459,535,7.286,536,6.568,537,7.286,538,7.286,539,7.286,540,7.286,541,7.286,542,7.286]],["t/63",[43,1.746,74,1.87,137,2.314,138,1.446,252,4.147,321,6.925,436,2.165,506,3.561,507,3.238,534,7.577,543,7.108,544,7.885,545,3.252,546,6.027,547,2.778,548,3.17,549,6.027,550,2.523,551,6.027,552,6.027,553,6.027,554,6.027,555,5.433]],["t/65",[43,2.05,74,2.195,138,1.098,252,5.2,436,2.542,506,4.18,507,3.801,514,6.343,543,7.87,555,6.377,556,7.87]],["t/67",[43,2.176,200,3.462,282,7.135,361,4.69,452,4.558,464,7.574,557,3.793,558,7.51,559,4.035,560,4.326,561,5.918,562,6.282]],["t/69",[43,2.245,50,3.294,103,3.699,108,3.06,200,3.572,282,6.106,464,6.482,559,5.293,563,5.155]],["t/70",[7,1.753,44,4.215,50,3.604,103,3.222,108,2.665,121,4.653,212,4.653,446,5.295,503,5.318,506,5.009,559,4.98,563,4.49,564,6.079,565,6.749,566,6.084,567,4.345,568,4.49,569,4.096,570,6.749]],["t/72",[39,4.965,117,5.866,127,3.231,452,4.965,514,5.109,571,6.844]],["t/74",[28,4.529,48,3.593,135,4.98,138,1.038,218,6.044,346,3.852,354,4.795,389,3.808,486,6.028,514,5.765,518,5.011,557,3.377,572,5.27,573,6.687,574,5.27,575,6.687,576,5.594,577,3.673,578,6.687]],["t/76",[39,4.096,43,1.955,48,3.626,54,0.903,74,2.876,87,4.49,154,3.794,252,4.459,433,4.202,442,6.079,514,4.215,556,6.084,579,5.645,580,6.084,581,6.391,582,6.749,583,6.749]],["t/78",[39,3.916,43,2.387,54,1.214,119,5.305,282,5.084,291,3.258,320,4.448,442,4.626,452,5.001,514,5.146,579,5.397,580,5.816,584,6.595,585,6.452,586,6.452,587,6.452,588,3.466,589,6.452,590,6.452,591,4.626,592,6.452]],["t/80",[31,2.175,39,4.059,137,3.237,138,1.607,242,5.011,514,4.176,550,3.529,593,3.951,594,6.028,595,6.687,596,6.687,597,8.43,598,8.43,599,8.43,600,6.687]],["t/82",[32,4.8,54,1.115,74,2.037,127,3.291,138,1.293,141,5.058,242,4.921,452,3.986,601,4.709,602,6.567,603,6.567,604,9.155,605,8.334,606,9.155,607,5.204,608,5.365]],["t/84",[51,2.089,54,0.687,64,3.119,112,2.329,115,2.369,138,1.606,174,2.595,326,1.973,389,2.12,399,3.685,452,3.119,514,3.209,519,3.685,534,3.85,547,2.369,594,7.308,609,5.139,610,2.453,611,6.782,612,5.901,613,7.084,614,5.139,615,5.139,616,5.139,617,5.139,618,4.299,619,5.139,620,4.632,621,5.139,622,5.139,623,5.139,624,3.543,625,5.139,626,5.139,627,5.139,628,5.139,629,4.632,630,5.139,631,4.632]],["t/86",[16,3.265,31,2.575,54,1.059,101,4.163,102,3.473,384,5.931,632,6.238,633,6.622,634,7.916]],["t/88",[36,5.109,138,1.269,635,5.267,636,6.844,637,7.375,638,5.64]],["t/90",[18,5.342,36,4.839,138,1.202,584,5.342,635,4.988,636,6.482,637,6.985,638,5.342,639,7.749,640,7.749,641,7.749]],["t/92",[138,0.967,367,5.213,577,3.423,584,6.51,635,6.295,636,8.179,638,5.556,642,6.232,643,6.232,644,9.778,645,6.232,646,8.059,647,4.642,648,6.232,649,6.232,650,6.232]],["t/94",[21,6.171,42,4.302,102,3.435,632,6.171,638,5.399,651,7.831,652,7.831,653,7.831,654,6.171,655,7.831]],["t/96",[47,4.145,131,4.705,656,4.145,657,5.769]],["t/98",[8,3.757,51,2.903,52,4.221,54,0.956,66,3.539,139,3.134,509,3.924,563,4.753,588,3.838,658,7.144,659,9.511,660,5.629,661,6.44,662,6.44,663,5.976,664,7.144,665,7.144]],["t/100",[25,3.141,51,2.893,53,2.936,54,0.692,68,2.613,71,2.288,112,2.346,115,2.386,121,3.568,125,3.568,127,2.044,149,3.074,171,3.711,187,3.141,265,8.283,272,3.332,298,2.386,373,4.329,392,5.955,488,3.711,666,8.137,667,3.878,668,3.711,669,5.175,670,7.119,671,5.104,672,3.339,673,5.175,674,8.137,675,5.175,676,5.175,677,5.175,678,4.665,679,3.141,680,3.443,681,3.332,682,4.329,683,3.878,684,5.175,685,2.981,686,3.141,687,5.175]],["t/102",[1,1.914,7,1.492,17,3.959,42,3.155,53,2.369,54,0.768,78,3.393,87,3.821,112,2.603,124,3.821,125,3.959,138,1.185,139,2.519,141,3.486,149,2.48,153,3.229,264,3.02,296,3.086,326,2.933,354,4.118,433,2.603,461,4.804,601,4.118,672,2.694,688,5.743,689,5.177,690,3.821,691,5.743,692,5.177,693,3.821,694,7.638,695,5.177,696,4.513,697,4.804,698,3.697,699,7.638,700,5.743,701,5.177,702,4.804]],["t/104",[31,2.603,72,2.634,123,5.517,262,6.306,307,5.517,377,5.996,703,6.694,704,5.152]],["t/106",[7,1.425,25,3.329,31,2.409,54,1.254,72,2.956,134,3.329,138,1.149,139,2.406,165,1.763,168,2.05,175,2.106,262,5.836,305,2.486,326,2.106,361,3.425,422,3.159,449,2.717,608,3.531,672,2.573,705,4.588,706,4.945,707,4.322,708,4.588,709,4.945,710,4.588,711,6.608,712,3.159,713,3.329,714,4.945,715,3.782,716,3.084,717,3.933,718,4.588,719,4.11,720,2.826,721,4.11,722,4.588,723,4.945]],["t/108",[43,1.973,57,3.742,124,4.531,138,1.057,162,4.254,175,2.616,377,5.104,422,3.923,484,6.14,531,2.768,672,3.195,703,5.698,716,3.829,717,4.884,718,5.698,719,5.104,720,3.509,721,5.104,724,6.812,725,5.038,726,6.812,727,5.368,728,5.698]],["t/110",[0,3.622,7,1.414,31,2.397,36,3.4,50,3.133,51,2.213,54,1.117,64,3.304,115,2.51,126,3.796,127,3.299,138,1.143,139,2.388,156,2.991,204,3.622,294,2.863,305,3.34,314,3.505,320,3.753,326,2.091,350,3.4,389,2.246,433,2.468,588,3.959,690,3.622,696,3.217,704,5.763,729,4.554,730,2.863,731,3.753,732,3.622,733,4.908,734,3.4,735,5.444,736,5.444,737,4.473,738,5.444]],["t/112",[1,2.059,54,0.827,71,1.986,74,1.917,175,2.373,231,3.75,313,5.169,317,5.169,326,2.373,346,3.559,402,3.121,472,4.215,612,6.059,672,2.898,693,4.111,739,5.57,740,6.179,741,3.978,742,6.179,743,5.169,744,5.57,745,5.57,746,6.179,747,5.169,748,6.704,749,4.63,750,6.179,751,6.179,752,5.169,753,6.179]],["t/115",[32,3.874,54,1.132,94,3.211,128,1.57,159,4.487,165,1.533,175,2.583,182,1.832,200,2.199,283,4.823,427,2.563,562,3.991,577,4.9,612,6.444,631,6.063,693,5.183,705,5.627,754,8.461,755,6.063,756,4.771,757,5.04,758,4.771,759,4.3,760,4.3,761,4.771,762,6.066,763,6.726,764,7.627,765,3.174,766,4.3,767,6.726]],["t/117",[32,2.58,53,2.653,54,0.86,57,3.533,138,1.351,159,2.58,160,2.718,165,2.067,168,2.403,175,1.72,179,2.307,190,2.797,200,2.964,230,2.646,296,2.406,346,2.58,506,4.446,524,4.819,525,2.46,562,5.38,612,6.151,686,3.903,693,5.47,748,6.294,755,5.797,757,5.638,759,4.037,760,4.037,768,4.479,769,4.479,770,5.797,771,7.524,772,5.187,773,6.294,774,4.479,775,4.479,776,2.797,777,4.479,778,4.567,779,4.479,780,4.479]],["t/119",[32,3.282,51,2.316,54,1.016,66,2.823,100,1.995,149,2.461,175,2.188,264,4.496,273,4.086,274,4.767,276,6.13,318,3.929,326,2.918,392,4.767,437,6.85,545,2.35,572,4.49,577,4.696,612,4.892,693,5.055,748,4.767,757,6.833,773,4.767,781,5.699,782,4.27,783,8.22,784,5.699,785,4.49]],["t/121",[32,4.155,115,3.325,138,1.545,175,2.77,276,5.173,346,4.155,612,5.689,693,4.799,783,6.503,786,5.405,787,7.214,788,7.214,789,7.214,790,7.214]],["t/123",[13,3.881,54,1.096,128,2.105,130,3.513,134,3.881,172,3.23,175,2.456,180,4.792,399,4.586,428,5.35,436,3.248,612,6.138,692,5.765,693,4.255,752,5.35,778,3.881,791,5.765,792,5.35,793,6.457,794,3.881,795,5.765,796,3.684,797,4.586,798,6.395]],["t/126",[3,2.784,4,3.987,47,4.2,54,1.134,93,2.743,103,3.222,108,3.348,138,1.047,168,2.522,389,2.784,433,3.059,449,3.343,656,3.343,720,3.477,799,4.215,800,6.749,801,6.084,802,3.626,803,4.282,804,4.49,805,5.645]],["t/128",[15,3.14,18,4.696,23,4.134,46,6.72,47,3.374,48,3.66,67,3.829,68,3.44,93,3.466,102,2.988,103,4.071,104,4.696,108,2.69,548,3.582,564,4.884,806,5.698,807,4.696,808,6.812,809,6.14,810,5.698,811,6.812]],["t/130",[7,2.458,103,3.863,108,3.738,138,1.255,812,4.78]],["t/132",[103,4.041,108,3.343,813,7.631]],["t/134",[25,3.916,28,4.427,31,2.68,36,4.029,54,0.863,106,3.916,119,4.153,134,3.916,190,4.029,272,5.305,337,5.084,449,3.196,472,3.393,548,3.393,743,6.893,814,8.24,815,6.452,816,4.448,817,6.452,818,5.681,819,6.452,820,6.452,821,6.452,822,6.452]],["t/136",[15,2.548,51,2.246,54,0.739,69,4.355,100,1.935,127,2.182,128,2.45,138,1.397,185,3.183,209,3.558,298,3.432,394,4.793,413,4.141,454,2.464,569,4.518,574,4.355,707,4.355,725,3.265,818,3.81,823,3.677,824,5.132,825,9.007,826,4.355,827,5.527,828,4.952,829,5.527,830,7.444,831,5.338,832,6.71,833,4.982,834,4.141,835,3.265,836,4.982,837,4.982]],["t/138",[1,1.148,3,1.421,4,2.036,7,1.375,13,2.092,15,1.589,23,3.91,25,2.092,51,2.151,52,2.036,54,0.461,68,1.74,74,1.069,84,3.106,94,1.645,97,1.401,113,2.376,126,1.775,134,3.212,135,3.127,138,1,159,1.985,165,1.108,168,1.288,172,1.74,175,1.323,192,2.293,267,2.036,292,3.048,298,1.589,307,2.376,308,2.293,342,2.716,350,2.152,374,3.048,394,3.407,401,2.582,413,4.828,414,4.771,415,2.673,446,2.152,448,2.152,449,1.707,453,2.376,454,3.67,472,1.812,545,1.421,577,1.893,629,3.106,667,2.582,672,1.616,720,1.775,791,3.106,792,2.883,802,1.851,812,2.036,818,2.376,824,2.376,828,3.521,831,2.471,838,3.965,839,2.376,840,3.446,841,2.883,842,3.106,843,3.446,844,4.771,845,3.446,846,2.716,847,3.446,848,4.771,849,4.17,850,3.106,851,1.985,852,3.106,853,2.471,854,3.106,855,2.883,856,3.106,857,3.106,858,3.106,859,2.219,860,3.91,861,2.152,862,3.106,863,2.716,864,3.106,865,2.883,866,3.106,867,2.716,868,2.471,869,2.471,870,2.471,871,2.883,872,2.622,873,4.427,874,2.883,875,2.716,876,2.376,877,3.106]],["t/141",[3,1.87,35,2.832,45,2.336,54,0.607,94,2.165,107,3.397,126,2.336,138,0.703,165,1.457,204,3.016,214,2.204,234,3.412,266,6.826,276,3.251,296,3.486,316,2.752,318,3.126,326,1.741,361,5.164,374,2.612,380,3.016,451,3.251,501,2.612,519,6.274,577,3.564,608,2.919,679,2.752,713,2.752,716,2.549,730,2.385,799,2.832,834,3.397,859,2.919,868,3.251,878,4.087,879,4.087,880,4.534,881,3.573,882,4.534,883,5.432,884,4.087,885,3.016,886,3.251,887,3.573,888,3.251,889,4.087,890,4.534,891,4.087,892,3.573,893,4.087,894,4.534,895,4.534,896,4.534,897,4.534,898,4.534,899,4.534,900,4.534]],["t/144",[7,0.656,31,0.462,32,5.679,39,0.862,51,1.387,52,0.839,54,0.338,67,3.199,72,0.468,74,1.058,87,1.681,94,2.263,100,0.497,107,1.064,112,0.644,115,1.573,127,0.561,128,0.468,132,0.915,134,3.187,138,1.442,141,2.51,159,1.455,160,2.071,171,4.591,180,1.064,200,0.655,204,0.945,207,0.979,208,1.329,211,0.945,212,0.979,221,0.78,234,1.329,253,0.799,264,0.747,266,4.185,274,8.456,288,0.839,294,1.794,296,0.763,298,0.655,305,0.644,319,0.979,326,0.97,361,3.998,380,1.681,433,1.145,436,1.226,456,1.019,501,0.818,519,3.766,520,1.991,531,0.577,548,2.175,559,0.763,560,0.818,568,0.945,569,0.862,581,1.742,607,0.887,612,3.663,660,1.119,679,1.533,685,0.818,686,0.862,698,1.627,716,0.799,720,2.131,737,1.533,764,1.281,772,1.742,794,1.533,816,3.268,828,3.154,867,1.119,868,1.019,869,1.019,876,1.742,883,1.812,885,0.945,886,1.019,888,1.812,892,1.119,901,1.421,902,5.252,903,1.119,904,4.74,905,5.69,906,2.526,907,4.74,908,3.412,909,1.421,910,1.281,911,1.421,912,2.526,913,2.526,914,1.421,915,1.893,916,1.421,917,1.533,918,1.281,919,1.421,920,1.188,921,1.119,922,2.113,923,1.119,924,0.979,925,1.281,926,6.07,927,2.526,928,2.526,929,1.064,930,1.281,931,1.281,932,0.862,933,1.019,934,1.064,935,1.281,936,1.119,937,6.403,938,1.421,939,1.281,940,1.281,941,1.119,942,1.421,943,2.113,944,1.421,945,1.421,946,1.421,947,1.281,948,2.526,949,1.421,950,2.526,951,1.421,952,1.421,953,1.421,954,1.421,955,0.915,956,1.421,957,2.852,958,1.281,959,1.421,960,1.421,961,1.421,962,3.412,963,1.281,964,1.421,965,1.421,966,1.421,967,1.421,968,1.421,969,1.421,970,1.281,971,1.281]],["t/146",[4,1.679,7,0.295,28,1.527,32,1.637,51,0.462,53,0.469,54,0.543,67,2.558,68,0.574,71,2.876,72,1.763,74,1.26,76,0.828,78,0.671,79,1.548,87,0.756,94,1.357,98,0.585,100,1.422,102,0.498,107,0.851,112,1.288,113,0.783,115,1.872,128,0.936,131,0.639,134,4.968,138,1.478,139,0.498,141,2.465,143,0.71,146,0.951,158,0.732,159,2.015,160,1.725,165,0.913,166,5.973,171,0.815,175,0.794,179,0.585,181,2.762,190,0.71,198,2.378,201,0.61,207,0.783,208,1.087,209,1.83,211,1.375,221,0.624,232,1.005,234,0.598,253,2.558,266,3.137,268,0.851,275,0.756,285,0.895,287,0.815,288,1.679,292,0.654,294,1.087,297,0.414,303,0.71,305,1.586,319,1.425,325,0.69,326,2.583,354,0.815,361,0.71,374,0.654,377,0.851,380,1.891,399,0.815,407,2.13,415,1.435,426,3.453,431,2.927,432,0.69,433,0.515,436,1.257,456,0.815,510,0.783,525,1.922,564,0.815,567,0.732,569,0.69,571,0.951,576,0.951,581,0.783,588,0.61,607,0.71,610,0.542,611,1.729,660,3.201,679,1.254,685,1.19,686,2.465,696,0.671,716,0.639,720,1.464,728,0.951,729,0.951,730,0.598,737,1.725,772,1.425,776,0.71,797,0.815,799,0.71,859,0.732,868,0.815,870,0.815,878,1.024,883,4.664,886,0.815,887,0.895,910,1.863,917,0.69,923,4.219,932,0.69,936,2.24,940,1.024,941,0.895,947,1.024,970,1.024,972,1.863,973,1.136,974,1.136,975,2.702,976,4.062,977,6.929,978,2.067,979,2.067,980,2.067,981,3.499,982,4.062,983,4.062,984,2.842,985,2.842,986,0.815,987,0.851,988,0.895,989,1.136,990,1.729,991,2.562,992,1.024,993,3.154,994,0.895,995,1.024,996,0.851,997,1.375,998,0.921,999,1.024,1000,1.11,1001,1.024,1002,1.863,1003,0.951,1004,1.136,1005,1.548,1006,1.136,1007,1.136,1008,1.136,1009,1.136,1010,3.499,1011,1.729,1012,0.851,1013,0.598,1014,1.136,1015,1.136,1016,1.136,1017,5.99,1018,2.842,1019,2.842,1020,1.136,1021,1.136,1022,1.136,1023,1.136,1024,1.136,1025,0.815,1026,1.162,1027,2.562,1028,0.815,1029,0.624,1030,0.895,1031,1.024,1032,2.067,1033,2.842,1034,2.067,1035,1.136,1036,0.639,1037,0.756,1038,1.136,1039,1.087,1040,1.024,1041,1.136,1042,1.136,1043,1.024,1044,2.067,1045,1.136,1046,0.815,1047,2.067,1048,1.136,1049,0.951,1050,0.951,1051,1.136,1052,1.136,1053,1.136,1054,1.024,1055,1.136,1056,1.024,1057,0.895]],["t/148",[16,1.37,32,1.913,50,1.412,51,1.35,54,0.688,66,1.645,72,1.093,74,1.03,95,3.014,127,2.799,128,3.018,131,1.867,135,3.719,165,1.067,166,3.854,168,1.241,169,2.598,171,2.381,190,4.427,198,2.778,200,1.531,210,2.778,211,2.209,230,1.962,245,2.778,264,1.747,283,2.381,294,1.747,296,1.784,298,1.531,326,3.521,327,3.422,361,2.074,426,4.654,456,2.381,468,2.617,472,2.705,569,2.016,576,2.778,581,2.29,588,1.784,591,2.381,611,2.778,657,2.29,680,4.188,698,2.138,725,1.962,747,2.778,797,2.381,860,2.016,932,2.016,936,2.617,939,2.994,943,2.778,958,2.994,991,4.637,993,5.675,996,2.488,998,1.481,999,2.994,1001,2.994,1002,2.994,1005,2.488,1013,3.311,1037,3.422,1049,4.303,1056,2.994,1058,2.617,1059,3.321,1060,3.321,1061,2.994,1062,4.717,1063,3.321,1064,3.321,1065,3.321,1066,2.994,1067,3.321,1068,2.778,1069,3.321,1070,2.209,1071,5.144,1072,2.381,1073,3.321,1074,3.321,1075,3.321,1076,2.381,1077,2.29]],["t/151",[29,4.136,53,2.165,54,1.097,58,3.764,110,6.013,128,2.366,165,1.687,213,4.247,231,3.186,252,2.761,276,6.839,305,2.379,314,3.379,326,2.016,342,4.136,433,3.258,470,4.391,472,3.78,511,4.391,545,2.165,557,2.651,567,4.628,671,3.764,672,2.462,739,4.732,741,3.379,766,4.732,792,6.013,794,3.186,872,3.561,903,4.136,931,4.732,1078,5.249,1079,5.249,1080,7.189,1081,5.249,1082,3.933,1083,4.391,1084,3.023,1085,4.732]],["t/154",[43,2.248,50,2.5,54,0.787,100,2.06,102,3.809,154,3.307,165,1.89,298,2.711,306,6.115,316,3.57,445,3.913,451,4.217,559,4.666,581,4.055,593,5.131,638,5.35,716,3.307,802,3.16,816,4.055,872,2.914,1062,4.407,1086,5.882,1087,4.635,1088,5.564,1089,4.635,1090,5.302,1091,3.913,1092,4.055,1093,5.302,1094,4.635,1095,2.622]],["t/156",[102,3.196,118,3.609,201,4.776,286,5.741,297,2.652,303,4.55,447,4.847,451,5.224,472,3.832,559,3.914,593,4.305,802,3.914,1092,5.023,1093,6.568,1096,7.286,1097,6.568]],["t/158",[16,2.197,97,2.165,102,3.623,156,2.925,165,1.712,168,1.99,172,2.69,209,3.429,296,2.861,316,3.232,327,3.543,433,3.29,450,4.455,475,4.455,501,4.181,545,2.994,559,2.861,591,3.819,593,3.147,638,5.005,680,3.543,696,4.289,725,3.147,786,5.44,851,3.067,933,3.819,987,3.99,1049,4.455,1090,6.544,1094,4.197,1095,3.237,1097,4.801,1098,5.326,1099,7.26,1100,4.197,1101,5.326,1102,5.326,1103,7.26,1104,5.326,1105,4.801,1106,3.672,1107,5.326]],["t/160",[2,6.377,54,0.946,71,2.274,76,2.834,127,2.794,143,4.418,200,3.261,232,3.44,252,3.721,531,2.875,734,4.418,776,4.418,860,4.294,1039,3.721,1095,3.154,1108,5.301,1109,4.707,1110,4.707,1111,6.377,1112,5.073]],["t/162",[1,2.042,31,1.993,47,3.035,53,2.527,54,0.82,96,3.62,97,2.49,118,3.035,127,3.148,137,2.353,138,1.509,143,3.827,165,1.969,350,3.827,395,2.874,402,3.095,415,3.095,436,2.864,507,3.292,545,2.527,656,3.035,696,3.62,717,4.394,793,4.829,1108,5.972,1113,4.225,1114,5.126,1115,6.128,1116,6.128]],["t/164",[53,3.07,54,1.205,72,1.819,74,1.715,76,2.982,93,2.246,129,3.374,131,3.107,169,2.791,182,2.859,200,4.152,267,3.265,316,3.354,365,4.355,395,2.592,406,4.982,415,2.791,432,3.354,449,2.738,531,3.661,550,2.314,656,2.738,730,2.906,804,3.677,834,4.141,1084,3.183,1113,5.132,1117,4.982,1118,4.355,1119,4.623,1120,4.355,1121,3.81,1122,4.982,1123,3.81,1124,5.527]],["t/166",[4,3.217,8,2.863,16,2.246,43,2.135,53,3.04,54,1.117,76,3.747,93,3.395,103,3.988,108,3.535,168,2.754,169,2.749,200,4.312,365,4.29,449,3.65,454,2.427,803,2.749,998,2.427,1113,5.759,1123,3.753,1125,8.354,1126,7.369,1127,5.444,1128,5.444,1129,4.079]],["t/168",[54,0.603,76,1.805,138,1.654,141,2.735,165,1.448,182,1.731,200,2.077,285,3.551,291,2.276,395,2.114,545,1.859,547,2.077,548,2.37,550,1.887,567,2.901,610,3.084,672,2.114,998,2.009,1026,2.533,1117,4.062,1122,4.062,1130,3.77,1131,4.506,1132,4.506,1133,4.506,1134,6.459,1135,6.459,1136,4.506,1137,4.506,1138,4.506,1139,4.506,1140,3.231,1141,4.506,1142,4.506,1143,4.506,1144,4.506,1145,4.506,1146,4.506,1147,4.506,1148,4.062,1149,6.459,1150,4.062,1151,4.506,1152,4.062,1153,4.506,1154,4.506,1155,4.506,1156,4.506,1157,4.062,1158,4.506,1159,4.506,1160,4.506,1161,2.421,1162,4.506]],["t/170",[1,1.549,32,2.678,43,1.347,67,2.614,71,2.123,97,1.89,128,2.174,129,2.107,138,1.578,177,5.564,230,2.747,252,4.04,326,2.537,442,4.736,501,2.678,816,3.205,870,3.334,921,5.205,923,3.664,1054,4.191,1095,2.073,1109,5.111,1111,6.925,1163,2.614,1164,4.65,1165,4.65,1166,3.889,1167,4.65,1168,6.605,1169,5.525,1170,4.65,1171,5.954,1172,4.65,1173,6.605,1174,6.605,1175,6.605,1176,6.605,1177,6.605,1178,4.65,1179,4.191,1180,4.65]],["t/172",[1,1.59,54,0.638,74,1.48,137,1.832,138,1.609,149,2.904,155,3.421,252,2.509,350,4.2,395,3.155,531,1.939,545,3.213,607,2.979,610,3.211,672,2.237,704,3.071,716,2.682,719,3.574,720,2.458,721,3.574,722,3.991,839,4.637,975,3.174,1095,3.772,1110,3.174,1112,3.421,1181,8.921,1182,3.574,1183,3.991,1184,4.3,1185,4.771,1186,4.771,1187,6.726,1188,6.726,1189,6.726,1190,4.771,1191,4.771,1192,4.771,1193,4.771,1194,4.771]],["t/174",[1,1.899,31,1.853,97,2.316,116,4.272,137,2.188,138,0.884,139,3.333,162,3.559,326,2.188,417,3.669,422,3.282,483,5.137,560,3.282,672,3.564,679,4.612,680,3.791,716,3.204,717,4.086,718,4.767,719,4.27,720,2.936,721,4.27,727,4.49,824,3.929,831,4.086,865,4.767,886,4.086,998,2.541,1039,2.997,1068,4.767,1087,4.49,1095,3.388,1182,4.27,1195,4.767,1196,4.49,1197,5.137,1198,5.137,1199,5.699,1200,5.699,1201,5.699]],["t/177",[7,1.492,43,2.65,72,1.891,123,3.959,124,3.821,125,3.959,128,1.891,131,3.229,138,1.185,258,7.179,272,3.697,291,2.9,297,2.09,298,2.647,307,3.959,326,2.206,395,2.694,399,4.118,683,4.303,690,3.821,704,3.697,737,3.486,1202,5.177,1203,7.638,1204,5.743,1205,4.804,1206,5.743,1207,5.743,1208,5.743,1209,4.804,1210,5.743,1211,5.743,1212,5.743,1213,5.743,1214,5.177,1215,5.743,1216,5.743]],["t/179",[50,3.478,127,3.231,298,3.771,1217,6.844,1218,8.181,1219,4.599]],["t/181",[31,2.691,201,4.445,559,4.445,593,4.888,1220,8.274]],["t/183",[9,1.88,16,2.509,42,2.279,43,1.763,50,1.764,54,0.555,72,1.366,76,1.662,87,2.76,97,1.686,100,2.522,113,2.86,123,2.86,127,1.638,128,1.366,138,0.944,142,3.109,149,3.11,153,2.333,175,2.336,201,2.229,252,2.182,272,2.671,296,2.229,297,2.622,305,1.88,314,3.917,326,2.336,380,4.047,389,1.711,397,2.76,398,3.269,400,3.269,433,1.88,531,2.928,547,1.913,559,4.538,593,4.991,683,3.109,690,4.047,720,2.137,730,2.182,737,2.518,741,2.671,776,2.591,794,2.518,813,5.484,835,2.451,859,2.671,917,2.518,1012,3.109,1029,2.279,1030,3.269,1088,2.975,1089,4.794,1106,2.86,1221,2.229,1222,3.74,1223,3.74,1224,3.74,1225,3.471,1226,4.149,1227,4.149,1228,4.149,1229,3.74,1230,4.149,1231,3.74]],["t/185",[3,0.843,31,1.728,35,1.276,43,0.592,50,0.868,53,1.429,54,0.711,72,2.649,74,0.634,76,1.388,95,1.473,97,3.271,98,1.785,100,1.213,112,0.926,113,5.39,127,3.329,133,1.465,134,1.24,137,0.784,138,1.534,149,2.295,165,0.656,168,0.763,172,1.032,174,2.278,187,1.24,221,1.903,228,1.709,252,3.624,288,1.207,291,1.032,297,2.508,303,1.276,307,1.408,314,1.315,321,1.61,325,1.24,326,3.091,395,1.625,426,2.103,445,1.359,525,1.122,531,3.353,545,2.454,559,4.529,564,1.465,593,4.98,601,3.235,657,1.408,685,2.599,690,4.823,696,1.207,704,1.315,734,1.276,737,2.103,743,1.709,749,1.531,794,2.103,796,1.177,799,1.276,824,1.408,831,1.465,861,1.276,883,2.484,887,1.61,995,1.841,1025,1.465,1028,1.465,1029,1.122,1076,2.484,1077,2.389,1123,1.408,1182,1.531,1232,4.512,1233,2.596,1234,2.043,1235,4.512,1236,4.512,1237,3.555,1238,3.465,1239,1.207,1240,2.043,1241,4.067,1242,2.898,1243,2.043,1244,2.043,1245,2.043,1246,2.043,1247,2.043,1248,1.61,1249,2.043,1250,0.911,1251,1.841,1252,1.359,1253,2.043]],["t/187",[1,1.122,13,2.043,15,1.552,42,1.85,43,1.506,50,1.431,52,1.989,53,2.945,54,0.849,64,3.155,72,2.966,74,1.045,76,1.349,98,1.735,113,4.378,127,3.699,128,1.108,137,2.439,138,0.522,155,2.414,165,1.671,168,2.669,174,1.7,175,1.293,190,2.103,201,2.793,205,2.414,297,3.41,325,2.043,326,2.742,410,2.653,436,2.565,501,1.939,547,2.927,560,1.939,584,2.321,690,2.24,702,2.816,720,2.678,731,4.923,732,4.224,737,2.043,776,2.103,824,2.321,861,2.103,929,2.523,997,2.24,998,2.831,1013,3.755,1088,2.414,1089,2.653,1106,2.321,1231,3.035,1237,2.653,1252,2.24,1254,3.367,1255,2.414,1256,3.367,1257,3.367,1258,3.035,1259,3.895,1260,2.168,1261,5.199,1262,3.367,1263,3.367,1264,3.367,1265,2.816,1266,3.035,1267,3.367]],["t/189",[18,5.399,30,6.171,31,2.547,36,4.891,514,4.891,933,5.615,1268,7.059,1269,7.831,1270,7.831,1271,7.831]],["t/191",[7,2.125,126,4.215,132,5.267,211,5.443,1272,7.375,1273,8.181]],["t/193",[7,2.358,31,3.287,68,3.258,126,4.245,138,1.278,153,3.627,389,2.661,588,3.466,680,4.292,698,4.153,846,5.084,861,4.029,872,3.196,915,4.834,1037,4.292,1121,4.448,1274,4.834,1275,6.452,1276,5.816,1277,5.816,1278,6.452]],["t/195",[7,2.606,9,2.645,31,1.898,54,1.033,129,2.645,174,2.947,298,2.69,326,3.677,395,2.737,404,5.26,432,5.25,435,3.006,588,3.135,678,5.26,681,3.756,704,3.756,812,4.561,861,3.644,883,4.184,1077,4.023,1119,4.881,1221,3.135,1239,3.447,1279,5.835,1280,6.958,1281,4.598,1282,4.023]],["t/197",[1,1.511,7,2.148,9,2.055,27,5.848,31,1.475,48,2.436,72,1.493,126,2.336,128,1.493,138,1.412,150,2.491,168,1.695,172,3.826,192,3.016,230,2.679,234,4.349,267,2.679,286,3.573,298,3.492,316,2.752,326,2.909,447,3.016,520,3.573,548,2.385,568,3.016,601,3.251,708,3.793,712,2.612,828,5.501,859,2.919,872,3.214,941,3.573,1095,2.021,1110,3.016,1129,4.861,1255,3.251,1283,6.488,1284,3.793,1285,3.251,1286,3.793,1287,4.534,1288,4.534,1289,4.534,1290,4.534,1291,4.534,1292,4.534,1293,4.087,1294,3.793,1295,4.534,1296,4.177,1297,4.534,1298,4.534,1299,4.534]],["t/199",[1,2.166,3,2.681,7,2.366,15,1.611,54,0.87,72,1.761,74,1.66,95,2.275,126,1.801,128,1.761,129,2.946,137,2.055,138,0.83,143,2.183,182,2.055,264,2.814,322,1.965,326,3.729,332,2.25,361,3.341,389,1.442,449,1.732,450,2.924,470,2.924,472,4.128,505,3.151,568,2.325,588,1.878,668,2.506,690,4.324,696,4.892,698,2.25,711,4.216,712,2.013,716,1.965,741,2.25,776,3.341,883,2.506,885,3.559,933,3.836,934,2.619,1025,2.506,1239,3.161,1272,3.151,1274,2.619,1285,2.506,1300,5.437,1301,5.35,1302,2.924,1303,5.35,1304,3.496,1305,3.496,1306,3.496,1307,3.161,1308,3.496,1309,3.496,1310,5.35,1311,5.35,1312,3.496,1313,3.496,1314,3.151,1315,3.496,1316,3.151,1317,3.151,1318,3.496,1319,3.496]],["t/201",[7,1.971,28,4.077,54,1.015,326,2.914,632,5.979,657,5.231,727,5.979,730,3.99,812,4.483,917,4.605,1095,3.383,1196,5.979,1320,7.588]],["t/203",[7,2.079,36,4.997,54,1.07,128,2.634,326,3.073,1214,7.214,1321,8.003,1322,8.003]],["t/205",[7,2.257,31,1.165,43,1.912,54,0.479,94,1.71,97,1.455,137,2.093,138,1.67,141,2.173,143,2.236,153,3.709,165,1.751,168,1.338,169,2.752,266,4.549,294,1.883,298,1.651,322,2.013,326,2.093,327,2.382,343,3.228,362,2.469,407,2.683,422,3.8,426,2.173,472,1.883,519,2.568,550,1.499,568,2.382,577,1.967,593,3.898,624,3.757,672,1.679,696,2.116,731,2.469,933,2.568,986,2.568,1011,2.995,1025,2.568,1031,3.228,1095,1.596,1113,2.469,1123,2.469,1202,3.228,1239,2.116,1274,4.083,1323,2.822,1324,3.581,1325,3.581,1326,3.581,1327,3.581,1328,2.995,1329,3.228,1330,3.581,1331,3.228,1332,3.581,1333,3.581,1334,3.581,1335,3.581,1336,3.581,1337,3.581,1338,3.581,1339,3.228]],["t/207",[7,2.212,15,3.485,31,1.839,54,0.756,124,3.762,125,3.898,126,2.913,129,2.563,138,1.569,139,2.48,153,3.179,165,1.817,266,5.212,454,2.521,550,2.368,577,3.106,624,3.898,698,3.64,786,4.237,875,4.456,933,4.055,975,3.762,1274,4.237,1340,4.73,1341,5.655,1342,4.73,1343,5.097,1344,4.73,1345,8.195,1346,5.097,1347,5.655,1348,3.106]],["t/209",[7,1.621,9,1.947,25,2.607,31,2.39,54,1.233,72,2.942,97,1.746,105,2.765,128,1.414,134,2.607,138,1.251,139,1.884,143,2.682,150,3.427,154,2.415,165,1.38,175,1.649,179,2.213,262,5.79,264,2.259,305,1.947,326,1.649,361,3.896,373,3.593,397,2.857,422,2.474,449,2.128,472,2.259,577,2.359,681,2.765,705,3.593,706,3.872,707,3.384,708,3.593,709,3.872,710,3.593,711,5.79,712,2.474,713,2.607,714,3.872,715,2.961,717,3.08,719,3.218,721,3.218,722,3.593,723,3.872,730,2.259,801,3.872,1274,3.218,1282,2.961,1349,4.295,1350,4.46,1351,3.593,1352,4.295,1353,4.295,1354,4.295,1355,4.295,1356,4.295,1357,4.295,1358,4.295,1359,4.295,1360,4.295]],["t/211",[7,2.513,31,1.96,64,3.658,97,2.45,98,3.105,106,5.658,115,2.778,118,2.985,126,4.062,129,2.732,168,2.252,201,3.238,204,4.009,205,4.321,288,3.561,318,4.155,422,3.471,557,3.044,567,3.88,569,4.786,632,4.749,730,3.17,802,3.238,869,4.321,987,4.516,1057,4.749,1361,4.516,1362,5.041,1363,6.027]],["t/214",[7,1.553,54,0.8,78,3.532,129,2.709,138,0.927,165,1.921,267,3.532,298,2.755,326,3.012,332,5.635,624,4.121,682,6.56,689,7.07,715,6.035,1025,5.624,1239,5.851,1302,5,1350,3.628,1364,5.388,1365,5.978,1366,7.843,1367,5.978,1368,5.978,1369,7.843]],["t/216",[7,2.165,54,0.878,138,1.42,186,6.567,332,4.228,433,2.977,607,4.101,610,3.135,715,4.528,737,3.986,881,5.175,1161,4.918,1370,6.971,1371,6.971,1372,8.334,1373,6.567,1374,6.567,1375,6.567,1376,6.567]],["t/218",[138,1.45,150,4.348,531,3.217,1161,4.253,1377,7.916,1378,7.916,1379,7.916,1380,7.916]],["t/220",[138,1.566,610,3.585,1095,3.348,1381,7.51,1382,7.51,1383,5.178,1384,7.51,1385,7.51,1386,7.51,1387,7.51,1388,7.51]],["t/222",[7,2.199,704,5.45,715,5.836]],["t/224",[1,1.643,7,2.06,31,1.604,54,0.66,95,2.096,96,2.914,115,2.273,129,3.12,137,2.644,138,1.332,139,2.163,143,4.299,145,3.175,160,2.993,165,1.585,179,3.547,182,1.894,291,2.49,298,3.173,322,2.772,350,3.079,395,2.313,415,2.49,417,3.175,433,2.235,506,2.914,550,2.882,567,4.432,568,3.281,610,2.354,672,2.313,715,3.4,924,3.4,1013,2.593,1252,3.281,1300,4.125,1302,4.125,1307,4.686,1383,3.4,1389,3.536,1390,4.125,1391,4.931,1392,4.931,1393,4.445,1394,4.931,1395,4.931,1396,4.931,1397,4.931,1398,4.125,1399,4.931,1400,4.931]],["t/226",[1,2.696,138,1.255,715,5.578,1039,4.977,1307,4.78,1331,7.293]],["t/228",[1,1.474,3,1.825,7,1.149,43,1.282,54,0.853,129,2.005,138,1.671,165,1.422,213,2.614,297,2.32,326,1.699,395,3.505,436,1.59,545,3.082,557,2.235,610,3.043,1307,2.614,1361,4.777,1401,3.989,1402,4.425,1403,4.425,1404,4.425,1405,9.028,1406,4.425,1407,6.375,1408,4.425,1409,4.425,1410,4.425,1411,4.425,1412,4.425,1413,4.425,1414,4.425,1415,3.989,1416,5.747,1417,4.425,1418,4.425,1419,4.425,1420,4.425,1421,4.425,1422,4.425,1423,4.425]],["t/230",[42,4.444,138,1.468,395,3.795,681,5.209,1205,6.768,1424,7.293]],["t/232",[7,1.504,9,2.624,16,2.388,54,1.027,74,1.796,138,1.424,162,3.615,165,1.86,168,2.163,326,2.223,432,3.513,513,3.851,550,3.842,560,4.423,581,3.991,704,5.908,1260,3.727,1281,4.561,1307,5.422,1344,4.842,1425,5.789,1426,5.218,1427,5.218,1428,5.789,1429,5.218,1430,5.789,1431,4.842,1432,5.789,1433,5.789]],["t/234",[7,1.281,31,2.239,42,2.709,54,0.921,76,1.975,115,2.273,116,2.772,126,4.086,137,1.894,177,3.281,192,5.277,200,2.273,284,4.125,289,4.445,294,2.593,305,3.12,326,3.468,331,3.695,395,3.72,476,3.4,569,2.993,693,3.281,698,5.106,704,5.106,712,2.84,716,2.772,729,4.125,731,3.4,732,3.281,733,4.445,924,3.4,990,4.125,1011,4.125,1205,4.125,1239,2.914,1339,4.445,1401,4.445,1424,4.445,1434,4.931,1435,4.931,1436,4.931,1437,4.931,1438,4.931,1439,4.445,1440,4.931,1441,4.931,1442,4.445,1443,4.931]],["t/236",[7,2.613,47,3.682,68,3.754,292,5.793,323,5.858,389,3.066,765,4.945,1119,6.218,1444,7.434]],["t/238",[31,2.575,54,1.059,228,6.622,291,3.998,1130,6.622,1445,7.916,1446,7.136,1447,7.916,1448,6.238]],["t/241",[16,3.252,47,3.906,53,3.252,76,3.159,95,2.562,100,2.11,175,2.314,221,3.311,232,2.93,242,4.516,415,3.982,557,3.982,668,4.321,1000,5.008,1029,3.311,1076,4.321,1163,5.241,1169,5.041,1248,4.749,1250,3.918,1449,6.027,1450,6.027,1451,4.147]],["t/243",[54,0.944,74,1.021,138,1.626,139,1.444,175,2.404,200,1.517,234,1.731,253,2.872,264,2.686,273,2.36,329,2.119,401,2.466,436,1.182,531,3.104,550,2.621,561,2.594,610,2.988,711,6.018,826,2.594,923,2.594,1000,4.808,1005,2.466,1026,1.85,1161,1.768,1448,5.559,1452,4.604,1453,4.604,1454,5.108,1455,3.291,1456,3.291,1457,3.291,1458,3.291,1459,3.291,1460,6.884,1461,3.291,1462,4.604,1463,3.291,1464,3.291,1465,3.291,1466,3.291,1467,3.291,1468,3.291,1469,2.967,1470,2.967,1471,2.967,1472,2.967,1473,2.967,1474,2.967,1475,3.291,1476,3.291,1477,2.594,1478,3.291,1479,3.291,1480,5.108,1481,3.291,1482,4.273,1483,2.967,1484,3.291,1485,5.108,1486,3.291,1487,3.291,1488,3.291,1489,5.559,1490,3.291,1491,2.466,1492,3.291,1493,2.594,1494,3.291]],["t/246",[12,3.587,16,2.369,51,3.104,67,3.229,68,2.9,94,2.742,132,3.697,137,2.206,142,4.303,144,4.804,211,6.336,230,3.393,326,2.933,340,4.118,402,4.62,436,2.063,454,4.246,525,3.155,534,4.303,861,3.587,1140,4.118,1284,4.804,1495,4.526,1496,5.743,1497,5.743,1498,5.177,1499,5.743,1500,5.743,1501,4.804,1502,4.526,1503,4.303]],["t/248",[16,1.408,31,2.526,36,2.132,67,1.919,95,2.234,98,1.759,100,1.84,102,2.81,115,2.422,116,1.919,129,1.547,138,0.53,142,2.558,144,2.856,156,2.886,158,4.124,165,1.097,168,1.964,169,2.654,178,3.078,200,1.574,209,3.383,231,2.072,267,2.017,287,2.448,297,1.243,318,2.354,374,1.966,426,3.189,427,1.834,435,4.226,454,2.343,525,1.875,559,2.823,560,3.026,569,2.072,591,2.448,601,2.448,663,2.856,716,2.954,725,2.017,737,2.072,765,2.271,795,3.078,797,2.448,802,1.834,834,2.558,835,2.017,859,2.198,860,3.189,888,2.448,934,2.558,988,2.69,998,1.522,1000,2.823,1012,3.937,1013,2.763,1029,1.875,1276,3.078,1296,2.198,1340,4.395,1439,3.078,1495,2.69,1504,5.668,1505,3.888,1506,2.69,1507,6.406,1508,3.414,1509,3.414,1510,3.383,1511,3.078,1512,3.414,1513,3.414,1514,3.414,1515,2.69,1516,2.354,1517,3.414,1518,3.414,1519,3.414,1520,5.255,1521,3.414,1522,3.414,1523,3.414,1524,3.414,1525,3.414,1526,3.078,1527,3.414,1528,3.414,1529,3.078,1530,3.414,1531,3.078,1532,2.354]],["t/250",[47,4.128,76,3.338,79,4.921,93,2.669,98,3.383,102,4.016,200,3.027,296,3.528,316,3.986,712,3.783,720,3.383,922,5.494,1495,5.175,1504,5.175,1505,6.163,1515,5.175,1533,5.92,1534,4.924]],["t/252",[1,1.322,47,3.847,54,0.531,68,2.004,71,1.276,97,1.613,100,1.39,102,3.079,112,1.799,130,2.18,149,1.714,153,2.231,165,1.276,168,2.201,174,2.004,175,2.696,182,2.696,214,3.413,288,2.345,292,2.286,297,2.143,314,2.555,402,2.974,432,2.409,435,4.273,458,5.308,488,2.846,531,1.613,560,3.391,588,2.132,695,3.578,697,3.32,712,2.286,730,3.097,744,3.578,823,3.917,853,2.846,863,3.128,932,2.409,997,2.64,998,1.769,1013,2.087,1028,2.846,1029,3.234,1072,4.222,1084,3.391,1221,2.132,1233,2.974,1248,3.128,1250,3.129,1260,2.555,1389,2.846,1495,3.128,1505,5.034,1515,3.128,1534,5.314,1535,2.64,1536,2.736,1537,3.578,1538,3.969,1539,3.969,1540,3.578,1541,3.578,1542,3.578]],["t/254",[1,1.899,54,1.144,76,3.425,97,2.316,150,3.13,200,2.627,221,3.13,267,3.367,292,3.282,296,3.062,329,3.669,371,4.49,389,2.35,402,2.878,415,3.837,448,3.559,778,4.612,782,4.27,885,3.791,997,3.791,998,2.541,1000,4.082,1072,4.086,1106,3.929,1163,3.204,1505,3.459,1534,4.49,1543,7.599,1544,3.669,1545,7.599,1546,5.699,1547,4.767,1548,5.699,1549,4.27,1550,4.49]],["t/256",[13,2.993,47,3.41,53,2.84,54,0.921,74,1.53,100,1.727,112,2.235,119,3.175,153,2.772,165,1.585,169,2.49,175,3.046,230,2.914,253,3.87,402,3.477,415,2.49,427,2.649,431,4.125,435,3.547,445,3.281,510,3.4,550,2.882,647,3.965,778,2.993,802,2.649,998,2.198,1000,2.649,1250,3.069,1260,3.175,1389,6.155,1451,2.593,1504,3.886,1505,4.814,1534,5.336,1535,3.281,1544,3.175,1551,4.931,1552,4.931,1553,3.175,1554,4.445,1555,4.445,1556,4.445,1557,4.931,1558,3.695,1559,3.175]],["t/258",[1,2.205,9,1.631,31,2.152,43,1.585,47,1.783,54,0.481,66,1.783,97,1.463,100,1.916,102,2.4,115,1.659,137,1.382,138,1.147,165,1.156,168,2.763,175,2.101,182,2.101,214,1.75,297,3.556,303,3.416,305,2.479,314,2.317,327,2.394,432,2.184,435,3.409,479,2.836,507,1.933,526,2.696,531,1.463,545,2.256,547,3.86,566,3.244,657,2.481,663,3.01,765,4.402,796,3.151,839,2.481,860,2.184,918,3.244,936,2.836,1012,4.099,1013,3.48,1039,2.877,1050,3.01,1121,2.481,1217,3.01,1221,1.933,1250,2.439,1268,3.244,1361,2.696,1390,3.01,1550,2.836,1560,3.599,1561,3.244,1562,3.599,1563,3.599,1564,3.244,1565,3.244,1566,2.317,1567,5.47,1568,5.47,1569,5.536,1570,4.931,1571,5.47,1572,5.47,1573,3.599,1574,3.599,1575,2.696]],["t/260",[1,1.899,7,1.48,66,2.823,76,3.425,93,2.316,98,2.936,100,1.995,130,3.13,138,1.179,170,4.767,174,2.878,182,2.188,230,3.367,244,4.49,297,3.111,398,4.49,446,3.559,545,3.134,547,3.503,765,5.055,1012,4.27,1029,3.13,1072,5.448,1084,3.282,1091,3.791,1163,4.272,1169,4.767,1233,4.27,1250,2.541,1534,3.367,1570,5.137,1576,5.699,1577,5.699,1578,5.699,1579,5.699]],["t/262",[53,2.007,54,0.651,97,1.978,98,4.057,115,2.243,116,2.735,149,2.101,168,1.818,169,2.457,175,2.619,182,1.869,297,3.761,305,2.205,435,2.507,526,3.646,531,1.978,545,2.813,547,3.144,725,2.875,765,3.237,860,4.139,872,2.41,932,2.953,990,5.705,998,3.041,1013,2.559,1039,2.559,1072,3.489,1082,3.646,1106,3.354,1163,2.735,1221,2.614,1242,4.07,1250,2.169,1259,3.646,1265,5.705,1286,4.07,1323,3.834,1510,3.132,1534,2.875,1541,4.386,1580,4.386,1581,4.866,1582,4.866,1583,4.386,1584,4.866]],["t/266",[54,0.697,95,3.041,128,2.355,137,3.655,143,3.255,153,2.93,168,1.948,212,3.593,267,3.079,283,5.856,319,3.593,427,2.8,476,3.593,550,2.995,571,4.36,842,4.698,998,2.324,1028,3.737,1091,4.759,1095,3.189,1112,5.129,1266,4.698,1350,5.591,1426,4.698,1493,5.637,1531,4.698,1575,3.905,1585,4.698,1586,5.212,1587,5.212,1588,5.212,1589,5.212,1590,4.698,1591,5.212,1592,5.212,1593,5.212,1594,5.129,1595,5.212]],["t/268",[54,0.595,57,2.445,66,2.205,78,2.63,115,2.951,128,2.468,137,2.459,138,1.611,154,4.216,162,2.78,213,2.63,234,2.341,318,3.069,374,2.564,449,2.205,453,3.069,501,2.564,550,3.637,560,2.564,610,3.057,657,3.069,672,3.003,712,2.564,839,3.069,917,2.702,1029,3.517,1091,2.961,1110,2.961,1123,3.069,1150,4.013,1152,4.013,1221,4.029,1255,3.192,1596,7.499,1597,3.724,1598,3.724,1599,4.452,1600,4.013,1601,7.499,1602,4.452,1603,4.452,1604,3.724,1605,4.452,1606,4.452,1607,4.452,1608,4.452,1609,4.452]],["t/270",[118,4.274,137,3.313,138,1.598,610,4.119,1610,6.941,1611,8.628,1612,6.941,1613,8.628,1614,6.941,1615,6.941,1616,6.941,1617,6.941]],["t/272",[138,1.549,550,3.351,1221,4.299,1600,7.214,1618,9.404]],["t/275",[31,2.802,67,3.254,86,3.727,95,2.461,138,1.578,165,1.86,172,2.923,182,2.223,185,4.963,283,4.151,435,4.727,436,2.08,545,2.388,550,2.424,574,4.561,998,3.841,1260,3.727,1361,4.337,1389,5.506,1590,5.218,1619,5.789,1620,6.922,1621,7.678,1622,5.789]],["t/278",[16,2.332,115,3.485,128,2.488,129,3.86,138,1.669,159,3.257,160,3.432,165,1.817,182,2.903,185,3.257,234,4.478,524,4.237,547,2.607,686,5.168,802,3.038,1221,3.038,1623,4.73,1624,7.56,1625,5.655,1626,5.655]],["t/280",[95,2.896,128,2.242,182,2.616,200,3.14,253,3.829,258,5.698,292,3.923,794,4.134,903,5.368,1620,6.14,1627,6.812,1628,6.14,1629,6.14,1630,6.812,1631,6.812,1632,6.812,1633,6.812,1634,6.812,1635,6.812,1636,6.812,1637,6.812,1638,6.812,1639,6.812,1640,6.812]],["t/282",[31,2.893,54,0.827,57,4.402,67,3.474,115,2.848,126,3.183,128,2.638,137,3.416,154,3.474,168,2.309,197,5.169,319,4.26,502,5.169,725,4.735,730,4.215,731,4.26,802,3.32,1516,4.26,1641,4.63,1642,5.57,1643,6.179,1644,6.179,1645,5.57,1646,6.179,1647,6.179,1648,6.179,1649,6.179]],["t/284",[0,4.146,7,2.093,13,4.891,31,2.027,54,1.078,66,3.087,100,2.182,137,3.095,139,2.734,152,8.051,294,3.277,306,4.911,322,3.504,374,3.589,550,2.609,731,4.296,765,4.146,1013,3.277,1084,4.642,1285,4.469,1604,5.213,1641,4.669,1650,5.618,1651,5.618,1652,5.618,1653,5.213,1654,5.618]],["t/286",[3,2.246,15,2.51,16,2.246,18,5.081,21,6.583,23,3.304,45,2.805,46,4.29,49,4.29,51,2.213,52,3.217,57,2.991,58,3.904,93,2.213,96,3.217,103,4.274,104,3.753,108,3.299,138,0.845,139,2.388,168,2.035,384,4.079,454,2.427,559,2.925,656,2.697,803,2.749,806,4.554,807,3.753,809,4.908,1506,4.29,1597,4.554,1623,4.554,1655,4.908,1656,4.908,1657,4.079,1658,7.369,1659,3.753,1660,4.554,1661,5.444,1662,7.369,1663,5.444]],["t/288",[4,2.646,76,3.014,95,1.904,135,2.646,138,1.532,175,2.47,214,2.177,297,1.63,389,1.847,394,2.883,435,3.313,436,1.609,448,2.797,478,3.211,531,3.342,550,1.875,610,3.07,778,2.718,870,3.211,1000,2.406,1005,3.356,1026,2.518,1029,2.46,1161,2.406,1163,3.615,1448,6.48,1452,4.037,1453,4.037,1460,7.413,1469,4.037,1470,4.037,1471,4.037,1472,4.037,1473,4.037,1474,4.037,1477,3.529,1482,3.747,1483,4.037,1505,3.903,1544,4.14,1547,3.747,1664,4.037,1665,4.037,1666,3.747,1667,4.037,1668,4.479,1669,4.479,1670,3.747,1671,4.479,1672,4.479,1673,4.479,1674,4.479,1675,4.479,1676,4.479]],["t/290",[54,1.196,71,2.365,129,3.335,158,4.738,427,3.954,1030,5.799,1039,3.87,1163,5.028,1482,6.156,1489,5.799,1549,6.702,1677,7.359,1678,7.359]],["t/292",[3,2.758,9,3.031,54,0.895,76,2.679,130,3.673,182,2.568,221,4.631,267,3.951,288,3.951,297,2.434,346,3.852,350,4.176,410,5.27,531,3.426,548,3.517,560,3.852,935,6.028,1039,3.517,1351,5.594,1544,4.305,1679,6.028,1680,6.028,1681,6.687,1682,6.687]],["t/294",[54,0.903,72,2.222,135,3.987,138,1.554,175,2.592,182,2.592,218,6.079,234,3.549,402,3.408,436,2.425,531,2.743,547,3.111,647,3.887,867,5.318,1544,4.345,1670,5.645,1683,5.318,1684,6.749,1685,6.749,1686,6.749]],["t/296",[9,2.777,50,2.605,72,2.624,74,1.901,128,2.624,156,3.366,175,2.353,182,3.061,267,3.62,288,3.62,297,2.23,303,4.978,309,4.829,362,4.225,432,4.838,547,3.674,647,4.591,778,4.838,1000,3.292,1084,3.529,1501,5.126,1505,3.719,1544,5.132,1665,5.524,1670,5.126,1687,6.128,1688,6.128]],["t/298",[54,1.102,67,3.627,95,2.743,117,5.908,130,3.544,175,3.487,182,2.478,221,4.987,272,4.153,329,4.153,380,4.292,531,2.622,679,3.916,686,3.916,1163,3.627,1489,7.154,1544,4.153,1547,7.594,1679,5.816,1689,6.452]],["t/300",[74,2.404,150,4.256,175,2.976,567,4.988,1000,4.163,1477,6.106,1537,8.317,1544,4.988,1550,6.106,1690,6.985]],["t/303",[1,2.231,31,1.542,53,3.202,54,0.634,64,4.064,86,3.051,126,2.442,137,1.82,138,1.551,139,2.079,154,3.764,168,1.771,182,2.571,214,2.304,297,2.826,531,1.926,545,2.762,547,3.086,550,1.984,610,3.196,737,2.877,793,3.735,802,2.546,804,3.153,1026,2.665,1039,3.521,1161,2.546,1224,4.273,1250,3.967,1691,7.764,1692,5.017,1693,3.965,1694,8.436,1695,6.696,1696,4.74,1697,3.268,1698,4.74,1699,4.74,1700,4.74]],["t/305",[51,2.133,53,3.381,54,0.702,102,2.303,127,2.073,137,2.016,138,1.543,139,2.303,168,1.962,169,2.651,322,2.951,436,1.886,525,2.884,730,2.761,734,3.278,804,3.492,861,3.278,1026,2.951,1083,4.391,1250,4.354,1566,5.278,1693,7.978,1701,4.732,1702,7.189,1703,5.249,1704,4.732,1705,5.249,1706,6.013,1707,6.013,1708,5.249,1709,5.249]],["t/307",[1,1.914,8,3.02,15,2.647,51,2.334,57,4.196,79,4.303,88,4.303,96,3.393,100,2.011,115,2.647,116,3.229,118,4.531,131,3.229,149,3.95,153,3.229,155,4.118,168,2.146,175,2.933,179,3.935,184,3.086,221,3.155,446,3.587,507,3.086,531,2.334,588,3.086,782,4.303,835,3.393,872,2.845,996,4.303,1114,4.804,1505,5.209,1710,5.743,1711,5.177]],["t/309",[43,1.532,86,3.404,95,2.248,100,1.851,118,4.587,127,2.088,130,2.904,139,2.319,149,2.283,175,2.03,179,3.722,214,2.57,221,2.904,291,2.67,297,1.924,306,4.166,331,3.962,389,2.98,454,2.357,607,3.302,608,3.404,697,6.043,713,4.384,732,3.517,741,3.404,793,4.166,872,4.587,876,4.98,932,4.384,957,3.645,998,2.357,1108,3.962,1462,6.512,1566,3.404,1580,4.766,1712,4.766,1713,5.287,1714,5.287,1715,4.766,1716,4.766]],["t/311",[1,1.712,8,2.702,31,1.671,35,3.209,43,1.489,47,2.546,54,0.687,69,5.582,72,1.692,86,3.308,97,2.089,100,1.799,128,2.669,138,1.47,149,3.059,175,1.973,179,4.177,214,2.498,446,3.209,518,3.85,519,5.08,548,2.702,671,3.685,749,5.308,1120,4.049,1534,3.036,1628,4.632,1711,7.308,1717,5.139,1718,5.139,1719,7.084,1720,5.139,1721,5.139,1722,5.139,1723,7.084,1724,5.139,1725,5.139,1726,5.139,1727,5.139,1728,3.85,1729,5.139,1730,5.139]],["t/313",[9,2.094,43,2.553,47,2.289,50,3.254,54,0.88,68,2.333,88,3.462,96,2.73,97,1.878,98,2.38,100,2.302,118,3.792,119,2.974,131,2.597,147,3.185,149,2.839,175,1.774,179,4.719,244,5.181,427,4.113,446,4.106,472,2.43,507,2.482,563,3.074,671,3.313,672,2.167,720,2.38,749,3.462,752,3.865,778,4.646,782,3.462,802,2.482,869,3.313,876,4.533,1282,3.185,1491,3.462,1629,4.165,1731,4.62,1732,4.62,1733,4.62,1734,5.927,1735,6.575,1736,4.62,1737,4.62,1738,4.62,1739,4.62,1740,4.62,1741,4.62,1742,4.62,1743,4.165]],["t/315",[16,1.761,38,4.655,54,0.831,95,4.153,115,2.864,126,2.2,135,2.523,138,0.964,153,2.401,156,2.346,168,2.322,182,1.64,185,2.459,202,4.655,205,3.062,217,4.655,221,3.413,253,4.521,277,2.944,288,2.523,298,2.864,435,3.201,449,3.078,559,2.294,588,2.294,601,3.062,799,3.88,1028,4.455,1029,2.346,1062,5.487,1140,3.062,1209,3.572,1221,2.294,1250,1.904,1252,2.841,1296,2.749,1350,2.592,1493,3.365,1526,3.849,1532,4.283,1534,4.752,1535,2.841,1536,2.944,1594,4.455,1744,4.27,1745,3.365,1746,4.27,1747,4.27,1748,4.27,1749,4.27,1750,4.27,1751,3.849,1752,3.572,1753,4.27,1754,4.27,1755,4.27]],["t/318",[31,3.075,45,4.71,51,2.865,54,1.165,85,4.021,94,3.366,102,2.239,105,3.285,159,2.939,160,4.279,168,1.907,207,4.86,297,2.566,307,3.518,436,2.533,448,3.187,449,2.528,454,3.143,472,2.684,524,3.824,525,3.873,713,4.279,720,2.629,844,4.6,846,4.021,924,3.518,987,3.824,1123,3.518,1196,4.021,1222,4.6,1314,6.355,1348,2.803,1516,3.518,1756,4.6,1757,4.6,1758,5.103,1759,3.824,1760,5.103]],["t/320",[3,1.499,54,0.737,72,2.446,74,1.127,100,1.273,138,1.598,139,2.418,165,1.168,182,2.117,219,2.723,226,4.731,234,1.911,268,2.723,290,2.506,297,1.323,435,1.872,545,1.499,547,3.069,550,1.522,557,1.835,786,2.723,835,2.147,839,2.506,851,4.6,934,2.723,998,2.457,1003,3.04,1039,2.898,1057,2.864,1195,3.04,1221,1.953,1293,3.276,1361,2.723,1641,2.723,1697,3.8,1761,6.882,1762,3.634,1763,3.276,1764,3.634,1765,3.634,1766,3.634,1767,3.634,1768,6.681,1769,2.723,1770,5.512,1771,3.634,1772,2.606,1773,6.658,1774,2.723,1775,3.276,1776,3.634,1777,3.634,1778,5.512,1779,3.634,1780,3.634,1781,3.634,1782,3.634,1783,3.634,1784,3.634,1785,3.634,1786,3.634,1787,3.634,1788,3.634]],["t/322",[129,3.981,139,3.134,226,5.002,333,5.629,799,4.461,1095,4.24,1106,6.055,1110,4.753,1761,5.629,1789,7.144,1790,5.353,1791,7.144,1792,7.144]],["t/324",[234,4.118,436,2.813,507,4.207,932,4.753,1759,5.868,1761,7.316,1768,6.551,1793,7.059,1794,7.831]],["t/326",[16,2.02,54,0.655,71,1.574,95,2.082,102,4.204,115,3.159,137,1.881,165,1.574,168,2.954,172,2.474,185,2.821,200,2.258,297,3.559,305,3.106,332,3.153,380,3.259,394,3.153,400,3.86,402,2.474,432,2.973,454,2.184,545,2.02,560,2.821,633,4.097,745,4.415,796,2.821,860,2.973,887,3.86,917,2.973,992,4.415,998,2.184,1037,3.259,1239,4.048,1250,2.184,1252,3.259,1259,3.67,1260,3.153,1285,3.512,1317,4.415,1515,5.4,1536,4.724,1565,4.415,1795,4.898,1796,4.898,1797,4.415,1798,4.415]],["t/328",[1,1.762,16,2.181,50,2.248,71,2.644,76,2.894,100,1.851,138,1.6,168,1.976,182,2.03,185,3.045,294,2.781,297,2.995,545,3.648,547,3.33,672,3.388,732,3.517,860,4.384,1183,4.423,1239,3.124,1250,3.221,1534,4.268,1745,5.692,1799,4.766,1800,4.766,1801,8.229,1802,5.287,1803,5.287,1804,4.423,1805,5.287]],["t/330",[28,4.329,43,1.806,51,3.275,52,3.682,67,3.504,72,2.051,97,2.533,138,1.554,182,3.095,232,3.03,292,3.589,297,2.268,479,4.911,547,2.873,1221,3.348,1239,5.277,1252,4.146,1774,6.038,1799,5.618,1806,6.232,1807,5.213,1808,6.232,1809,6.232]],["t/332",[138,1.633,182,2.393,545,3.324,547,2.873,1026,3.504,1039,3.277,1161,3.348,1221,3.348,1239,3.682,1250,2.778,1697,4.296,1772,4.469,1774,4.669,1804,5.213,1807,5.213,1810,6.232,1811,8.931,1812,6.232,1813,5.618,1814,6.232,1815,6.232,1816,6.232]],["t/334",[97,2.246,138,1.625,168,2.065,182,2.122,545,3.472,547,2.548,720,2.847,823,3.677,998,2.464,1026,3.107,1029,3.036,1039,2.906,1161,2.969,1183,4.623,1221,2.969,1233,4.141,1250,2.464,1697,3.81,1772,3.963,1800,4.982,1804,4.623,1807,4.623,1813,4.982,1817,5.527,1818,5.527,1819,5.527,1820,5.527,1821,8.418,1822,5.527,1823,5.527,1824,5.527,1825,5.527,1826,5.527,1827,5.527]],["t/336",[1,1.235,31,2.192,54,1.004,72,1.221,76,2.242,116,2.085,117,4.012,138,0.868,149,1.601,175,2.149,201,1.992,218,2.659,221,3.702,226,1.95,232,2.72,234,2.943,267,2.191,268,2.778,297,1.35,305,1.681,318,2.556,346,3.882,360,2.556,371,4.409,415,1.873,531,3.051,548,3.544,608,2.387,680,3.723,785,2.922,803,2.826,851,2.136,922,3.102,998,1.653,1000,1.992,1095,4.209,1110,6.163,1140,2.659,1182,2.778,1195,3.102,1248,6.349,1503,2.778,1561,6.075,1566,3.602,1575,2.778,1680,3.342,1728,2.778,1828,3.342,1829,3.708,1830,3.708,1831,5.596,1832,3.708,1833,3.708,1834,3.708,1835,3.708,1836,3.708,1837,3.708,1838,3.708,1839,5.596,1840,3.708,1841,2.922,1842,3.708,1843,5.596,1844,3.708]],["t/338",[138,1.572,165,2.092,305,2.95,433,2.95,436,3.275,686,3.95,725,3.846,839,4.487,851,3.749,929,4.877,1094,6.53,1095,4.279,1112,5.942,1845,5.445,1846,6.509,1847,6.509,1848,6.509]],["t/340",[13,2.358,54,0.52,72,1.908,74,1.205,95,1.652,97,1.579,115,1.791,128,1.279,138,1.567,139,1.704,165,1.249,182,2.225,213,2.295,222,4.566,226,3.048,234,3.048,285,3.061,298,1.791,433,1.761,435,2.001,436,1.396,448,2.426,531,1.579,547,2.671,550,1.627,610,1.855,799,2.426,805,3.25,835,2.295,839,3.995,851,4.426,998,2.583,1000,2.087,1085,3.502,1095,2.583,1110,6.242,1112,2.786,1140,4.155,1221,2.087,1566,2.501,1697,2.678,1728,2.911,1768,6.876,1772,4.155,1775,3.502,1849,3.502,1850,3.885,1851,3.885,1852,3.885,1853,3.885,1854,3.885,1855,7.684,1856,3.885,1857,3.885,1858,3.885,1859,3.885,1860,3.885,1861,3.885,1862,3.885,1863,3.885,1864,3.885,1865,3.885]],["t/342",[1,1.193,3,2.248,16,3.042,54,0.729,67,2.013,86,2.305,128,1.179,137,1.375,138,1.496,156,1.967,165,1.151,172,1.808,175,1.375,202,4.083,234,1.883,277,3.757,290,2.469,436,2.37,447,4.389,510,3.757,513,2.382,518,4.083,531,3.866,647,2.063,686,5.854,730,1.883,732,2.382,872,3.268,1037,2.382,1070,2.382,1095,1.596,1110,2.382,1129,6.71,1157,4.913,1489,5.199,1493,4.295,1558,2.683,1583,3.228,1598,2.995,1866,3.581,1867,5.45,1868,5.45,1869,3.581,1870,3.581,1871,6.598,1872,5.947,1873,6.598,1874,3.228,1875,3.581,1876,5.45,1877,4.559,1878,5.45]],["t/344",[1,1.199,28,1.933,47,3.278,53,2.256,54,0.885,66,1.783,72,1.185,75,3.771,95,1.53,98,1.854,100,1.26,130,3.005,138,1.516,165,1.156,182,1.382,184,1.933,230,2.126,253,4.156,432,2.184,435,3.809,448,2.247,506,2.126,526,2.696,531,1.463,547,2.522,550,1.507,610,2.612,647,4.258,720,1.854,778,2.184,794,2.184,863,2.836,998,2.439,1000,1.933,1026,2.023,1039,1.892,1091,2.394,1161,1.933,1221,1.933,1280,4.931,1342,3.01,1451,3.48,1505,4.826,1534,5.299,1535,2.394,1550,2.836,1558,4.099,1690,3.244,1697,2.481,1772,2.58,1774,4.099,1797,3.244,1798,3.244,1879,3.244,1880,3.01,1881,3.244,1882,3.599,1883,5.47,1884,5.47,1885,5.47,1886,3.599,1887,3.244,1888,3.244,1889,3.599,1890,5.47,1891,3.599,1892,3.599,1893,3.599,1894,3.599]],["t/346",[31,2.301,42,3.886,43,2.53,51,2.875,54,0.946,121,4.877,132,4.555,184,3.801,201,3.801,207,4.877,210,5.918,297,2.575,320,4.877,322,3.977,1088,5.073,1121,4.877,1225,5.918,1895,6.377,1896,5.918]],["t/348",[16,1.968,42,2.621,54,1.193,74,1.48,86,3.071,128,2.214,138,1.384,165,1.533,172,2.409,189,3.759,201,2.563,214,2.319,297,3.643,394,5.016,428,3.991,435,2.458,526,3.574,545,3.213,547,3.9,730,2.509,796,2.748,997,4.475,1013,3.537,1057,3.759,1077,3.289,1088,3.421,1091,3.174,1250,2.127,1585,6.063,1623,3.991,1896,6.517,1897,4.3,1898,4.771,1899,6.063,1900,4.3,1901,4.771,1902,4.3,1903,4.771,1904,4.3]],["t/350",[1,1.301,43,2.664,50,1.66,51,1.587,53,1.611,54,0.522,74,1.212,76,1.565,102,1.713,128,1.286,138,1.525,158,2.514,162,2.439,165,1.255,207,2.693,263,3.267,297,3.256,319,2.693,362,2.693,394,2.514,435,2.012,449,1.935,545,2.4,547,4.474,730,2.054,799,3.633,920,3.267,957,2.693,1088,6.415,1221,2.098,1250,1.741,1362,4.867,1431,3.267,1535,2.598,1569,4.867,1896,4.867,1897,5.244,1899,5.244,1902,3.521,1904,3.521,1905,3.906,1906,3.906,1907,3.267,1908,3.906,1909,3.906,1910,3.906,1911,3.906,1912,3.906,1913,3.906,1914,3.906,1915,3.906,1916,3.906,1917,3.906,1918,3.906,1919,3.906,1920,3.521,1921,3.521]],["t/352",[31,2.531,54,0.965,100,1.201,102,3.975,124,3.508,138,1.435,162,2.142,168,1.282,213,2.027,230,2.027,232,3.123,286,4.155,287,2.459,297,3.215,305,1.555,374,1.976,419,2.703,423,4.411,435,2.717,436,1.232,475,2.869,476,2.365,525,1.884,531,2.931,545,3.389,633,4.411,638,5.665,667,2.57,672,1.609,683,2.57,713,2.082,730,2.773,732,2.282,776,2.142,810,2.869,816,2.365,861,4.012,987,2.57,1025,2.459,1037,3.508,1043,3.092,1076,2.459,1084,1.976,1087,2.703,1239,3.796,1265,2.869,1371,2.869,1569,6.032,1874,3.092,1920,4.754,1921,4.754,1922,3.092,1923,3.43,1924,3.43,1925,5.273,1926,5.273,1927,5.273,1928,3.092,1929,3.43,1930,3.43,1931,3.43,1932,3.43,1933,3.43,1934,3.43,1935,3.43]],["t/355",[50,2.315,54,0.986,69,4.29,100,1.906,127,2.15,135,4.354,138,1.143,146,4.554,168,3.122,172,2.749,191,4.29,218,3.904,360,3.753,362,3.753,454,2.427,568,3.622,569,4.473,574,4.29,608,3.505,707,4.29,823,3.622,824,6.171,828,3.622,831,5.284,832,4.908,833,4.908,834,4.079,835,3.217,836,4.908,837,4.908,838,4.079,860,3.304,1076,3.904,1198,4.908,1252,3.622,1282,3.753,1666,4.554,1712,4.908,1936,5.444,1937,5.444,1938,5.444]],["t/357",[4,2.295,7,1.505,15,1.791,23,2.358,31,2.254,51,3.123,52,2.295,68,1.962,94,1.855,97,1.579,126,2.985,135,2.295,138,1.075,165,1.249,168,1.452,192,2.584,290,4.778,292,2.238,307,3.995,308,2.584,374,2.238,394,4.462,401,2.911,413,2.911,433,1.761,453,3.995,454,3.843,472,2.043,502,3.25,503,3.061,545,1.602,548,2.043,577,3.183,672,1.822,698,2.501,713,2.358,802,2.087,818,2.678,824,2.678,828,3.855,831,2.786,835,2.295,838,4.342,846,3.061,848,3.502,849,6.055,850,3.502,851,2.238,852,3.502,853,2.786,854,3.502,855,3.25,856,3.502,857,3.502,858,3.502,859,2.501,860,4.206,861,2.426,862,3.502,863,3.061,864,3.502,865,4.847,866,3.502,867,4.566,868,2.786,869,4.155,870,2.786,871,3.25,1005,2.911,1050,3.25,1346,3.502,1769,4.342,1939,3.885]],["t/359",[3,3.035,23,5.429,25,4.466,94,3.513,267,4.348,292,4.239,350,4.596,374,4.239,449,3.645,872,3.645,873,6.156,874,6.156,875,5.799,876,5.074,877,6.634]],["t/361",[7,1.912,9,3.335,12,4.596,31,2.909,68,3.716,391,6.634,447,4.896,861,4.596,1013,3.87,1277,6.634,1323,5.799,1506,5.799,1536,5.074,1716,6.634,1940,7.359]],["t/364",[7,2.391,31,1.898,54,1.033,60,4.598,68,2.947,100,2.043,126,3.006,128,1.921,137,2.241,162,3.644,172,2.947,212,4.023,287,4.184,288,3.447,305,2.645,313,4.881,319,4.023,454,2.601,525,4.24,560,3.361,661,5.26,802,3.135,812,3.447,872,2.89,963,5.26,997,3.882,1029,3.205,1121,4.023,1223,5.26,1281,4.598,1285,4.184,1427,5.26,1429,5.26,1575,4.372,1650,5.26,1941,5.835,1942,5.26]],["t/366",[7,2.289,31,1.342,68,2.083,74,1.28,106,2.504,128,2.363,129,1.87,138,1.66,150,2.266,151,3.719,153,4.448,170,3.451,185,3.49,231,2.504,294,2.17,326,1.584,417,2.656,426,2.504,501,2.376,525,2.266,550,1.727,557,2.083,593,2.438,610,1.97,680,2.745,681,2.656,725,2.438,749,4.54,812,2.438,816,2.844,885,2.745,915,3.091,1026,2.319,1123,2.844,1250,1.839,1329,3.719,1343,5.461,1416,3.719,1594,2.958,1752,3.451,1943,6.059,1944,4.126,1945,3.451,1946,3.719,1947,4.126,1948,4.126,1949,3.719,1950,4.126,1951,3.719,1952,3.719,1953,4.126,1954,4.126,1955,4.126,1956,4.126]],["t/368",[7,2.369,9,3.875,29,4.49,54,0.762,66,2.823,72,2.814,74,1.768,100,2.661,102,2.5,108,2.25,128,1.876,137,2.188,168,2.13,181,3.459,200,2.627,222,4.49,253,3.204,268,4.27,350,3.559,430,5.137,545,2.35,588,3.062,796,3.282,823,5.055,996,4.27,1068,6.356,1076,4.086,1083,4.767,1957,5.699,1958,5.699,1959,5.699,1960,5.137,1961,5.699,1962,4.767,1963,5.699]],["t/370",[3,1.054,4,1.51,7,2.298,31,0.831,54,0.342,64,1.551,72,2.012,79,1.915,97,1.039,98,2.151,100,0.895,106,4.998,130,1.404,135,2.467,137,2.347,138,1.642,139,1.121,145,5.583,153,4.289,154,2.348,172,2.674,177,3.522,179,2.151,294,2.784,305,2.4,360,2.879,374,1.472,377,1.915,398,2.014,426,1.551,456,1.833,525,2.294,531,1.039,557,3.087,567,1.646,593,2.467,607,1.596,610,3.217,679,2.534,727,2.014,730,1.344,765,1.7,812,1.51,841,2.138,917,1.551,925,2.304,932,1.551,986,1.833,1062,1.915,1070,1.7,1091,1.7,1095,1.14,1121,2.879,1259,1.915,1307,3.612,1328,3.493,1350,1.551,1446,2.304,1532,1.762,1533,2.304,1535,1.7,1598,2.138,1604,2.138,1641,1.915,1651,2.304,1652,2.304,1653,2.138,1654,2.304,1701,2.304,1881,3.764,1964,2.556,1965,2.556,1966,2.556,1967,2.304,1968,2.138,1969,2.556,1970,2.556,1971,2.556,1972,7.231,1973,2.556,1974,2.304,1975,2.556,1976,4.176]],["t/373",[7,1.469,137,2.172,138,1.631,150,4.153,154,3.179,179,3.894,201,3.038,277,3.898,322,3.179,399,4.055,531,2.298,550,2.368,557,2.856,610,2.699,672,2.652,698,3.64,776,3.531,972,5.097,1252,3.762,1260,3.64,1282,3.898,1790,5.664,1841,5.957,1977,4.456,1978,4.73,1979,5.655,1980,4.73,1981,5.097,1982,4.73,1983,5.097,1984,5.097]],["t/375",[3,2.344,7,1.772,54,1.184,126,1.95,137,2.913,138,1.581,150,3.748,154,3.195,162,2.363,172,1.911,175,1.453,179,1.95,214,1.84,232,1.84,322,2.128,333,2.982,447,2.518,550,1.585,610,1.807,712,3.93,731,2.609,823,5.679,1026,4.978,1095,3.042,1161,3.666,1296,3.658,1370,3.166,1516,2.609,1594,2.714,1841,4.478,1845,4.753,1922,3.411,1945,3.166,1962,3.166,1977,5.976,1978,3.166,1980,3.166,1982,3.166,1983,3.411,1984,5.122,1985,3.785,1986,6.823,1987,3.785,1988,7.584,1989,3.411,1990,3.785,1991,3.785,1992,3.785,1993,3.785,1994,3.785]],["t/377",[7,1.208,31,1.512,137,2.537,138,1.654,150,5.042,179,3.403,531,3.122,607,2.903,608,2.993,610,2.22,1026,4.967,1095,4.209,1161,3.549,1184,4.191,1841,6.591,1977,3.664,1978,3.889,1980,3.889,1981,4.191,1982,6.426,1989,4.191,1995,4.65,1996,4.65,1997,4.65,1998,3.664,1999,4.65,2000,4.65,2001,4.65,2002,4.65]],["t/379",[1,3.056,7,1.89,8,2.22,17,2.91,38,3.162,51,1.715,66,2.091,96,4.298,102,2.702,120,2.268,129,2.792,138,1.423,182,2.366,184,2.268,216,3.326,219,3.162,297,2.242,308,2.808,322,2.373,325,2.562,326,1.621,346,2.431,351,3.326,402,2.132,507,2.268,518,3.162,548,2.22,550,1.767,559,2.268,607,2.636,610,2.015,785,3.326,957,4.247,998,1.882,1039,2.22,1092,4.247,1161,2.268,1163,2.373,1255,3.026,1307,4.298,1383,2.91,1566,2.717,1704,3.805,1715,3.805,1880,3.531,2003,4.221,2004,4.221,2005,4.221,2006,2.562,2007,4.221,2008,3.531,2009,4.221,2010,5.153,2011,3.805,2012,5.553,2013,4.221,2014,3.531,2015,4.221,2016,6.161,2017,6.161,2018,4.221,2019,4.221]],["t/381",[1,1.718,7,1.34,25,1.494,31,0.801,54,1.004,71,1.301,74,1.854,95,1.721,100,1.418,102,1.08,105,1.585,106,3.627,107,1.844,115,1.135,127,0.972,129,1.116,137,1.981,138,1.639,139,1.08,142,1.844,154,2.899,171,1.765,172,1.243,182,0.945,200,1.135,207,1.697,209,1.585,297,1.474,298,1.135,407,1.844,525,1.352,531,1,550,2.159,557,2.045,560,1.418,567,1.585,610,3.153,690,1.638,703,2.059,741,1.585,797,1.765,874,2.059,886,1.765,924,1.697,929,1.844,998,2.299,1013,1.295,1026,2.276,1039,1.295,1070,1.638,1092,1.697,1161,3.21,1163,1.384,1250,2.299,1255,1.765,1307,5.064,1328,2.059,1345,3.65,1383,2.791,1393,2.219,1415,4.649,1532,1.697,1555,2.219,1566,1.585,1942,2.219,1960,4.649,2010,2.059,2014,2.059,2020,2.219,2021,2.462,2022,2.462,2023,2.462,2024,4.049,2025,4.049,2026,5.158,2027,2.462,2028,2.462,2029,2.462,2030,2.462,2031,2.462,2032,2.219,2033,4.049,2034,4.049,2035,3.65,2036,2.462,2037,2.462,2038,2.462,2039,2.219,2040,2.462,2041,2.219,2042,2.219,2043,2.219,2044,2.462,2045,2.219,2046,4.049,2047,2.462,2048,5.158,2049,2.462,2050,2.462,2051,4.049,2052,2.462,2053,2.462,2054,2.462]],["t/383",[7,2.553,47,3.141,53,3.714,54,1.09,102,2.781,120,3.406,213,3.746,291,3.202,326,2.435,548,3.334,557,3.202,812,4.814,924,4.371,955,4.082,998,2.826,1307,4.814,1323,4.996,1451,4.735,2006,3.848,2055,5.715,2056,4.546]],["t/385",[1,1.278,3,1.583,7,2.404,9,1.045,13,2.329,15,1.063,17,1.59,31,0.75,35,1.44,47,3.157,48,2.061,50,0.981,53,1.583,54,0.976,66,1.143,72,1.89,74,1.528,94,1.101,96,1.363,108,0.911,120,3.696,137,1.892,138,1.451,139,1.012,148,3.459,149,0.996,150,1.267,159,1.329,160,1.4,174,1.165,175,1.892,232,1.121,294,1.213,296,1.239,297,1.793,298,1.063,305,1.045,309,1.818,341,1.929,360,1.59,409,1.728,424,1.929,436,1.378,443,2.875,447,1.534,449,1.143,451,1.654,472,1.213,478,1.654,510,1.59,545,1.583,557,1.165,568,1.534,591,4.117,608,1.485,647,2.837,654,1.818,668,1.654,671,1.654,685,2.837,712,1.329,818,1.59,957,1.59,1013,1.213,1040,2.079,1070,1.534,1089,1.818,1092,1.59,1161,2.647,1182,2.875,1284,1.929,1350,1.4,1351,1.929,1383,1.59,1451,3.352,1503,1.728,1504,1.818,1505,2.329,1510,1.485,1529,2.079,1575,1.728,1594,1.654,1745,5.421,1790,1.728,1828,2.079,1880,1.929,1887,3.459,1888,4.441,1952,2.079,1977,1.818,2006,2.329,2010,1.929,2012,3.459,2014,1.929,2032,2.079,2039,2.079,2041,2.079,2042,2.079,2056,2.751,2057,4.776,2058,2.307,2059,1.485,2060,2.307,2061,2.307,2062,3.837,2063,3.837,2064,2.307,2065,2.875,2066,3.837,2067,3.171,2068,2.307,2069,3.837,2070,3.837,2071,2.307,2072,2.307,2073,2.307,2074,2.307,2075,2.307,2076,2.307,2077,2.079,2078,4.524,2079,2.307,2080,2.307,2081,2.307,2082,2.307]],["t/387",[7,2.481,54,0.965,102,3.165,172,3.643,253,4.056,443,5.405,647,4.155,778,5.363,812,4.262,1084,4.155,1389,5.173,1451,3.794,1503,5.405,1505,4.378,1544,4.644]],["t/389",[7,1.769,45,3.509,54,1.141,86,5.49,97,2.768,102,3.741,216,5.368,326,2.616,389,2.81,443,6.39,1072,4.884,1233,6.39,1307,4.025,1350,4.134,1928,6.14,1946,6.14,2035,6.14,2083,6.14,2084,6.14]],["t/391",[7,2.165,54,1.115,76,3.338,95,2.792,97,2.669,126,3.383,155,4.709,221,3.608,389,2.709,432,3.986,448,4.101,588,3.528,778,3.986,812,3.88,860,3.986,1029,3.608,1070,4.369,1084,3.783,1121,5.745,1163,3.692,1556,5.92,2083,5.92,2085,6.567,2086,5.92]],["t/393",[3,1.981,4,3.992,7,2.323,50,2.041,51,1.952,52,2.837,66,2.379,71,1.543,76,3.582,93,3.178,95,2.041,98,2.474,100,1.681,112,2.176,150,2.638,172,2.425,177,3.194,187,2.914,297,1.748,309,3.784,326,2.595,336,3.784,340,3.443,389,1.981,447,3.194,507,3.63,557,2.425,588,2.58,647,2.766,725,2.837,796,2.766,805,4.017,838,3.598,997,3.194,1013,2.525,1028,4.845,1095,2.141,1118,5.325,1163,3.799,1250,2.141,1281,3.784,1294,4.017,1307,2.837,1389,4.845,1451,2.525,1645,4.329,1692,3.598,2006,2.914,2057,3.598,2087,4.802,2088,4.802,2089,4.802]],["t/395",[7,2.102,121,5.578,124,5.383,125,5.578,473,7.293,561,6.376,2090,8.091]],["t/397",[1,2.094,128,2.069,137,2.414,184,3.377,308,4.181,320,4.333,362,4.333,366,5.666,402,3.174,509,3.453,853,4.507,859,4.047,1250,2.802,1653,5.258,2008,5.258,2091,5.666,2092,6.778,2093,5.81,2094,7.465,2095,5.666,2096,5.666,2097,6.286,2098,6.286,2099,6.286,2100,5.258,2101,6.286,2102,6.286,2103,5.666]],["t/399",[15,1.991,16,1.782,32,2.488,44,2.698,47,2.14,54,0.578,68,2.182,71,1.388,72,1.422,92,2.781,94,2.063,97,1.756,101,3.295,108,2.912,120,2.321,128,2.063,138,1.583,139,1.895,226,3.295,325,2.622,327,2.874,417,2.781,545,2.584,593,2.553,672,2.026,757,3.237,762,5.799,772,4.32,799,2.698,807,4.32,2093,5.286,2104,5.648,2105,5.648,2106,4.32,2107,3.894,2108,3.614,2109,4.168,2110,5.648,2111,6.266,2112,6.266,2113,6.266,2114,5.648,2115,6.266,2116,6.266,2117,5.648,2118,5.241,2119,5.648]],["t/401",[1,1.199,3,2.256,15,1.659,16,2.256,43,1.043,44,2.247,45,1.854,47,1.783,51,1.463,72,1.185,92,2.317,93,3.232,94,1.718,101,1.892,103,2.612,108,2.92,120,1.933,127,1.421,128,1.801,138,1.558,327,2.394,332,2.317,427,1.933,509,1.977,762,3.922,772,3.771,796,2.073,803,1.817,835,2.126,872,1.783,1307,4.368,1659,4.562,1907,3.01,2043,3.244,2091,3.244,2092,3.01,2094,4.311,2096,3.244,2100,3.01,2105,3.244,2109,3.639,2120,7.393,2121,3.599,2122,4.576,2123,3.599,2124,3.599,2125,3.599,2126,3.244,2127,3.599,2128,3.599,2129,3.599,2130,5.47,2131,8.085,2132,5.47,2133,5.47,2134,7.549,2135,5.47,2136,5.47,2137,5.47,2138,5.47,2139,5.47]],["t/403",[7,1.09,16,1.731,44,2.621,71,1.349,76,2.457,78,3.625,97,1.706,106,2.547,108,2.863,116,2.359,127,1.657,128,2.019,129,1.902,138,1.615,158,4.668,161,4.834,165,1.972,325,2.547,332,2.702,433,1.902,478,4.399,762,4.399,807,4.229,1070,2.792,1660,6.065,2100,3.51,2103,5.53,2109,4.081,2114,5.53,2134,3.783,2140,4.197,2141,5.53,2142,6.536,2143,8.487,2144,6.135,2145,6.135,2146,6.135,2147,3.783,2148,6.135,2149,7.251,2150,4.197,2151,4.197,2152,3.783,2153,4.197]],["t/405",[7,1.971,44,4.739,106,4.605,120,4.077,635,4.885,1383,5.231,2093,5.441,2094,7.18,2154,7.588,2155,7.588,2156,7.588,2157,7.588]],["t/407",[7,1.457,54,0.751,68,2.834,71,2.417,74,1.741,92,3.612,95,2.385,97,3.057,135,3.315,166,4.204,195,4.694,220,4.694,222,5.926,224,6.291,340,4.023,422,3.232,433,2.543,545,2.314,835,3.315,891,7.647,1109,5.003,1516,3.868,1532,3.868,2093,6.975,2095,8.169,2158,7.521,2159,5.611,2160,4.694,2161,5.611,2162,7.521]],["t/409",[0,2.776,12,3.815,15,3.667,16,1.721,47,2.067,51,1.696,54,0.817,72,2.011,92,2.686,93,2.937,97,3.441,101,3.213,108,2.854,110,3.49,120,2.242,149,1.802,169,2.107,213,2.465,291,2.107,327,2.776,340,2.992,389,1.721,417,2.686,419,3.288,427,3.282,454,3.222,545,2.52,547,3.667,802,2.242,823,2.776,849,3.288,1037,2.776,1095,1.86,1197,3.761,1344,3.49,1793,3.761,1907,3.49,1974,3.761,2093,6.719,2104,5.507,2109,5.292,2110,3.761,2117,5.507,2118,5.11,2119,5.507,2160,3.49,2163,6.109,2164,3.126,2165,3.288,2166,3.761,2167,4.173,2168,4.173,2169,4.173,2170,4.173,2171,4.173]],["t/411",[1,2.27,7,1.769,68,3.44,71,2.189,92,4.385,101,4.485,103,3.252,116,3.829,118,3.374,135,4.025,138,1.057,190,4.254,325,4.134,507,3.66,1549,5.104,2092,7.787,2093,4.884,2094,5.368,2172,6.812,2173,6.812,2174,6.812]],["t/414",[15,2.711,51,2.39,67,3.307,106,3.57,108,2.323,120,3.16,128,1.936,138,1.433,728,4.92,994,4.635,1100,4.635,1340,4.92,1706,4.92,1707,4.92,2065,6.919,2108,4.92,2109,5.162,2175,5.882,2176,7.76,2177,5.882,2178,5.882,2179,5.882,2180,5.882,2181,5.882,2182,5.882,2183,7.76,2184,5.882,2185,5.882,2186,4.407,2187,5.882,2188,5.302,2189,5.882]],["t/416",[3,1.537,97,1.515,101,1.96,102,1.635,108,1.472,138,1.648,402,1.882,449,1.846,762,2.672,797,2.672,807,2.569,975,2.479,1350,2.262,1706,3.117,1707,3.117,2065,4.209,2109,4.497,2131,9.548,2190,3.727,2191,3.359,2192,3.727,2193,3.727,2194,5.617,2195,3.359,2196,3.727,2197,3.727,2198,3.727,2199,3.727,2200,3.727,2201,3.727,2202,3.727,2203,3.727,2204,3.727,2205,3.727,2206,3.727,2207,3.727,2208,3.727,2209,3.727,2210,3.727,2211,3.727,2212,3.727,2213,3.727,2214,3.727,2215,3.727,2216,3.727,2217,3.727,2218,3.727,2219,3.727,2220,3.727,2221,3.727,2222,3.727,2223,3.727,2224,3.727,2225,3.727,2226,3.727,2227,3.727]],["t/418",[42,4.256,43,2.245,50,3.294,54,1.037,93,3.149,118,3.838,149,3.346,572,6.106,803,3.913,804,5.155,1659,5.342]],["t/420",[2228,7.543,2229,7.543,2230,8.368,2231,8.368]],["t/422",[2228,7.72,2229,7.72]],["t/424",[15,3.558,16,2.407,18,5.321,21,4.598,23,3.541,45,3.006,46,4.598,49,4.598,51,2.371,52,3.447,58,4.184,92,3.756,93,3.137,103,4.129,104,4.023,106,3.541,108,3.048,138,0.905,384,4.372,656,2.89,806,4.881,807,4.023,1451,3.068,1506,4.598,1655,5.26,1656,5.26,1657,4.372,2065,5.783,2232,5.835,2233,4.561,2234,7.719,2235,5.835,2236,5.26]],["t/427",[1,2.025,17,4.189,54,0.813,71,1.953,72,2,74,1.885,101,3.196,103,2.901,208,4.169,291,4.004,294,3.196,389,3.27,401,4.553,449,3.01,454,2.709,513,4.043,671,4.357,672,2.85,702,5.083,826,6.247,1091,4.043,1225,5.083,1348,4.847,1451,3.196,1558,5.94,2059,3.912,2067,3.912,2160,5.083,2237,4.789,2238,6.077]],["t/429",[13,4.059,39,4.059,43,1.937,50,2.843,74,2.075,226,3.517,389,3.477,436,2.402,449,3.313,454,2.981,463,5.011,635,4.305,685,3.852,720,3.445,1348,3.673,1451,3.517,2011,6.028,2059,4.305,2067,5.427,2239,6.687,2240,6.687,2241,6.687,2242,6.687,2243,6.687]],["t/431",[43,1.853,54,1.096,95,2.719,101,4.309,103,3.053,108,3.57,115,2.948,116,3.595,305,2.899,389,2.638,427,4.857,563,4.255,635,4.117,654,5.039,716,3.595,835,3.779,1092,4.409,1348,4.501,2059,4.117,2067,4.117,2244,6.395,2245,7.386,2246,5.765]],["t/433",[427,4.299,635,5.152,1348,4.396,1657,5.996,2059,5.152,2067,5.152,2247,8.003,2248,7.214]],["t/435",[43,2.567,50,2.605,54,0.82,101,3.223,105,3.945,118,3.035,353,7.185,395,2.874,427,4.282,472,3.223,509,3.366,679,3.719,734,3.827,868,4.394,876,4.225,1260,3.945,1348,5.654,1448,4.829,1451,4.934,1558,4.591,2249,6.128,2250,6.128]],["t/437",[4,3.42,9,2.624,43,1.677,44,3.615,54,1.153,66,2.867,68,2.923,72,2.528,73,4.561,92,4.943,101,4.038,103,2.763,104,5.294,108,3.032,120,3.11,336,4.561,417,3.727,635,3.727,654,4.561,917,3.513,921,4.561,1092,3.991,1129,4.337,1348,4.733,1350,4.66,1516,3.991,2059,3.727,2067,3.727,2246,5.218,2251,6.922,2252,5.789,2253,5.789]],["t/439",[7,1.116,23,4.46,43,1.808,50,3.124,54,0.983,72,1.414,92,2.765,94,2.05,103,4.089,108,3.383,128,1.414,138,0.666,158,2.765,208,4.241,213,2.538,290,2.961,322,2.415,427,3.352,433,1.947,454,2.782,463,3.218,547,1.98,550,1.798,635,4.017,679,2.607,683,3.218,734,5.58,785,4.917,818,2.961,849,5.79,1030,3.384,1066,3.872,1348,4.909,1451,3.864,2059,4.017,2067,4.017,2166,3.872,2245,3.872,2251,3.872,2254,4.295,2255,5.624,2256,3.872,2257,4.295,2258,6.239,2259,4.295,2260,3.872,2261,4.295]],["t/442",[4,3.62,7,1.592,17,4.225,43,1.775,120,3.292,129,2.777,156,3.366,181,4.838,217,4.591,226,3.223,230,3.62,272,3.945,287,4.394,290,4.225,336,4.829,588,3.292,710,5.126,713,3.719,732,5.303,737,3.719,851,3.529,915,4.591,1036,3.445,1217,5.126,1348,5.343,1535,4.077,2262,6.128,2263,6.128,2264,5.524,2265,6.128]],["t/444",[1,1.52,12,2.849,54,1.017,72,2.73,120,3.501,129,2.068,140,3.817,156,2.506,162,2.849,165,2.094,200,2.103,201,3.501,226,3.427,232,3.168,298,2.103,308,3.035,424,3.817,445,4.335,468,3.595,477,3.817,478,3.271,563,3.035,607,2.849,620,4.113,734,4.07,879,4.113,884,4.113,886,3.271,917,2.769,1026,2.565,1036,3.664,1037,3.035,1077,3.145,1087,3.595,1095,2.905,1161,2.451,1285,4.673,1296,2.937,1348,4.555,1370,3.817,1398,3.817,1536,3.145,2056,4.673,2264,4.113,2266,4.113,2267,4.563,2268,4.563,2269,4.563,2270,4.563,2271,4.563,2272,4.563,2273,3.595,2274,4.563,2275,2.849,2276,4.563,2277,4.563,2278,4.113,2279,4.563]],["t/446",[13,3.304,15,2.51,54,0.728,108,3.299,120,2.925,154,3.061,161,4.29,201,2.925,209,3.505,226,4.708,291,2.749,292,3.136,314,3.505,322,3.061,409,5.522,426,3.304,563,3.622,588,2.925,685,3.136,712,3.136,734,3.4,770,4.908,881,4.29,932,3.304,934,4.079,943,4.554,1058,4.29,1095,3.724,1112,3.904,1130,4.554,1260,4.744,1296,3.505,1348,5.138,1769,4.079,1845,4.554,2280,4.908,2281,5.444,2282,5.444]],["t/448",[28,3.265,32,4.566,43,2.297,54,0.813,74,1.885,94,3.785,101,3.196,103,2.901,108,3.131,226,4.169,298,2.801,786,4.553,876,4.189,1348,3.338,1659,4.189,1761,4.789,1790,7.268,1877,5.083,1879,5.478,2056,5.684,2059,3.912,2067,3.912,2283,6.077,2284,6.077,2285,6.077,2286,6.077,2287,6.077]],["t/450",[23,3.406,31,2.446,53,3.102,54,1.135,150,3.082,165,1.803,168,2.097,181,3.406,184,4.04,208,2.951,389,2.314,419,5.926,453,3.868,454,3.782,647,4.332,662,6.779,712,3.232,826,5.926,870,4.023,871,4.694,932,3.406,1058,4.422,1095,2.502,1342,4.694,1348,5.458,1451,3.955,1962,4.694,2288,5.611,2289,5.611]],["t/452",[53,2.81,71,2.741,184,4.582,187,5.176,214,4.146,231,4.134,232,3.312,297,3.388,299,6.274,1039,3.582,1084,3.923,1109,4.531,2186,5.104,2290,6.14]],["t/454",[51,2.768,71,2.992,98,3.509,100,2.385,169,3.44,184,3.66,187,5.176,211,4.531,299,5.814,305,3.865,657,4.696,1255,4.884,2291,6.72,2292,7.787,2293,5.698]],["t/456",[54,0.63,71,1.513,100,1.649,138,1.549,174,2.378,182,1.808,187,4.695,201,2.53,214,2.289,277,3.247,297,2.426,298,2.171,299,4.831,350,2.941,362,3.247,395,2.209,410,3.711,422,2.712,479,3.711,545,1.942,547,2.171,647,3.839,686,4.695,794,2.858,998,2.099,1013,2.477,1140,3.377,1148,4.245,1559,3.032,1751,4.245,1872,4.245,1877,5.575,2186,4.994,2291,3.711,2292,5.575,2293,3.939,2294,6.008,2295,5.252,2296,4.709,2297,4.709,2298,4.709,2299,3.939,2300,4.709,2301,4.709,2302,5.575,2303,4.709,2304,4.245,2305,4.709,2306,4.709]],["t/458",[28,3.56,54,0.886,71,2.694,128,2.181,138,1.426,156,3.64,158,4.266,174,3.347,182,2.545,234,3.485,294,3.485,297,2.412,299,4.138,422,4.827,510,4.569,686,5.087,1774,4.965,2290,5.974,2292,5.543,2302,7.011,2307,5.974]],["t/460",[71,3.157,100,2.43,138,1.339,174,3.505,182,2.665,187,5.698,214,3.374,219,5.2,277,4.785,294,3.65,299,4.334,422,3.998,794,4.212,888,4.977,2307,7.778]],["t/462",[1,1.689,28,2.723,54,0.939,71,2.255,95,2.154,145,3.263,156,2.784,158,3.263,173,4.239,174,3.543,184,4.324,187,4.884,201,2.723,211,3.371,213,4.145,214,2.464,231,3.076,253,2.849,288,2.994,294,2.665,299,5.424,395,3.291,448,3.165,647,2.919,686,3.076,712,2.919,730,2.665,812,2.994,872,2.51,888,3.634,1084,2.919,1109,3.371,1179,4.568,1451,2.665,1534,2.994,1951,4.568,2056,3.634,2059,3.263,2067,3.263,2186,3.797,2293,4.239,2294,6.325,2299,4.239,2302,4.239,2304,4.568,2308,4.568,2309,5.068,2310,3.797]],["t/464",[1,2.357,74,2.195,129,3.207,138,1.469,181,4.294,182,2.717,208,3.721,647,5.029,1250,3.154,1697,4.877,1772,5.073,2291,6.88,2311,7.87,2312,7.075,2313,7.075]],["t/466",[53,3.196,71,2.49,184,4.163,187,4.703,214,3.767,231,4.703,297,2.82,299,5.761,1039,4.075,2311,6.985]],["t/468",[71,2.439,76,3.65,95,3.226,297,2.762,299,4.739,506,4.483,679,4.605,998,3.383,2186,5.685,2291,5.979,2314,7.588,2315,7.588]],["t/471",[3,1.506,35,2.281,48,4.525,53,1.506,54,1.234,71,1.174,74,1.133,76,3.206,78,2.158,93,1.484,127,2.185,175,1.403,181,2.217,219,2.737,221,2.006,296,1.962,370,2.878,415,1.845,436,1.312,442,4.788,446,2.281,550,2.316,557,1.845,559,2.972,577,2.006,656,1.809,680,2.43,681,3.562,803,4.418,804,4.956,855,5.586,921,4.359,957,2.518,1036,3.11,1046,2.619,1077,2.518,1113,5.136,1114,3.055,1219,2.053,1534,3.946,1549,2.737,1693,5.586,1759,5.004,2152,4.987,2316,3.652,2317,5.066,2318,3.652,2319,3.652,2320,3.358,2321,3.292,2322,3.652,2323,3.652,2324,3.652,2325,3.652,2326,2.281,2327,3.652,2328,3.652,2329,3.652,2330,3.652,2331,2.878,2332,3.652,2333,3.652,2334,3.652]],["t/474",[12,4.215,71,2.169,74,2.094,93,3.446,264,4.875,297,2.456,415,3.408,436,2.425,455,5.318,550,2.826,656,3.343,696,3.987,975,4.49,1046,4.839,1219,5.212,1553,4.345,2317,5.212,2335,6.084]],["t/476",[54,0.813,71,1.953,74,1.885,93,3.222,232,2.954,264,4.169,291,3.069,296,3.265,297,2.212,370,4.789,415,3.069,436,2.183,531,3.586,577,3.338,624,4.189,656,3.927,696,3.59,803,3.069,893,5.478,1046,4.357,1219,3.416,1553,3.912,1641,4.553,2084,5.478,2317,5.258,2326,3.795,2336,8.824]],["t/478",[67,3.504,73,4.911,74,2.929,93,3.63,100,2.182,105,4.012,214,3.03,264,3.277,303,3.892,415,4.07,422,5.632,436,2.239,501,3.589,656,3.087,996,4.669,1046,4.469,1219,3.504,1362,5.213,1553,5.188,2317,5.021,2320,3.782]],["t/480",[3,2.89,24,5.522,48,3.765,93,3.528,168,2.619,415,3.539,488,6.223,501,4.036,550,2.934,696,4.14,2317,4.879,2320,5.981,2331,5.522,2337,5.25,2338,6.316]],["t/482",[24,5.175,45,3.383,48,3.528,71,2.678,93,2.669,112,2.977,213,3.88,216,5.175,435,3.383,436,2.359,545,2.709,564,4.709,681,4.228,696,3.88,720,3.383,776,4.101,803,3.317,975,4.369,1013,3.454,1390,5.494,1491,4.921,1510,4.228,2317,5.413,2339,5.175]],["t/484",[9,2.825,54,1.078,72,2.051,76,2.496,93,3.838,120,3.348,200,4.353,208,3.277,232,3.03,332,4.012,415,3.147,476,4.296,656,3.087,803,4.07,1000,3.348,1166,5.213,1503,4.669,1510,4.012,1692,6.692,2317,5.021,2340,5.618,2341,6.232]],["t/486",[8,3.118,9,3.536,12,4.872,54,0.793,57,4.285,76,2.375,78,4.609,93,2.41,100,2.076,175,2.277,177,3.945,297,2.158,370,4.672,393,4.96,402,2.994,407,4.443,415,3.94,455,4.672,476,4.088,488,4.251,577,3.257,656,2.937,796,3.415,872,2.937,1501,4.96,1549,5.845,1692,4.443,2317,3.333,2326,3.703,2340,5.345,2342,7.859,2343,5.929]],["t/489",[8,3.044,53,3.554,57,3.18,71,2.468,100,2.027,105,3.727,112,3.48,264,3.044,272,3.727,284,4.842,436,2.08,455,4.561,618,7.207,851,3.334,875,4.561,892,4.561,932,3.513,975,3.851,988,4.561,1036,3.254,1109,3.851,1251,5.218,1296,3.727,1502,4.561,2317,3.254,2320,5.229,2321,5.218,2337,4.337,2344,5.789,2345,4.337,2346,4.337,2347,5.789,2348,5.789,2349,5.789,2350,5.789]],["t/491",[3,3.07,8,3.915,57,4.089,71,1.776,74,1.715,100,1.935,102,2.424,112,3.374,118,2.738,154,3.107,160,3.354,169,2.791,179,2.847,192,3.677,205,5.338,221,3.036,264,4.427,291,2.791,305,2.505,316,3.354,436,2.674,563,3.677,1502,4.355,1759,4.141,2280,4.982,2317,4.185,2320,5.466,2337,4.141,2345,4.141,2346,4.141,2351,4.355,2352,4.982,2353,7.444,2354,4.982,2355,4.982,2356,5.527]],["t/493",[1,2.059,57,4.402,112,2.801,118,3.061,159,3.559,179,4.129,186,4.869,202,4.63,291,4.047,329,5.727,548,3.25,1036,3.474,1070,4.111,1510,3.978,2295,4.869,2317,3.474,2320,5.399,2339,4.869,2345,4.63,2346,4.63,2351,4.869,2357,8.895,2358,6.179,2359,6.179,2360,5.57,2361,6.179]],["t/495",[3,2.593,53,2.593,71,2.882,112,2.849,169,3.174,179,3.238,226,3.306,308,4.181,329,4.047,618,5.258,656,4.014,713,3.815,892,4.953,1058,4.953,1296,4.047,1743,5.666,2107,5.666,2317,3.534,2320,5.443,2345,4.71,2351,6.385,2362,8.103,2363,7.304,2364,6.286,2365,5.258]],["t/497",[100,2.63,128,2.472,395,3.522,656,3.72,713,4.558,2320,4.558,2331,7.135,2345,5.627,2346,5.627,2351,5.918,2363,6.77,2366,7.51,2367,7.51]],["t/499",[31,3.054,54,0.928,74,2.153,169,3.505,185,3.998,316,4.212,395,3.255,427,3.729,804,5.74,957,4.785,998,3.094,1371,5.806,1659,4.785,2055,6.257,2122,5.806,2317,3.902,2320,5.237,2331,5.469]],["t/502",[1,0.416,3,1.267,8,0.657,15,1.038,16,1.997,43,1.528,44,1.919,45,0.643,49,0.984,51,0.915,52,0.738,54,0.877,57,0.686,68,0.631,71,0.988,74,1.166,76,2.113,78,1.816,91,0.984,93,0.507,94,0.596,97,1.528,101,1.616,108,1.714,118,0.619,119,0.804,128,0.411,138,1.587,139,0.548,154,0.702,161,2.421,165,0.401,168,1.148,169,1.552,172,0.631,209,3.116,212,0.861,234,0.657,272,3.64,291,1.552,298,1.416,305,1.02,308,0.831,314,0.804,320,0.861,325,0.758,374,0.719,389,0.515,402,0.631,436,0.809,443,1.686,449,1.862,463,0.936,506,0.738,509,3.601,513,1.497,525,0.686,531,1.249,557,1.899,577,0.686,581,0.861,584,0.861,656,0.619,672,0.586,720,1.16,725,1.816,762,0.895,772,2.592,776,0.78,807,0.861,835,0.738,859,0.804,872,1.115,881,0.984,885,1.497,889,2.77,929,2.302,994,3.815,1000,0.671,1082,2.302,1129,2.302,1166,1.045,1171,1.126,1250,1.37,1442,1.126,1657,0.936,1659,0.861,1660,4.049,1664,1.126,1667,2.029,1683,0.984,1757,1.126,1769,0.936,1895,2.77,1945,1.045,2008,1.045,2045,1.126,2065,0.936,2086,2.029,2108,1.045,2109,1.497,2118,1.045,2142,3.389,2147,2.77,2165,2.962,2188,2.77,2191,1.126,2317,3.367,2365,1.045,2368,1.249,2369,1.249,2370,1.249,2371,1.249,2372,4.841,2373,1.126,2374,2.251,2375,1.249,2376,1.249,2377,1.249,2378,1.249,2379,1.249,2380,1.249,2381,1.249,2382,1.249,2383,1.249,2384,1.249,2385,1.249,2386,1.249,2387,1.249,2388,1.249,2389,1.249,2390,1.249,2391,1.249,2392,1.249,2393,1.249,2394,1.249,2395,1.249,2396,1.249,2397,1.249,2398,1.249,2399,1.249,2400,1.249,2401,2.251,2402,2.251,2403,1.249,2404,1.249,2405,1.249,2406,1.249,2407,1.126,2408,1.249,2409,1.249,2410,1.249,2411,1.249,2412,1.249,2413,1.249,2414,1.249,2415,2.251,2416,1.249,2417,1.249,2418,1.126,2419,1.249,2420,3.759,2421,1.249,2422,4.841,2423,5.99,2424,4.841,2425,3.073,2426,3.073,2427,2.251,2428,3.073,2429,3.073,2430,3.073,2431,3.073,2432,3.073,2433,3.073,2434,3.073,2435,3.073,2436,3.073,2437,3.073,2438,1.249,2439,3.073,2440,1.249,2441,1.249,2442,3.073,2443,3.073,2444,3.073,2445,3.073,2446,1.249,2447,3.073,2448,4.841,2449,3.073,2450,3.073,2451,1.249,2452,1.249,2453,1.249,2454,1.249,2455,1.249,2456,1.249,2457,1.249,2458,1.249,2459,1.249,2460,1.249,2461,1.249,2462,1.249,2463,1.249,2464,1.249,2465,1.249,2466,1.249,2467,1.249,2468,1.249,2469,1.249,2470,1.249,2471,1.249]],["t/505",[75,4.653,182,2.592,183,5.645,220,5.645,232,3.281,303,4.215,316,4.096,667,6.353,716,3.794,804,4.49,823,4.49,924,4.653,1029,3.707,1316,6.084,1752,5.645,1998,6.681,2141,6.084,2237,5.318,2335,6.084,2472,6.681,2473,6.749,2474,6.749]],["t/508",[48,2.614,53,2.007,64,2.953,71,2.192,72,2.245,75,5.884,76,3.419,91,5.374,105,5.07,208,2.559,214,2.366,232,3.316,296,2.614,667,3.646,853,5.646,1100,3.834,1348,3.747,1734,4.386,1968,7.517,1998,7.692,2342,4.386,2360,6.148,2472,7.081,2475,4.386,2476,4.866,2477,4.866,2478,4.866,2479,4.866,2480,4.386,2481,4.386,2482,4.866,2483,4.866,2484,4.866,2485,4.866,2486,4.866,2487,4.866]],["t/510",[12,4.997,15,3.689,98,4.123,149,3.455,174,4.041,1100,6.306,1241,7.214,2275,4.997]],["t/512",[1,1.814,12,3.4,15,3.851,16,2.246,72,1.792,101,2.863,103,2.599,108,3.299,128,1.792,138,1.451,139,2.388,291,2.749,402,3.722,507,3.959,851,4.245,853,3.904,975,3.622,1095,2.427,1683,4.29,1998,4.29,2237,7.371,2255,4.908,2275,5.841,2365,4.554,2472,7.054,2488,5.444,2489,5.444,2490,5.444,2491,5.444]],["t/514",[127,3.231,1108,6.13,1163,4.599,2275,5.109,2472,6.447,2475,7.375]],["t/516",[54,1.242,153,4.403,672,3.673,1451,4.118,2057,5.868,2233,4.627,2492,7.059,2493,6.171,2494,7.831]],["t/518",[44,1.811,48,2.484,53,1.196,54,0.619,73,2.285,78,3.407,93,2.344,127,1.145,128,1.898,137,1.114,138,0.717,149,1.252,155,2.08,174,2.335,181,1.76,190,2.888,208,1.525,217,3.465,224,2.426,226,1.525,234,1.525,283,2.08,298,1.337,363,4.321,426,1.76,442,2.08,488,2.08,509,3.168,531,1.179,548,1.525,656,1.437,660,2.285,681,3.712,734,1.811,802,1.558,828,3.076,851,3.321,872,1.437,994,2.285,1027,2.614,1036,1.63,1094,2.285,1095,1.293,1108,5.739,1118,2.285,1219,1.63,1250,2.571,1258,2.614,1398,2.426,1451,1.525,1502,2.285,2077,2.614,2165,2.285,2195,2.614,2233,4.525,2352,5.198,2480,2.614,2481,2.614,2495,2.9,2496,2.9,2497,2.9,2498,3.703,2499,2.9,2500,2.9,2501,2.9,2502,2.9,2503,2.426,2504,2.9,2505,2.9,2506,2.9,2507,2.9,2508,5.198,2509,2.9,2510,2.9,2511,2.9,2512,2.9,2513,4.783,2514,3.601,2515,2.9,2516,2.9,2517,2.9,2518,2.9,2519,2.9,2520,2.9,2521,2.9,2522,2.9,2523,2.9,2524,2.285,2525,2.9,2526,4.624,2527,2.9,2528,2.9,2529,2.9,2530,2.9,2531,3.316,2532,2.9,2533,2.9,2534,2.9,2535,2.9,2536,2.08,2537,2.9,2538,2.9,2539,4.624,2540,2.9,2541,2.9,2542,2.9,2543,2.9,2544,2.9,2545,4.624,2546,4.624,2547,4.624,2548,4.624,2549,2.9,2550,2.9]],["t/520",[14,3.394,15,1.87,28,2.18,48,2.18,64,2.462,71,2.911,72,2.341,74,2.435,93,3.19,147,2.797,169,2.049,174,2.049,185,2.337,189,3.197,227,3.394,273,4.291,329,2.612,389,1.673,433,1.839,436,1.458,501,2.337,507,2.18,509,3.287,624,2.797,668,2.909,803,3.964,869,2.909,1109,4.73,1219,2.281,1350,3.632,1642,3.657,1683,6.185,2078,3.197,2164,3.04,2337,3.04,2354,3.657,2498,4.973,2513,2.534,2514,2.534,2551,6.409,2552,7.895,2553,5.984,2554,5.006,2555,5.984,2556,6.185,2557,4.057,2558,3.657,2559,4.057,2560,3.737]],["t/523",[14,4.299,15,2.369,71,2.605,72,1.692,93,3.725,128,1.692,244,4.049,305,2.329,395,2.41,415,2.595,454,2.291,509,4.8,557,2.595,656,2.546,701,6.386,725,3.036,796,4.08,803,2.595,810,4.299,841,4.299,1109,3.419,1219,4.558,1350,3.119,1431,4.299,1553,3.308,1728,3.85,2233,3.036,2273,5.582,2498,5.097,2513,3.209,2514,3.209,2531,5.813,2561,4.632,2562,4.049,2563,5.139,2564,3.85]],["t/525",[72,2.485,74,1.398,93,3.069,98,2.322,108,1.78,208,4.777,291,3.262,375,3.77,395,3.541,427,2.421,433,2.042,436,1.619,451,3.231,454,2.009,507,2.421,509,4.147,572,3.551,685,2.596,796,3.72,803,3.813,851,2.596,885,2.998,917,2.735,1061,4.062,1084,2.596,1219,4.245,1491,3.377,1553,4.159,1559,2.901,2078,3.551,2233,4.461,2260,4.062,2273,5.09,2275,2.814,2295,3.551,2407,4.062,2493,5.09,2498,4.248,2513,2.814,2514,2.814,2531,4.632,2536,3.231,2560,4.715,2562,3.551,2564,5.657,2565,6.459]],["t/527",[9,2.266,28,2.685,35,3.121,71,1.606,72,2.842,93,3.51,96,2.953,108,1.974,141,3.034,147,3.446,181,5.508,208,2.629,227,4.181,273,3.584,436,1.796,448,3.121,509,2.746,557,2.524,656,4.278,679,3.034,713,3.034,734,3.121,796,2.879,803,3.51,851,2.879,1219,2.81,1237,3.939,1294,4.181,1451,3.655,1491,3.745,1553,3.218,1597,4.181,2078,3.939,2493,5.476,2498,4.449,2513,3.121,2514,3.121,2531,3.584,2536,3.584,2551,4.506,2552,4.506,2554,4.181,2556,3.939,2560,3.121]],["t/530",[28,3.086,43,1.664,48,4.104,53,2.369,54,1.022,71,1.846,95,2.442,264,4.017,363,4.303,393,4.804,395,2.694,509,3.155,525,3.155,557,3.857,656,2.845,668,4.118,794,3.486,1118,4.526,1219,4.294,1477,4.526,2266,5.177,2299,4.804,2498,4.421,2524,7.208,2554,4.804,2556,4.526,2560,4.77,2566,5.743,2567,5.743,2568,5.743,2569,5.743,2570,5.743,2571,5.743]],["t/532",[43,2.79,113,4.528,181,5.556,213,3.88,232,3.193,275,4.369,415,3.317,593,3.88,803,4.209,1161,3.528,1196,5.175,1219,3.692,1535,4.369,1553,4.228,1594,4.709,2498,4.293,2514,5.717,2572,5.92,2573,6.567]],["t/535",[3,1.955,43,1.373,51,1.926,54,0.634,58,4.801,62,3.735,93,3.617,97,1.926,103,4.532,108,3.894,118,2.348,138,0.735,139,2.079,165,1.523,548,3.521,656,3.846,802,2.546,803,3.921,818,3.268,872,2.348,873,3.965,1659,4.616,2109,3.153,2122,3.965,2126,4.273,2233,3.956,2236,4.273,2498,4.346,2513,2.96,2524,3.735,2574,4.74,2575,4.74,2576,4.74,2577,4.74,2578,4.74,2579,4.74,2580,4.74,2581,4.74,2582,4.74,2583,4.74,2584,4.74,2585,4.74]],["t/537",[24,4.911,35,3.892,48,3.348,74,1.933,185,3.589,214,3.03,264,3.277,273,4.469,433,2.825,436,3.209,454,2.778,506,3.682,548,3.277,624,4.296,803,3.147,872,3.087,903,6.35,1013,3.277,1046,4.469,1219,3.504,1510,4.012,2165,6.35,2339,4.911,2498,4.152,2556,6.35,2586,6.232,2587,6.232,2588,6.232]],["t/540",[39,3.513,43,1.677,54,0.774,72,2.836,93,2.353,112,2.624,127,2.286,173,4.842,232,2.814,275,3.851,288,3.42,681,3.727,955,3.727,986,4.151,1036,3.254,1532,3.991,1559,4.943,2006,3.513,2233,4.537,2275,4.795,2310,4.337,2326,5.38,2418,5.218,2498,2.982,2513,4.795,2514,5.73,2560,4.795,2589,5.506,2590,5.218,2591,4.842]],["t/542",[1,0.752,3,0.517,9,1.708,29,0.987,35,0.782,38,0.939,43,1.092,45,1.587,51,1.972,52,0.74,53,0.517,54,0.758,66,0.621,71,2.481,72,2.312,77,2.035,78,0.74,91,0.987,93,1.532,96,2.227,98,1.163,100,0.791,102,0.55,105,1.454,112,1.023,131,1.269,132,0.807,133,0.898,138,0.194,140,1.048,149,1.627,156,0.688,159,0.722,160,0.76,165,0.403,168,0.844,169,1.14,174,1.14,179,0.645,192,0.834,208,1.982,230,0.74,231,1.37,232,2.116,245,1.048,253,1.732,264,0.659,275,2.507,296,1.213,298,0.578,303,0.782,305,1.708,325,0.76,329,1.454,331,0.939,363,1.692,367,1.048,371,0.987,374,1.3,389,2.337,395,3.192,402,0.633,417,0.807,427,0.673,433,1.023,436,0.811,445,1.502,446,0.782,453,1.557,454,3.3,463,0.939,503,0.987,507,0.673,509,0.688,510,0.864,520,1.779,524,0.939,531,0.509,548,0.659,550,0.525,560,0.722,564,0.898,569,0.76,577,1.24,579,1.048,584,1.557,591,0.898,612,0.807,685,1.3,716,0.704,757,0.939,773,1.889,794,0.76,799,0.782,816,1.557,828,0.834,838,0.939,872,0.621,885,0.834,930,1.129,941,1.779,955,4.522,986,3.479,988,0.987,1000,0.673,1036,4.162,1077,0.864,1082,0.939,1084,0.722,1105,1.129,1229,1.129,1237,0.987,1242,1.048,1250,1.68,1259,0.939,1282,0.864,1286,1.048,1296,0.807,1498,1.129,1510,0.807,1511,1.129,1516,0.864,1536,0.864,1553,1.454,1559,2.802,1564,1.129,1657,0.939,1756,1.129,1763,1.129,1769,0.939,1900,2.035,1949,2.035,1967,1.129,1968,1.889,2006,0.76,2020,1.129,2056,0.898,2057,2.309,2164,3.96,2233,4.645,2256,1.129,2273,0.987,2275,3.538,2295,0.987,2308,1.129,2310,1.692,2320,2.641,2326,3.301,2337,0.939,2346,1.692,2492,1.129,2493,2.428,2498,2.918,2513,4.822,2514,3.538,2536,2.21,2560,4.1,2562,1.779,2564,0.939,2589,4.062,2590,1.129,2591,4.058,2592,1.253,2593,1.253,2594,1.253,2595,5.876,2596,3.398,2597,1.253,2598,1.253,2599,1.253,2600,1.253,2601,2.258,2602,1.253,2603,1.253,2604,2.258,2605,1.253,2606,1.253,2607,3.082,2608,1.253,2609,1.253,2610,1.253,2611,4.764,2612,1.253,2613,1.253,2614,1.129,2615,1.253,2616,1.253,2617,1.253,2618,1.253,2619,1.253,2620,1.129,2621,1.253,2622,1.253,2623,1.129,2624,1.129,2625,1.129,2626,2.258,2627,1.253,2628,1.253,2629,1.253,2630,1.253,2631,1.253]],["t/544",[9,2.414,10,4.801,15,2.455,71,2.851,100,1.865,112,2.414,208,2.801,232,2.589,292,3.067,395,3.405,423,6.073,433,2.414,436,1.913,445,3.543,506,3.147,509,2.925,513,3.543,955,4.674,1036,4.643,1250,2.374,1282,3.672,1559,3.429,2164,3.99,2233,4.289,2275,3.326,2320,5.013,2326,3.326,2498,2.744,2513,5.158,2536,3.819,2560,5.158,2561,4.801,2595,4.455,2611,4.801,2614,4.801,2623,4.801,2632,5.326,2633,5.326,2634,5.326]],["t/546",[1,2.008,9,2.732,71,1.937,72,2.596,156,3.311,174,3.044,232,2.93,454,2.687,457,5.433,747,5.041,955,3.88,986,4.321,1036,3.388,1532,4.155,1559,5.076,2006,3.658,2233,4.659,2275,3.764,2310,5.908,2326,4.924,2355,5.433,2498,4.062,2513,4.924,2514,4.924,2560,3.764,2591,5.041,2595,5.041,2624,5.433,2625,5.433,2635,6.027]],["t/549",[16,2.661,93,3.349,96,5.364,131,3.627,138,1.001,149,2.786,201,3.466,446,6.496,803,3.258,2338,5.816,2373,5.816,2498,5.207,2536,4.626,2636,6.452,2637,6.452]],["t/551",[1,1.301,39,2.37,43,1.132,54,0.93,66,1.935,74,1.212,76,1.565,93,2.365,96,2.308,101,2.054,112,1.77,118,3.444,129,1.77,133,2.8,149,2.512,164,3.267,175,1.5,181,2.37,184,2.098,226,2.054,291,1.972,316,2.37,346,2.25,375,3.267,389,1.611,395,2.729,402,1.972,426,2.37,454,2.594,476,2.693,536,3.521,685,2.25,741,3.746,803,1.972,888,2.8,971,3.521,997,2.598,1000,2.098,1003,4.867,1029,3.196,1106,4.011,1113,2.693,1163,3.271,1219,3.271,1250,3.1,1540,3.521,1559,2.514,1566,2.514,1692,2.926,1728,2.926,2006,3.531,2057,2.926,2233,4.867,2498,4.737,2503,3.267,2508,3.521,2514,2.439,2562,3.078,2564,2.926,2589,2.8,2638,3.906,2639,3.906,2640,3.906,2641,3.906,2642,3.906,2643,3.906,2644,3.906,2645,3.906,2646,3.906,2647,3.906,2648,3.906,2649,3.906,2650,3.906,2651,3.906,2652,3.906,2653,3.906]],["t/554",[1,2.15,8,3.393,43,1.869,54,0.863,138,1.409,149,3.558,184,3.466,326,2.478,346,3.716,351,5.084,394,4.153,685,4.746,741,4.153,955,4.153,1036,3.627,2006,3.916,2326,4.029,2498,3.324,2531,4.626,2560,4.029,2589,4.626,2654,6.452,2655,6.452,2656,7.428,2657,6.452]],["t/556",[17,3.672,42,2.925,54,0.971,71,2.333,78,3.147,85,4.197,133,3.819,137,2.788,149,2.299,164,4.455,168,1.99,208,2.801,303,3.326,317,4.455,409,3.99,435,2.744,436,2.608,462,4.801,513,5.495,534,5.44,559,2.861,561,4.197,672,2.498,685,3.067,717,5.205,741,5.317,782,3.99,872,2.638,955,3.429,1036,2.994,1120,4.197,1759,3.99,1849,4.801,2006,3.232,2275,3.326,2326,3.326,2498,4.255,2560,3.326,2589,3.819,2658,5.326,2659,5.326,2660,4.801]],["t/558",[1,0.849,8,1.34,28,1.368,31,0.828,35,2.6,42,2.287,43,1.53,54,0.706,58,1.826,66,1.262,71,1.338,87,1.695,94,1.216,95,1.77,118,1.262,137,0.978,138,0.395,143,1.591,149,1.1,165,0.819,184,3.613,208,2.777,231,1.546,232,3.509,252,1.34,264,1.34,266,1.756,299,1.591,332,1.64,351,3.281,363,3.957,380,1.695,384,1.909,389,1.051,400,3.281,402,2.103,409,1.909,436,0.915,445,1.695,454,2.719,509,1.399,513,1.695,584,1.756,682,2.131,685,2.398,772,1.756,778,3.702,920,2.131,955,1.64,1036,1.432,1062,1.909,1082,1.909,1113,1.756,1120,3.281,1239,1.505,1300,3.483,1364,2.296,1510,2.681,1536,3.641,1542,3.754,1554,2.296,1559,1.64,1666,2.131,1745,3.281,1790,1.909,2006,1.546,2164,1.909,2233,3.12,2237,2.007,2248,2.296,2310,3.12,2326,2.6,2498,4.463,2503,2.131,2524,2.007,2531,2.986,2558,3.754,2564,1.909,2572,2.296,2589,5.462,2596,3.754,2620,2.296,2656,2.296,2660,2.296,2661,4.164,2662,2.547,2663,2.547,2664,4.164,2665,2.547,2666,2.547,2667,2.547,2668,2.547,2669,4.164,2670,2.547,2671,2.547,2672,2.547,2673,2.547,2674,2.547,2675,2.547,2676,2.547,2677,2.547,2678,2.547,2679,2.547,2680,2.547,2681,4.164,2682,2.547,2683,2.547,2684,2.547,2685,2.547,2686,4.164,2687,4.164,2688,4.164,2689,2.547,2690,2.547,2691,2.547,2692,2.547,2693,2.547,2694,2.547,2695,4.164,2696,2.547,2697,2.547,2698,2.547,2699,2.547,2700,2.547,2701,2.547,2702,2.547,2703,2.547,2704,2.547,2705,2.547,2706,2.547,2707,4.164,2708,2.547,2709,2.547,2710,2.547]],["t/561",[15,3.199,16,2.863,43,2.011,103,3.313,108,2.741,141,4.212,149,3.725,174,3.505,337,5.469,472,3.65,796,3.998,917,4.212,1566,5.555,2711,6.941,2712,9.389,2713,8.628,2714,6.941]],["t/563",[3,3.413,43,2.397,472,4.351,1209,6.921,2715,8.274]],["t/565",[25,5.138,433,3.837,2716,8.465]],["t/567",[7,2.428,43,2.485,54,0.92,64,4.173,130,3.777,138,1.067,174,3.472,276,4.93,417,4.426,433,3.116,569,4.173,608,4.426,812,4.062,915,5.152,1383,4.74,2278,6.198,2339,5.418,2717,6.876,2718,6.876,2719,6.876]]],"invertedIndex":[["",{"_index":138,"t":{"17":{"position":[[129,1],[207,1]]},"21":{"position":[[823,1],[830,1],[842,1],[844,1],[854,1],[864,1],[866,1],[876,1],[889,1],[891,1],[899,1],[912,1]]},"25":{"position":[[174,2],[177,1],[200,2],[261,2],[264,1],[292,2],[362,2],[365,1],[399,2],[462,2],[465,1],[526,2]]},"27":{"position":[[108,1],[181,1],[365,1],[446,1],[461,1]]},"29":{"position":[[60,1]]},"31":{"position":[[704,2],[760,2],[763,2],[766,1],[768,3],[772,3],[818,2],[821,2],[824,1],[826,3],[830,3],[871,2],[874,2],[877,1],[879,3],[883,3],[934,2],[992,2],[995,2],[998,1],[1000,3],[1004,3],[1008,2],[1030,2],[1075,2],[1156,2],[1159,2],[1162,1],[1164,3],[1168,3],[1209,2],[1212,2],[1215,1],[1217,3],[1221,3]]},"37":{"position":[[7,1],[9,1],[24,1],[54,1],[103,1],[130,1],[172,1],[202,1],[247,1],[264,1],[308,1],[310,1],[325,1],[371,1],[373,1],[394,1],[420,1],[447,1],[500,1],[502,1],[517,1],[535,1],[567,1],[584,1],[602,1],[614,1],[638,1],[658,1],[695,1],[711,1],[735,1],[775,1],[789,1],[838,1],[863,1],[900,1],[919,1],[959,1],[973,1],[1007,1],[1022,1],[1049,1],[1051,1],[1063,1],[1091,1],[1118,1],[1144,1],[1160,1],[1175,1],[1177,1],[1198,1],[1226,1],[1258,1],[1260,1],[1275,1],[1309,1],[1311,1],[1323,1],[1357,1],[1359,1],[1379,1],[1402,1],[1404,1],[1419,1],[1451,1],[1490,1],[1514,1],[1555,1],[1557,1],[1577,1],[1626,1],[1647,1],[1690,1],[1712,1],[1752,1],[1770,1],[1815,1],[1832,1],[1851,1],[1869,1],[1915,1],[1941,1],[1974,1],[1988,1],[2003,1],[2027,1],[2051,1],[2070,1],[2105,1],[2121,1]]},"39":{"position":[[527,1],[775,1]]},"43":{"position":[[414,1]]},"45":{"position":[[131,1],[590,1],[602,1],[604,1],[612,1],[614,1],[624,1],[643,1],[877,1],[889,1],[902,1],[904,1],[912,1],[931,1],[965,1],[977,1],[990,1],[992,1],[1002,1],[1024,1]]},"57":{"position":[[55,1]]},"59":{"position":[[186,1],[333,1],[335,1],[354,1],[374,1],[459,2],[462,2],[465,2]]},"61":{"position":[[89,1],[124,1],[166,1]]},"63":{"position":[[173,1],[182,1],[254,2],[257,1]]},"65":{"position":[[78,1]]},"74":{"position":[[180,2]]},"80":{"position":[[137,1],[146,1],[176,1],[178,1],[180,3],[245,1],[283,1]]},"82":{"position":[[114,3],[163,3]]},"84":{"position":[[127,1],[149,2],[152,1],[266,2],[281,2],[284,1],[349,3],[372,2],[439,1],[502,1],[529,1],[553,1]]},"88":{"position":[[40,1]]},"90":{"position":[[83,1]]},"92":{"position":[[470,2]]},"102":{"position":[[265,2],[316,2]]},"106":{"position":[[16,3],[47,4]]},"108":{"position":[[30,2]]},"110":{"position":[[480,1],[486,1]]},"117":{"position":[[174,1],[271,1],[443,1],[583,1],[659,1]]},"121":{"position":[[68,3],[80,1],[157,1],[159,3]]},"126":{"position":[[28,3]]},"130":{"position":[[0,1]]},"136":{"position":[[156,2],[180,2],[183,2],[193,2]]},"138":{"position":[[837,1],[930,1],[992,1]]},"141":{"position":[[697,2]]},"144":{"position":[[427,2],[454,1],[594,2],[607,1],[630,1],[669,2],[672,1],[711,2],[714,3],[2338,2],[2359,1],[2449,2],[2541,2],[2571,1],[2594,1],[2666,2],[2669,1],[2687,2],[2690,1],[2758,2],[2761,3],[3261,2],[3282,1],[3389,2],[3574,2],[3609,1],[3657,2],[3703,1],[3783,2],[3895,2],[3923,1]]},"146":{"position":[[134,1],[136,1],[138,1],[190,1],[192,1],[244,1],[246,1],[1261,2],[1280,1],[1282,2],[1301,1],[1331,2],[1397,2],[1420,1],[1514,2],[1537,1],[1539,2],[1558,1],[1674,2],[1696,1],[1698,2],[1715,1],[2427,2],[2449,1],[2451,2],[2468,1],[2639,2],[2665,1],[3617,1],[4266,1],[4275,1],[4421,2],[4443,1],[4445,2],[4462,1],[4549,1],[4990,2],[5012,1],[5014,2],[5031,1],[5141,2],[5235,2],[5263,1],[5265,2],[5284,1],[5317,1]]},"162":{"position":[[341,1],[359,1],[434,1],[436,3],[440,1]]},"168":{"position":[[143,1],[269,1],[299,3],[361,1],[363,3],[414,1],[470,1],[524,2],[554,1],[567,1],[615,1],[617,1],[737,1],[742,1],[793,2],[796,3],[823,1],[901,2],[904,3],[908,1],[910,3],[914,1]]},"170":{"position":[[377,2],[401,2],[426,1],[460,2],[463,2],[597,2],[600,2],[623,2],[685,1],[719,2],[722,2],[849,2]]},"172":{"position":[[292,2],[350,1],[372,1],[431,1],[456,1],[458,1],[540,2],[588,1],[610,1],[670,1],[695,1],[719,1],[744,1],[746,1]]},"174":{"position":[[310,3]]},"177":{"position":[[344,2],[409,2]]},"183":{"position":[[537,1],[892,2]]},"185":{"position":[[110,1],[117,1],[229,1],[239,1],[281,1],[452,1],[457,1],[475,1],[590,1],[597,1],[730,1],[737,1],[856,3],[1044,1],[1052,1],[1085,1],[1087,1],[1089,1],[1091,1],[1101,1],[1103,1],[1105,1],[1107,1],[1114,1],[1116,1],[1121,1],[1123,1],[1130,1],[1614,1],[1795,1]]},"187":{"position":[[932,1]]},"193":{"position":[[287,1],[289,1]]},"197":{"position":[[51,1],[501,3],[505,2],[508,2],[511,5],[517,2]]},"199":{"position":[[748,1],[948,1]]},"205":{"position":[[429,2],[432,2],[435,1],[479,2],[482,2],[485,1],[487,3],[491,3],[495,3],[541,2],[544,2],[547,1],[549,3],[553,3],[680,2],[683,2],[686,1],[707,2],[710,2],[713,1],[783,2],[786,2],[789,1],[791,3],[795,2],[798,3],[802,3],[826,2],[829,2],[832,1],[901,2],[904,2],[907,1],[909,3],[913,2],[916,3],[920,3]]},"207":{"position":[[220,6],[276,3],[321,3],[325,2],[375,3],[379,2],[418,3],[422,2]]},"209":{"position":[[311,3],[342,4],[506,2],[688,2]]},"214":{"position":[[182,1]]},"216":{"position":[[82,3],[167,1],[246,3]]},"218":{"position":[[16,2],[106,2]]},"220":{"position":[[0,2],[52,1],[107,3],[186,2]]},"224":{"position":[[414,1],[496,1],[771,1],[798,2]]},"226":{"position":[[51,1]]},"228":{"position":[[118,1],[138,1],[274,1],[306,1],[324,1],[326,3],[330,2],[349,2],[411,1],[439,3],[443,3],[456,1],[470,2],[473,3],[614,1],[636,3],[640,2],[692,2],[695,2],[698,1],[725,1],[845,3],[849,2],[906,2],[909,2],[912,1],[985,3]]},"230":{"position":[[0,3],[8,2]]},"232":{"position":[[434,3],[503,2],[562,3],[566,3]]},"243":{"position":[[96,2],[247,1],[285,1],[294,1],[296,1],[347,2],[350,1],[393,2],[396,1],[439,2],[442,1],[489,2],[492,1],[550,2],[553,2],[556,2],[559,2],[615,2],[671,2],[726,2],[904,2],[942,1],[950,1],[969,1],[971,1],[999,1],[1105,1],[1178,1]]},"248":{"position":[[225,1]]},"258":{"position":[[760,2],[790,2],[859,2],[887,2]]},"260":{"position":[[215,2],[248,2]]},"268":{"position":[[335,1],[337,2],[340,3],[383,1],[414,2],[447,2],[529,1],[556,1],[576,1],[602,1],[604,2],[607,3],[634,1],[663,2],[666,2],[737,1]]},"270":{"position":[[28,1],[106,1],[137,1],[158,1],[212,1],[287,1]]},"272":{"position":[[36,1],[38,5],[63,1]]},"275":{"position":[[334,1],[356,2],[359,3],[363,1],[412,1],[434,1],[456,1],[458,1]]},"278":{"position":[[105,1],[107,2],[138,2],[160,2],[197,1],[212,2],[215,1],[217,2],[220,3],[224,1],[226,2],[271,1],[273,2],[276,3],[280,1],[282,2],[301,1]]},"286":{"position":[[386,2]]},"288":{"position":[[308,2],[359,2],[415,2],[471,2],[526,2],[649,1],[662,1],[773,2],[786,1],[832,1]]},"294":{"position":[[112,1],[198,2],[217,2],[220,3],[224,1]]},"303":{"position":[[297,2],[422,2],[447,2],[450,2],[564,1],[636,2],[670,1],[686,1],[750,1],[790,1]]},"305":{"position":[[329,2],[367,1],[369,2],[372,5],[423,1],[457,2],[460,5],[466,1]]},"311":{"position":[[305,2],[318,2],[382,3],[409,3],[445,3],[481,3]]},"315":{"position":[[283,1],[313,1]]},"320":{"position":[[598,1],[647,2],[678,1],[687,1],[727,1],[750,2],[753,2],[756,1],[758,1],[767,1],[776,1],[823,1],[861,1],[900,1],[930,1],[984,1],[986,1],[1078,1],[1118,1],[1155,2]]},"328":{"position":[[245,2],[271,1],[280,1],[312,1],[325,1],[392,3],[413,4],[427,3],[448,4],[453,2],[481,2]]},"330":{"position":[[89,2],[164,1],[205,1],[215,1],[220,1],[222,1]]},"332":{"position":[[0,2],[43,1],[54,1],[84,1],[103,1],[158,1],[209,2],[265,1],[347,1],[349,1]]},"334":{"position":[[0,2],[57,1],[76,1],[108,1],[139,1],[169,1],[183,1],[334,1],[391,2],[455,1],[489,1],[491,1]]},"336":{"position":[[367,2],[1024,2]]},"338":{"position":[[123,1],[139,1],[154,1],[201,1],[216,3],[220,1]]},"340":{"position":[[636,1],[721,1],[732,1],[767,1],[783,1],[785,1],[844,1],[896,2],[899,2],[918,1],[920,1],[947,1],[996,1],[1059,1],[1061,1]]},"342":{"position":[[490,1],[521,1],[591,1],[628,1],[731,2],[748,1],[780,2],[793,1],[901,2],[918,1],[1014,2],[1031,1]]},"344":{"position":[[422,2],[486,1],[540,1],[725,1],[775,2],[842,1],[857,1],[902,1],[917,2],[920,2],[936,1],[989,1],[991,1]]},"348":{"position":[[336,1],[349,1],[621,2],[661,1],[684,1]]},"350":{"position":[[418,2],[458,1],[474,1],[520,1],[552,1],[582,1],[595,1],[633,3],[887,1],[974,2],[1022,1],[1079,1]]},"352":{"position":[[413,2],[451,1],[508,1],[542,1],[599,1],[751,2],[789,1],[810,1],[844,1],[865,1]]},"355":{"position":[[297,1],[299,1]]},"357":{"position":[[725,1],[817,1],[879,1]]},"366":{"position":[[506,2],[564,3],[568,2],[571,1],[573,2],[623,2],[626,2],[629,1],[631,2],[707,3],[711,2],[714,1],[728,1],[730,2],[733,2],[736,1],[738,2],[788,1],[790,2],[849,2],[852,2],[855,1],[857,3],[861,3],[865,3],[869,3],[873,3]]},"370":{"position":[[1444,2],[1447,2],[1450,1],[1517,2],[1520,2],[1523,1],[1537,1],[1539,2],[1542,2],[1545,1],[1547,2],[1595,2],[1608,2],[1611,2],[1614,1],[1622,1],[1624,1],[1626,1],[1637,3],[1651,2],[1654,2],[1657,1],[1665,1],[1667,1],[1669,1],[1680,3],[1684,3],[1753,2],[1756,2],[1759,1],[1773,1],[1775,2],[1778,2],[1781,1],[1783,2],[1831,2],[1844,2],[1847,2],[1850,1],[1858,1],[1860,1],[1862,1],[1873,3],[1877,3],[1881,3]]},"373":{"position":[[240,2],[260,1],[288,2],[291,2],[294,1],[334,2],[355,2],[358,2],[361,1],[376,1],[423,2],[451,2]]},"375":{"position":[[438,2],[458,1],[523,2],[526,1],[602,2],[657,3],[680,2],[740,2],[743,2],[752,2],[769,3],[803,2],[806,2],[809,1],[811,2],[842,1],[895,2]]},"377":{"position":[[120,2],[140,1],[202,2],[205,2],[208,1],[248,2],[306,2],[309,2],[312,1],[325,1],[367,2],[399,2],[465,2],[468,2],[471,1],[483,2],[552,2],[626,2],[629,2],[632,1],[721,2]]},"379":{"position":[[771,2],[774,2],[777,1],[795,1],[919,1],[983,1],[1034,3]]},"381":{"position":[[337,2],[340,2],[343,1],[358,1],[404,1],[424,1],[485,2],[488,2],[509,1],[553,1],[580,3],[599,2],[602,2],[605,1],[629,3],[646,2],[649,1],[673,2],[1330,2],[1333,2],[1336,1],[1376,2],[1379,2],[1382,1],[1396,1],[1398,2],[1401,2],[1404,1],[1423,1],[1512,1],[1525,2],[1528,2],[1563,2],[1566,2],[1569,1],[1654,3],[1707,2],[1710,2],[1713,1],[1721,1],[1734,1],[1736,1],[1760,1],[1824,3],[1828,3],[1832,3]]},"385":{"position":[[1203,1],[1231,1],[1353,2],[1356,2],[1359,1],[1387,1],[1435,1],[1508,5],[1514,3],[1518,2],[1569,1],[1605,1],[1636,1],[1669,2],[1829,2],[1832,2],[1835,1],[1863,3]]},"399":{"position":[[401,1],[419,1],[434,1],[449,1],[493,1],[540,1],[542,3],[619,1],[637,1],[652,1],[667,1],[711,1],[758,1],[760,3]]},"401":{"position":[[470,1],[496,1],[509,1],[551,1],[609,1],[671,1],[737,1],[781,1],[844,1],[870,1],[883,1],[925,1],[983,1],[1045,1],[1111,1],[1155,1]]},"403":{"position":[[157,1],[185,1],[198,1],[261,1],[289,1],[302,1],[412,1],[462,1],[482,4],[487,1],[537,1],[592,1],[607,1],[618,2],[630,1],[632,6],[651,1],[663,1]]},"411":{"position":[[52,1]]},"414":{"position":[[298,1],[317,1],[327,1],[339,1]]},"416":{"position":[[313,1],[402,1],[459,1],[480,1],[533,1],[567,1],[632,1],[702,1],[742,1],[782,1],[819,1],[858,1],[905,1],[954,1],[1003,1],[1042,1],[1078,1],[1114,1],[1146,1],[1172,1],[1202,1],[1232,1],[1259,1],[1286,1],[1323,1],[1357,1],[1393,1],[1446,1]]},"424":{"position":[[220,1]]},"439":{"position":[[614,1]]},"456":{"position":[[140,2],[233,1],[288,2],[304,1],[339,2],[394,2],[422,1],[435,1],[537,2],[647,3]]},"458":{"position":[[179,1],[239,2],[262,1]]},"460":{"position":[[90,1],[116,1]]},"464":{"position":[[164,1],[228,2],[231,1]]},"502":{"position":[[353,2],[1733,1],[1748,1],[1776,1],[1824,1],[1845,1],[2466,1],[2502,1],[2529,1],[2576,1],[2596,1],[2619,1],[2644,1],[2654,3],[2658,1],[2660,1],[2678,1],[2702,1],[2738,1],[2769,1],[2817,1],[2838,1],[2881,1],[2919,1],[2944,1],[2968,1],[2970,1],[3171,1],[3217,2],[3220,1],[3391,1],[3418,1],[3465,1],[3614,1],[3637,1],[3662,1],[3672,3],[3676,1],[3678,1],[3696,1],[3720,1],[3756,1],[3787,1],[3835,1],[3856,1],[3899,1],[3937,1],[3962,1],[3986,1],[3988,1],[4217,1],[4330,1],[4357,1],[4404,1],[4495,1],[4518,1],[4543,1],[4553,3],[4557,1],[4559,1],[4587,1],[4611,1],[4647,1],[4678,1],[4726,1],[4747,1],[4790,1],[4828,1],[4853,1],[4877,1],[4879,1]]},"512":{"position":[[403,1],[496,1],[538,1],[551,2],[554,1]]},"518":{"position":[[1676,1],[1754,1]]},"535":{"position":[[246,1]]},"542":{"position":[[689,1]]},"549":{"position":[[111,1]]},"554":{"position":[[16,1],[169,1],[223,1]]},"558":{"position":[[690,2]]},"567":{"position":[[147,2]]}}}],["0",{"_index":2188,"t":{"414":{"position":[[329,2]]},"502":{"position":[[2983,2],[4001,2],[4892,2]]}}}],["0.12",{"_index":777,"t":{"117":{"position":[[436,6]]}}}],["0000d231816abba584714c9",{"_index":1788,"t":{"320":{"position":[[1128,26]]}}}],["02",{"_index":483,"t":{"53":{"position":[[27,2],[57,2]]},"174":{"position":[[410,2]]}}}],["04",{"_index":723,"t":{"106":{"position":[[453,2]]},"209":{"position":[[292,2]]}}}],["05",{"_index":1785,"t":{"320":{"position":[[1050,2]]}}}],["0a7ac9",{"_index":787,"t":{"121":{"position":[[88,10]]}}}],["0f3551",{"_index":790,"t":{"121":{"position":[[146,10]]}}}],["1",{"_index":159,"t":{"19":{"position":[[218,2],[331,2]]},"31":{"position":[[667,2]]},"53":{"position":[[175,1]]},"115":{"position":[[128,2],[169,2],[553,3]]},"117":{"position":[[229,4]]},"138":{"position":[[294,1]]},"144":{"position":[[647,3],[2611,3]]},"146":{"position":[[144,2],[159,3],[3372,1],[3734,1]]},"278":{"position":[[110,2]]},"318":{"position":[[352,2]]},"385":{"position":[[1422,3]]},"493":{"position":[[314,1]]},"542":{"position":[[3043,2]]}}}],["1.1",{"_index":2062,"t":{"385":{"position":[[948,3],[1473,5]]}}}],["1.2",{"_index":2066,"t":{"385":{"position":[[1100,3],[1544,5]]}}}],["10",{"_index":728,"t":{"108":{"position":[[174,2]]},"146":{"position":[[4781,2]]},"414":{"position":[[242,2]]}}}],["10000",{"_index":1993,"t":{"375":{"position":[[746,5]]}}}],["10px",{"_index":619,"t":{"84":{"position":[[239,7]]}}}],["11",{"_index":722,"t":{"106":{"position":[[450,2]]},"172":{"position":[[868,2]]},"209":{"position":[[289,2]]}}}],["12",{"_index":1201,"t":{"174":{"position":[[407,2]]}}}],["1234",{"_index":832,"t":{"136":{"position":[[223,4],[324,5]]},"355":{"position":[[341,5]]}}}],["127.0.0.1",{"_index":2423,"t":{"502":{"position":[[2544,9],[2554,11],[2994,9],[3433,9],[3443,11],[4012,9],[4372,9],[4382,11],[4903,9]]}}}],["127.0.0.1:9001",{"_index":2422,"t":{"502":{"position":[[2511,17],[2604,14],[3400,17],[3622,14],[4339,17],[4503,14]]}}}],["13:27:00",{"_index":2446,"t":{"502":{"position":[[2906,8]]}}}],["13:39:44",{"_index":2469,"t":{"502":{"position":[[4815,8]]}}}],["13:40:05",{"_index":2459,"t":{"502":{"position":[[3924,8]]}}}],["15:20:30.888",{"_index":1787,"t":{"320":{"position":[[1056,12]]}}}],["172.29.173.128",{"_index":2193,"t":{"416":{"position":[[173,16]]}}}],["18",{"_index":1655,"t":{"286":{"position":[[56,2]]},"424":{"position":[[56,2]]}}}],["1993",{"_index":854,"t":{"138":{"position":[[533,4]]},"357":{"position":[[421,4]]}}}],["1add",{"_index":279,"t":{"27":{"position":[[798,11]]}}}],["button>button",{"_index":897,"t":{"141":{"position":[[652,13]]}}}],["button>mybuttonopt",{"_index":906,"t":{"144":{"position":[[171,13],[214,13]]}}}],["button>two",{"_index":894,"t":{"141":{"position":[[607,10]]}}}],["byte",{"_index":2527,"t":{"518":{"position":[[1039,5]]}}}],["c6f6",{"_index":2545,"t":{"518":{"position":[[1707,4],[1798,4]]}}}],["cach",{"_index":1663,"t":{"286":{"position":[[380,5]]}}}],["calcul",{"_index":1540,"t":{"252":{"position":[[572,13]]},"551":{"position":[[1160,9]]}}}],["calendar",{"_index":1234,"t":{"185":{"position":[[241,8]]}}}],["call",{"_index":557,"t":{"67":{"position":[[44,6]]},"74":{"position":[[44,6]]},"151":{"position":[[282,6]]},"211":{"position":[[118,6]]},"228":{"position":[[675,4]]},"241":{"position":[[265,5],[367,5]]},"320":{"position":[[281,6]]},"366":{"position":[[591,6]]},"370":{"position":[[139,4],[810,4],[1003,4],[1223,6]]},"373":{"position":[[59,4]]},"381":{"position":[[1037,6],[1542,4]]},"383":{"position":[[211,5]]},"385":{"position":[[2460,6]]},"393":{"position":[[309,4]]},"471":{"position":[[488,5]]},"502":{"position":[[240,6],[390,6],[613,6],[3132,4]]},"523":{"position":[[140,5]]},"527":{"position":[[207,7]]},"530":{"position":[[226,7],[345,6]]}}}],["callback",{"_index":1987,"t":{"375":{"position":[[330,9]]}}}],["callback1",{"_index":912,"t":{"144":{"position":[[659,9],[2623,10]]}}}],["callback2",{"_index":913,"t":{"144":{"position":[[701,9],[2719,10]]}}}],["calld",{"_index":2038,"t":{"381":{"position":[[890,5]]}}}],["callout",{"_index":603,"t":{"82":{"position":[[58,9]]}}}],["camelcas",{"_index":258,"t":{"27":{"position":[[372,11],[468,11]]},"177":{"position":[[215,9],[283,9],[372,9]]},"280":{"position":[[27,10]]}}}],["cant",{"_index":1574,"t":{"258":{"position":[[1088,4]]}}}],["canva",{"_index":2595,"t":{"542":{"position":[[340,6],[443,6],[2040,6],[2287,6],[2856,6],[2914,6],[3107,7],[3385,7],[3412,6],[3485,6],[3616,6],[3919,6],[4204,7]]},"544":{"position":[[325,6]]},"546":{"position":[[226,6]]}}}],["canvas",{"_index":2607,"t":{"542":{"position":[[1849,8],[2666,8],[4742,9]]}}}],["capabl",{"_index":893,"t":{"141":{"position":[[589,13]]},"476":{"position":[[187,7]]}}}],["captur",{"_index":243,"t":{"25":{"position":[[561,8]]},"39":{"position":[[543,8]]}}}],["card",{"_index":273,"t":{"27":{"position":[[699,4]]},"119":{"position":[[242,6]]},"243":{"position":[[89,6]]},"520":{"position":[[138,4],[335,5]]},"527":{"position":[[82,5]]},"537":{"position":[[51,4]]}}}],["cardurlparam",{"_index":1479,"t":{"243":{"position":[[796,14]]}}}],["care",{"_index":608,"t":{"82":{"position":[[133,4],[211,4]]},"106":{"position":[[266,7]]},"141":{"position":[[256,8]]},"309":{"position":[[190,4]]},"336":{"position":[[461,5]]},"355":{"position":[[377,8]]},"377":{"position":[[102,4]]},"385":{"position":[[134,4]]},"567":{"position":[[16,4]]}}}],["carefulli",{"_index":1040,"t":{"146":{"position":[[3793,10]]},"385":{"position":[[181,10]]}}}],["case",{"_index":172,"t":{"21":{"position":[[199,5],[1146,4]]},"23":{"position":[[374,5]]},"123":{"position":[[115,4]]},"138":{"position":[[115,4]]},"158":{"position":[[492,4]]},"185":{"position":[[1542,4]]},"197":{"position":[[32,5],[98,5],[205,5]]},"275":{"position":[[8,6]]},"326":{"position":[[338,5]]},"342":{"position":[[275,6]]},"348":{"position":[[93,6]]},"355":{"position":[[423,4]]},"364":{"position":[[206,4]]},"370":{"position":[[410,4],[418,4],[563,5]]},"375":{"position":[[268,4]]},"381":{"position":[[1244,5]]},"387":{"position":[[187,6]]},"393":{"position":[[491,6]]},"502":{"position":[[4104,4]]}}}],["catch",{"_index":1845,"t":{"338":{"position":[[7,5]]},"375":{"position":[[206,5],[619,14]]},"446":{"position":[[83,5]]}}}],["catch(error",{"_index":1847,"t":{"338":{"position":[[141,12]]}}}],["categor",{"_index":403,"t":{"41":{"position":[[88,10]]}}}],["categori",{"_index":532,"t":{"59":{"position":[[382,11]]}}}],["caus",{"_index":1094,"t":{"154":{"position":[[424,6]]},"158":{"position":[[233,5]]},"338":{"position":[[73,5],[203,6]]},"518":{"position":[[235,5]]}}}],["cd",{"_index":49,"t":{"6":{"position":[[81,2]]},"286":{"position":[[143,2]]},"424":{"position":[[171,2]]},"502":{"position":[[356,2]]}}}],["cell",{"_index":1047,"t":{"146":{"position":[[4831,4],[5110,4]]}}}],["central",{"_index":400,"t":{"39":{"position":[[870,7]]},"183":{"position":[[787,7]]},"326":{"position":[[667,7]]},"558":{"position":[[68,7],[215,11]]}}}],["certain",{"_index":143,"t":{"17":{"position":[[192,7]]},"146":{"position":[[1988,7]]},"160":{"position":[[40,7]]},"162":{"position":[[65,7]]},"199":{"position":[[1142,7]]},"205":{"position":[[1022,7]]},"209":{"position":[[102,7]]},"224":{"position":[[176,7],[219,7]]},"266":{"position":[[357,7]]},"558":{"position":[[652,7]]}}}],["chain",{"_index":1986,"t":{"375":{"position":[[134,7],[300,7],[402,6]]}}}],["challeng",{"_index":1059,"t":{"148":{"position":[[111,10]]}}}],["chang",{"_index":454,"t":{"47":{"position":[[61,7]]},"136":{"position":[[5,6]]},"138":{"position":[[126,7],[647,7],[749,6],[780,6],[870,7],[967,7]]},"166":{"position":[[48,8]]},"207":{"position":[[250,7]]},"246":{"position":[[108,7],[128,6],[385,8],[438,6],[532,7]]},"248":{"position":[[368,6],[1305,6]]},"286":{"position":[[261,6]]},"309":{"position":[[401,7]]},"318":{"position":[[169,8],[237,7]]},"326":{"position":[[630,8]]},"355":{"position":[[5,6]]},"357":{"position":[[140,7],[535,7],[637,6],[668,6],[757,7],[854,7]]},"364":{"position":[[105,7]]},"409":{"position":[[34,7],[232,7],[327,7]]},"427":{"position":[[18,6]]},"429":{"position":[[212,7]]},"439":{"position":[[308,7],[597,8]]},"450":{"position":[[264,8],[328,7],[443,7]]},"523":{"position":[[484,7]]},"525":{"position":[[35,7]]},"537":{"position":[[220,6]]},"542":{"position":[[200,7],[492,7],[562,7],[1010,7],[1033,6],[1088,6],[1143,7],[1368,7],[1407,7],[1938,8],[2519,7],[3132,7],[3174,7],[3251,7],[4122,7]]},"546":{"position":[[142,7]]},"551":{"position":[[1023,7],[1195,8]]},"558":{"position":[[433,7],[609,7],[913,7],[1030,7]]}}}],["channel",{"_index":2564,"t":{"523":{"position":[[428,7]]},"525":{"position":[[280,7],[394,7],[497,8]]},"542":{"position":[[1336,9]]},"551":{"position":[[621,9]]},"558":{"position":[[1515,7]]}}}],["chapter",{"_index":695,"t":{"102":{"position":[[209,7]]},"252":{"position":[[1154,7]]}}}],["charact",{"_index":708,"t":{"106":{"position":[[128,10]]},"197":{"position":[[490,10]]},"209":{"position":[[423,10]]}}}],["charset=utf",{"_index":2435,"t":{"502":{"position":[[2803,11],[3821,11],[4712,11]]}}}],["chat",{"_index":1707,"t":{"305":{"position":[[416,6],[579,7]]},"414":{"position":[[162,4]]},"416":{"position":[[300,4]]}}}],["check",{"_index":1350,"t":{"209":{"position":[[94,5],[139,5],[488,5]]},"214":{"position":[[236,5]]},"266":{"position":[[318,8],[472,11],[550,10],[564,8],[744,8]]},"315":{"position":[[331,6]]},"370":{"position":[[183,5]]},"385":{"position":[[60,5]]},"389":{"position":[[270,7]]},"416":{"position":[[0,6]]},"437":{"position":[[3,5],[294,5]]},"520":{"position":[[598,6],[649,8]]},"523":{"position":[[185,5]]}}}],["checkbox",{"_index":847,"t":{"138":{"position":[[331,8]]}}}],["checkinputisvalid",{"_index":1593,"t":{"266":{"position":[[503,19]]}}}],["checkpermiss",{"_index":1592,"t":{"266":{"position":[[484,18]]}}}],["child",{"_index":1369,"t":{"214":{"position":[[294,5],[340,5]]}}}],["childcompon",{"_index":1306,"t":{"199":{"position":[[479,15]]}}}],["children",{"_index":611,"t":{"84":{"position":[[129,11],[335,13],[353,10]]},"146":{"position":[[2887,8],[3830,8]]},"148":{"position":[[524,8]]}}}],["choos",{"_index":29,"t":{"4":{"position":[[177,6]]},"151":{"position":[[57,7]]},"368":{"position":[[442,6]]},"542":{"position":[[3727,7]]}}}],["chosen",{"_index":1081,"t":{"151":{"position":[[240,6]]}}}],["chunk",{"_index":2279,"t":{"444":{"position":[[736,6]]}}}],["chunkwis",{"_index":2271,"t":{"444":{"position":[[372,9]]}}}],["ci",{"_index":104,"t":{"10":{"position":[[77,2]]},"37":{"position":[[1487,2]]},"43":{"position":[[294,2],[523,3]]},"128":{"position":[[117,2]]},"286":{"position":[[168,2]]},"424":{"position":[[196,2]]},"437":{"position":[[205,2],[247,2]]}}}],["ci/cd",{"_index":390,"t":{"39":{"position":[[19,5],[735,5]]}}}],["circl",{"_index":1097,"t":{"156":{"position":[[29,6]]},"158":{"position":[[343,7]]}}}],["circular",{"_index":638,"t":{"88":{"position":[[31,8]]},"90":{"position":[[56,8]]},"92":{"position":[[95,8],[293,8]]},"94":{"position":[[87,8]]},"154":{"position":[[0,8],[304,8]]},"158":{"position":[[246,8],[571,8]]},"352":{"position":[[0,8],[201,8],[267,8],[649,8],[918,8],[1288,8]]}}}],["claim",{"_index":2539,"t":{"518":{"position":[[1431,7],[1503,5]]}}}],["clariti",{"_index":163,"t":{"19":{"position":[[411,8]]},"21":{"position":[[1382,8]]}}}],["class",{"_index":182,"t":{"21":{"position":[[415,5],[463,7],[564,5],[630,5],[665,5],[741,5],[770,5]]},"25":{"position":[[228,5],[305,5],[412,5]]},"27":{"position":[[232,5],[344,7]]},"29":{"position":[[117,6],[188,5]]},"115":{"position":[[69,7]]},"164":{"position":[[42,7],[164,7]]},"168":{"position":[[318,5]]},"199":{"position":[[653,6],[722,6]]},"224":{"position":[[31,7]]},"252":{"position":[[553,5],[625,7],[715,8]]},"258":{"position":[[770,5],[869,5]]},"260":{"position":[[225,5]]},"262":{"position":[[482,5]]},"275":{"position":[[365,5]]},"278":{"position":[[0,7],[92,5]]},"280":{"position":[[0,7]]},"292":{"position":[[245,6]]},"294":{"position":[[89,5]]},"296":{"position":[[60,6],[270,5]]},"298":{"position":[[323,5]]},"303":{"position":[[432,5],[533,5]]},"315":{"position":[[69,6]]},"320":{"position":[[559,5],[810,5]]},"328":{"position":[[463,5]]},"330":{"position":[[33,6],[141,5]]},"332":{"position":[[144,5]]},"334":{"position":[[258,5]]},"340":{"position":[[550,5],[929,5]]},"344":{"position":[[678,5]]},"379":{"position":[[293,5],[403,5]]},"381":{"position":[[733,6]]},"456":{"position":[[150,5]]},"458":{"position":[[161,5]]},"460":{"position":[[64,5]]},"464":{"position":[[140,5]]},"505":{"position":[[66,7]]}}}],["class=\"bg",{"_index":761,"t":{"115":{"position":[[289,9]]}}}],["class=\"text",{"_index":763,"t":{"115":{"position":[[392,11],[530,11]]}}}],["classnam",{"_index":188,"t":{"21":{"position":[[642,13],[671,13],[747,13],[776,13],[832,9],[1003,12]]},"25":{"position":[[251,9],[352,9],[452,9]]}}}],["clean",{"_index":1050,"t":{"146":{"position":[[4928,5]]},"258":{"position":[[181,5]]},"357":{"position":[[84,5]]}}}],["cleanup",{"_index":2058,"t":{"385":{"position":[[374,7]]}}}],["clear",{"_index":1597,"t":{"268":{"position":[[196,5]]},"286":{"position":[[374,5]]},"527":{"position":[[491,5]]}}}],["clearli",{"_index":1575,"t":{"258":{"position":[[1308,7]]},"266":{"position":[[30,7]]},"336":{"position":[[822,7]]},"364":{"position":[[229,7]]},"385":{"position":[[362,7]]}}}],["cli",{"_index":2160,"t":{"407":{"position":[[126,3]]},"409":{"position":[[136,3]]},"427":{"position":[[328,3]]}}}],["click",{"_index":624,"t":{"84":{"position":[[297,7]]},"205":{"position":[[469,9],[891,9]]},"207":{"position":[[268,7]]},"214":{"position":[[92,6]]},"476":{"position":[[14,6]]},"520":{"position":[[251,6]]},"537":{"position":[[103,5]]}}}],["click=\"callback1",{"_index":959,"t":{"144":{"position":[[3310,18]]}}}],["click=\"callback1\">opt",{"_index":927,"t":{"144":{"position":[[1080,25],[2387,25]]}}}],["click=\"callback2",{"_index":949,"t":{"144":{"position":[[2483,19]]}}}],["click=\"callback2\">opt",{"_index":928,"t":{"144":{"position":[[1141,25],[3423,25]]}}}],["click=\"callback3\">opt",{"_index":964,"t":{"144":{"position":[[3721,25]]}}}],["click=\"callback4\">opt",{"_index":966,"t":{"144":{"position":[[3802,25]]}}}],["click=\"emit('delete')\">delete{{option.label}}facebook",{"_index":627,"t":{"84":{"position":[[460,24]]}}}],["color=\"#25c2a0\">docusauru",{"_index":625,"t":{"84":{"position":[[394,26]]}}}],["color=\"'r",{"_index":948,"t":{"144":{"position":[[2468,14],[3408,14]]}}}],["colors.grey.darken3",{"_index":789,"t":{"121":{"position":[[111,20]]}}}],["column",{"_index":1046,"t":{"146":{"position":[[4784,7]]},"471":{"position":[[164,6]]},"474":{"position":[[68,6]]},"476":{"position":[[353,6]]},"478":{"position":[[151,6]]},"537":{"position":[[22,7]]}}}],["columnboard",{"_index":2551,"t":{"520":{"position":[[59,12],[99,11],[270,11]]},"527":{"position":[[28,11]]}}}],["combin",{"_index":1532,"t":{"248":{"position":[[1348,9]]},"315":{"position":[[755,7],[792,8]]},"370":{"position":[[318,11]]},"381":{"position":[[814,8]]},"407":{"position":[[307,12]]},"540":{"position":[[187,11]]},"546":{"position":[[244,11]]}}}],["come",{"_index":995,"t":{"146":{"position":[[1019,4]]},"185":{"position":[[1996,5]]}}}],["command",{"_index":101,"t":{"10":{"position":[[18,7]]},"37":{"position":[[752,8],[936,8]]},"39":{"position":[[473,9]]},"86":{"position":[[34,7]]},"399":{"position":[[85,8],[272,8]]},"401":{"position":[[134,8]]},"409":{"position":[[289,7],[834,8]]},"411":{"position":[[24,8],[59,7]]},"416":{"position":[[269,8]]},"427":{"position":[[332,8]]},"431":{"position":[[41,8],[192,7]]},"435":{"position":[[168,7]]},"437":{"position":[[101,7],[175,7]]},"448":{"position":[[280,7]]},"502":{"position":[[325,8],[1672,8],[1956,7]]},"512":{"position":[[56,8]]},"551":{"position":[[593,8]]}}}],["commands.j",{"_index":348,"t":{"37":{"position":[[907,11]]}}}],["comment",{"_index":307,"t":{"31":{"position":[[587,7],[1033,9]]},"104":{"position":[[40,8]]},"138":{"position":[[994,8]]},"177":{"position":[[74,7]]},"185":{"position":[[89,7]]},"318":{"position":[[443,8]]},"357":{"position":[[230,9],[881,8]]}}}],["commit",{"_index":849,"t":{"138":{"position":[[398,6],[479,6]]},"357":{"position":[[9,6],[109,7],[173,7],[264,6]]},"409":{"position":[[247,6]]},"439":{"position":[[357,6],[586,6],[620,6]]}}}],["common",{"_index":306,"t":{"31":{"position":[[447,6],[603,6],[910,6],[1047,6]]},"154":{"position":[[25,6],[97,6]]},"284":{"position":[[159,6]]},"309":{"position":[[153,6]]}}}],["common_login",{"_index":248,"t":{"27":{"position":[[62,13]]}}}],["commoncoursesteps.spec.j",{"_index":302,"t":{"31":{"position":[[292,25],[937,25],[1088,25]]}}}],["commun",{"_index":476,"t":{"49":{"position":[[258,9]]},"234":{"position":[[467,13]]},"266":{"position":[[38,11]]},"352":{"position":[[971,11]]},"484":{"position":[[8,13]]},"486":{"position":[[129,14]]},"551":{"position":[[861,14]]}}}],["compar",{"_index":868,"t":{"138":{"position":[[975,8]]},"141":{"position":[[462,7]]},"144":{"position":[[2303,7]]},"146":{"position":[[3490,13]]},"357":{"position":[[862,8]]},"435":{"position":[[181,7]]}}}],["compass",{"_index":2082,"t":{"385":{"position":[[2426,8]]}}}],["compat",{"_index":2430,"t":{"502":{"position":[[2709,11],[3727,11],[4618,11]]}}}],["compent",{"_index":1435,"t":{"234":{"position":[[58,8]]}}}],["compil",{"_index":1092,"t":{"154":{"position":[[355,8]]},"156":{"position":[[119,8]]},"379":{"position":[[604,9],[891,13]]},"381":{"position":[[491,13]]},"385":{"position":[[1550,13]]},"431":{"position":[[169,8]]},"437":{"position":[[146,8]]}}}],["complementari",{"_index":2613,"t":{"542":{"position":[[2444,13]]}}}],["complet",{"_index":107,"t":{"12":{"position":[[18,9]]},"141":{"position":[[450,11]]},"144":{"position":[[2868,10]]},"146":{"position":[[3469,10]]},"381":{"position":[[786,10]]}}}],["complex",{"_index":288,"t":{"29":{"position":[[135,7]]},"144":{"position":[[2841,7]]},"146":{"position":[[27,7],[1902,8],[2008,10]]},"185":{"position":[[250,7]]},"211":{"position":[[63,7]]},"252":{"position":[[604,7]]},"292":{"position":[[164,7]]},"296":{"position":[[0,7]]},"315":{"position":[[546,7]]},"364":{"position":[[177,7]]},"462":{"position":[[585,7]]},"540":{"position":[[341,7]]}}}],["complic",{"_index":944,"t":{"144":{"position":[[2247,11]]}}}],["compon",{"_index":326,"t":{"35":{"position":[[221,10]]},"84":{"position":[[76,10]]},"102":{"position":[[23,10],[358,10]]},"106":{"position":[[325,9]]},"110":{"position":[[84,11]]},"112":{"position":[[53,9]]},"119":{"position":[[226,10],[386,9]]},"141":{"position":[[24,9]]},"144":{"position":[[1313,11],[1920,10]]},"146":{"position":[[834,10],[988,9],[1008,10],[1103,9],[1170,9],[1857,10],[2276,9],[2866,10],[2916,10],[3443,9],[4357,11],[5380,10],[5529,9]]},"148":{"position":[[24,10],[58,10],[180,10],[369,9],[545,9],[684,10],[818,9],[1097,10],[1183,10],[1358,9]]},"151":{"position":[[569,10]]},"170":{"position":[[449,10],[708,10]]},"174":{"position":[[160,10]]},"177":{"position":[[82,10]]},"183":{"position":[[83,11],[766,13]]},"185":{"position":[[289,11],[391,10],[492,10],[654,11],[705,11],[1230,9],[1578,10],[1622,10],[1812,10],[1963,10],[2111,11]]},"187":{"position":[[812,10],[867,10],[910,10],[1054,10]]},"195":{"position":[[157,9],[174,9],[279,10],[347,9],[443,10]]},"197":{"position":[[165,9],[233,9],[276,10]]},"199":{"position":[[20,10],[145,11],[213,11],[374,9],[451,9],[543,9],[600,9],[635,9],[703,10],[830,10],[928,9],[1085,10],[1166,10]]},"201":{"position":[[24,10]]},"203":{"position":[[34,9]]},"205":{"position":[[591,9],[979,9]]},"209":{"position":[[851,9]]},"214":{"position":[[300,10],[346,9]]},"228":{"position":[[26,10]]},"232":{"position":[[217,9]]},"234":{"position":[[192,9],[219,10],[408,9],[630,10],[663,11]]},"246":{"position":[[214,10],[264,11]]},"366":{"position":[[356,9]]},"379":{"position":[[257,10]]},"383":{"position":[[42,9]]},"389":{"position":[[84,10]]},"393":{"position":[[79,10],[523,10]]},"554":{"position":[[242,9]]}}}],["componenta",{"_index":1099,"t":{"158":{"position":[[69,10],[537,11]]}}}],["componentb",{"_index":1103,"t":{"158":{"position":[[320,10],[472,11]]}}}],["components/featur",{"_index":1243,"t":{"185":{"position":[[836,19]]}}}],["compos",{"_index":704,"t":{"104":{"position":[[49,11]]},"110":{"position":[[0,11],[119,11],[261,11],[488,12]]},"172":{"position":[[25,10]]},"177":{"position":[[203,11]]},"185":{"position":[[2417,12]]},"195":{"position":[[133,11]]},"222":{"position":[[23,11]]},"232":{"position":[[17,10],[270,11],[385,10],[531,10]]},"234":{"position":[[293,11],[323,10],[535,12]]}}}],["composable.t",{"_index":736,"t":{"110":{"position":[[302,14]]}}}],["composables/appl",{"_index":1187,"t":{"172":{"position":[[379,26],[617,26]]}}}],["composed_valu",{"_index":1727,"t":{"311":{"position":[[510,15]]}}}],["composit",{"_index":735,"t":{"110":{"position":[[224,11]]}}}],["comput",{"_index":1596,"t":{"268":{"position":[[44,12],[107,11],[173,11]]}}}],["concept",{"_index":1225,"t":{"183":{"position":[[501,8]]},"346":{"position":[[97,8]]},"427":{"position":[[149,7]]}}}],["concern",{"_index":1012,"t":{"146":{"position":[[1970,9]]},"183":{"position":[[420,7]]},"248":{"position":[[241,8],[1204,7]]},"258":{"position":[[107,9],[1333,7]]},"260":{"position":[[494,8]]}}}],["condit",{"_index":1594,"t":{"266":{"position":[[577,9],[755,10]]},"315":{"position":[[370,10],[570,11]]},"366":{"position":[[393,9]]},"375":{"position":[[168,10]]},"385":{"position":[[1677,10]]},"532":{"position":[[252,10]]}}}],["confid",{"_index":1275,"t":{"193":{"position":[[66,11]]}}}],["config",{"_index":876,"t":{"138":{"position":[[1161,6]]},"144":{"position":[[2148,6],[2197,6]]},"309":{"position":[[494,6],[543,6]]},"313":{"position":[[664,6],[804,6]]},"359":{"position":[[72,6]]},"435":{"position":[[352,6]]},"448":{"position":[[64,6]]}}}],["config.controller.t",{"_index":2638,"t":{"551":{"position":[[7,20]]}}}],["config.json",{"_index":2134,"t":{"401":{"position":[[596,12],[658,12],[724,12],[970,12],[1032,12],[1098,12]]},"403":{"position":[[688,12]]}}}],["configmap",{"_index":1711,"t":{"307":{"position":[[207,10]]},"311":{"position":[[112,9],[241,9],[284,9]]}}}],["configmap.yml.j2",{"_index":1721,"t":{"311":{"position":[[202,17]]}}}],["configuar",{"_index":2409,"t":{"502":{"position":[[1541,14]]}}}],["configur",{"_index":118,"t":{"12":{"position":[[243,14]]},"37":{"position":[[945,13],[992,14],[1586,13],[1781,13],[1951,13]]},"39":{"position":[[218,13],[610,15],[792,13],[878,13]]},"49":{"position":[[49,13]]},"156":{"position":[[101,13]]},"162":{"position":[[188,13]]},"211":{"position":[[168,12]]},"270":{"position":[[198,13],[271,15]]},"307":{"position":[[4,13],[231,13],[317,14],[340,13]]},"309":{"position":[[56,13],[209,13],[304,13],[369,13],[387,13]]},"313":{"position":[[11,13],[178,13],[690,14]]},"411":{"position":[[175,10]]},"418":{"position":[[14,9]]},"435":{"position":[[270,13]]},"491":{"position":[[506,13]]},"493":{"position":[[120,13]]},"502":{"position":[[1511,14]]},"535":{"position":[[348,14]]},"551":{"position":[[68,13],[426,15],[702,13]]},"558":{"position":[[157,13]]}}}],["confirmationdialog.composable.t",{"_index":1207,"t":{"177":{"position":[[225,32]]}}}],["conflict",{"_index":1756,"t":{"318":{"position":[[123,9]]},"542":{"position":[[117,9]]}}}],["congratul",{"_index":500,"t":{"53":{"position":[[408,16]]}}}],["conjunct",{"_index":2140,"t":{"403":{"position":[[35,11]]}}}],["connect",{"_index":509,"t":{"55":{"position":[[30,9]]},"98":{"position":[[135,11]]},"397":{"position":[[129,8]]},"401":{"position":[[52,7]]},"435":{"position":[[367,7]]},"502":{"position":[[1171,10],[1356,7],[2531,9],[2921,11],[2972,10],[3420,9],[3939,11],[3990,10],[4359,9],[4830,11],[4881,10]]},"518":{"position":[[429,10],[1173,10],[1236,10]]},"520":{"position":[[547,10],[872,9]]},"523":{"position":[[39,8],[114,11],[246,10],[363,9]]},"525":{"position":[[78,11],[296,9],[424,9]]},"527":{"position":[[327,9]]},"530":{"position":[[437,10]]},"542":{"position":[[1108,9]]},"544":{"position":[[272,9]]},"558":{"position":[[189,12]]}}}],["consid",{"_index":732,"t":{"110":{"position":[[131,8]]},"187":{"position":[[968,8],[1160,8],[1336,8]]},"234":{"position":[[168,9]]},"309":{"position":[[335,8]]},"328":{"position":[[0,8]]},"342":{"position":[[74,11]]},"352":{"position":[[1000,8]]},"442":{"position":[[0,8],[280,8]]}}}],["consider",{"_index":1581,"t":{"262":{"position":[[202,14]]}}}],["consist",{"_index":131,"t":{"14":{"position":[[179,11]]},"31":{"position":[[423,12]]},"33":{"position":[[4,10]]},"37":{"position":[[1604,10]]},"39":{"position":[[155,10],[823,10]]},"96":{"position":[[15,8]]},"146":{"position":[[861,8]]},"148":{"position":[[411,8]]},"164":{"position":[[405,11]]},"177":{"position":[[16,12]]},"307":{"position":[[29,8]]},"313":{"position":[[735,10]]},"542":{"position":[[247,12],[1229,12]]},"549":{"position":[[73,8]]}}}],["consol",{"_index":1100,"t":{"158":{"position":[[113,8]]},"414":{"position":[[259,7]]},"508":{"position":[[71,7]]},"510":{"position":[[10,7]]}}}],["console.log",{"_index":2284,"t":{"448":{"position":[[117,11]]}}}],["consoleerrorspi",{"_index":1382,"t":{"220":{"position":[[36,15]]}}}],["consoleerrorspy.mockrestor",{"_index":1388,"t":{"220":{"position":[[189,30]]}}}],["const",{"_index":610,"t":{"84":{"position":[[111,5]]},"146":{"position":[[114,5]]},"168":{"position":[[569,5],[800,5]]},"172":{"position":[[425,5],[713,5]]},"216":{"position":[[153,5]]},"220":{"position":[[30,5]]},"224":{"position":[[403,5]]},"228":{"position":[[597,5],[700,5]]},"243":{"position":[[944,5],[985,5],[1090,5]]},"268":{"position":[[360,5],[731,5]]},"270":{"position":[[139,5],[192,5]]},"288":{"position":[[651,5],[776,5]]},"303":{"position":[[672,5],[737,5]]},"340":{"position":[[769,5]]},"344":{"position":[[844,5],[923,5]]},"366":{"position":[[716,5]]},"370":{"position":[[1525,5],[1616,5],[1659,5],[1761,5],[1852,5]]},"373":{"position":[[363,5]]},"375":{"position":[[829,5]]},"377":{"position":[[314,5]]},"379":{"position":[[779,5]]},"381":{"position":[[345,5],[1384,5],[1406,5],[1715,5],[1747,5]]}}}],["constantli",{"_index":2612,"t":{"542":{"position":[[2344,10]]}}}],["constructor",{"_index":1624,"t":{"278":{"position":[[56,11],[166,11]]}}}],["constructor(id",{"_index":1808,"t":{"330":{"position":[[179,15]]}}}],["constructor(priv",{"_index":1697,"t":{"303":{"position":[[566,19]]},"320":{"position":[[600,19],[825,19]]},"332":{"position":[[160,19]]},"334":{"position":[[336,19]]},"340":{"position":[[638,19]]},"344":{"position":[[727,19]]},"464":{"position":[[166,19]]}}}],["constructor(prop",{"_index":1625,"t":{"278":{"position":[[178,18]]}}}],["consum",{"_index":2535,"t":{"518":{"position":[[1314,8]]}}}],["contact",{"_index":83,"t":{"8":{"position":[[402,7],[989,7]]}}}],["contain",{"_index":97,"t":{"8":{"position":[[918,8]]},"39":{"position":[[10,8],[410,8],[656,8]]},"138":{"position":[[19,7]]},"158":{"position":[[182,8]]},"162":{"position":[[175,8]]},"170":{"position":[[131,7]]},"174":{"position":[[269,8]]},"183":{"position":[[22,11]]},"185":{"position":[[129,8],[368,7],[635,7],[755,8],[1210,8],[1488,8],[1681,8],[1776,8],[2048,8],[2094,7],[2225,8]]},"205":{"position":[[241,8]]},"209":{"position":[[149,9]]},"211":{"position":[[338,8]]},"252":{"position":[[17,8]]},"254":{"position":[[273,8]]},"258":{"position":[[272,8]]},"262":{"position":[[766,7]]},"311":{"position":[[30,8]]},"313":{"position":[[167,10]]},"330":{"position":[[40,10]]},"334":{"position":[[526,7]]},"340":{"position":[[29,7]]},"357":{"position":[[95,9]]},"370":{"position":[[1188,8]]},"389":{"position":[[21,8]]},"391":{"position":[[19,7]]},"399":{"position":[[229,9]]},"403":{"position":[[135,10]]},"407":{"position":[[7,9],[172,8]]},"409":{"position":[[53,9],[109,9],[272,9],[385,9],[801,10]]},"416":{"position":[[230,10]]},"502":{"position":[[134,7],[1150,8],[1343,9],[1708,10]]},"535":{"position":[[27,10]]}}}],["content",{"_index":272,"t":{"27":{"position":[[691,7]]},"100":{"position":[[258,7]]},"134":{"position":[[151,7],[217,7]]},"177":{"position":[[51,7]]},"183":{"position":[[180,7]]},"298":{"position":[[351,8]]},"442":{"position":[[223,7]]},"489":{"position":[[457,7]]},"502":{"position":[[434,8],[652,8],[2771,7],[2819,7],[3789,7],[3837,7],[4680,7],[4728,7]]}}}],["contentelementresponsefactory.maptoresponse(el",{"_index":1488,"t":{"243":{"position":[[1107,53]]}}}],["context",{"_index":219,"t":{"23":{"position":[[144,7]]},"320":{"position":[[144,7]]},"379":{"position":[[321,7]]},"460":{"position":[[193,7]]},"471":{"position":[[841,8]]}}}],["continu",{"_index":2623,"t":{"542":{"position":[[4020,8]]},"544":{"position":[[430,9]]}}}],["contradict",{"_index":1969,"t":{"370":{"position":[[493,10]]}}}],["contrast",{"_index":2055,"t":{"383":{"position":[[238,8]]},"499":{"position":[[97,8]]}}}],["control",{"_index":1163,"t":{"170":{"position":[[20,10]]},"241":{"position":[[35,12],[118,10],[173,10],[254,10]]},"254":{"position":[[333,10]]},"260":{"position":[[4,11],[181,12]]},"262":{"position":[[774,12]]},"288":{"position":[[41,12],[85,10]]},"290":{"position":[[79,10],[102,10]]},"298":{"position":[[40,10]]},"379":{"position":[[830,12]]},"381":{"position":[[2006,7]]},"391":{"position":[[0,11]]},"393":{"position":[[566,10],[704,7]]},"514":{"position":[[27,10]]},"551":{"position":[[30,10],[147,10]]}}}],["controller/api",{"_index":2089,"t":{"393":{"position":[[261,14]]}}}],["conveni",{"_index":1906,"t":{"350":{"position":[[76,10]]}}}],["convent",{"_index":123,"t":{"14":{"position":[[27,11],[155,11]]},"27":{"position":[[531,10]]},"31":{"position":[[171,10]]},"33":{"position":[[55,11]]},"104":{"position":[[5,11]]},"177":{"position":[[266,10]]},"183":{"position":[[722,10]]}}}],["cooki",{"_index":2331,"t":{"471":{"position":[[999,7]]},"480":{"position":[[161,6]]},"497":{"position":[[2,6],[59,7]]},"499":{"position":[[192,6]]}}}],["cookie_session_refresh_interv",{"_index":2358,"t":{"493":{"position":[[88,31]]}}}],["copi",{"_index":818,"t":{"134":{"position":[[141,5],[207,5]]},"136":{"position":[[235,4]]},"138":{"position":[[570,4]]},"357":{"position":[[458,4]]},"385":{"position":[[2377,4]]},"439":{"position":[[252,7]]},"535":{"position":[[276,4]]}}}],["copymodule.copy(payload)).rejects.tothrow",{"_index":1377,"t":{"218":{"position":[[19,42]]}}}],["copymodulemock.copybysharetoken",{"_index":1392,"t":{"224":{"position":[[464,31]]}}}],["copyprocess",{"_index":1378,"t":{"218":{"position":[[62,12]]}}}],["copyresultmodal.unit.t",{"_index":1355,"t":{"209":{"position":[[509,23]]}}}],["core",{"_index":410,"t":{"43":{"position":[[133,4]]},"187":{"position":[[1138,4]]},"292":{"position":[[23,4]]},"456":{"position":[[4,4]]}}}],["core.autocrlf",{"_index":877,"t":{"138":{"position":[[1183,13]]},"359":{"position":[[94,13]]}}}],["core/error",{"_index":1838,"t":{"336":{"position":[[570,10]]}}}],["correct",{"_index":1503,"t":{"246":{"position":[[489,11]]},"336":{"position":[[1136,7]]},"385":{"position":[[653,7]]},"387":{"position":[[46,7]]},"484":{"position":[[186,7]]}}}],["correctli",{"_index":447,"t":{"45":{"position":[[1186,9]]},"156":{"position":[[177,10]]},"197":{"position":[[181,9]]},"342":{"position":[[183,9],[310,10],[946,10]]},"361":{"position":[[140,9]]},"375":{"position":[[948,9]]},"385":{"position":[[1995,9]]},"393":{"position":[[152,10]]}}}],["correspond",{"_index":244,"t":{"25":{"position":[[634,13]]},"260":{"position":[[24,13]]},"313":{"position":[[331,13],[447,13]]},"523":{"position":[[436,13]]}}}],["cost",{"_index":570,"t":{"70":{"position":[[191,4]]}}}],["coupl",{"_index":1265,"t":{"187":{"position":[[1069,7]]},"262":{"position":[[252,8],[306,8]]},"352":{"position":[[1340,7]]}}}],["cours",{"_index":185,"t":{"21":{"position":[[495,7]]},"27":{"position":[[302,7],[791,6]]},"31":{"position":[[143,8],[801,6],[844,6],[1139,6],[1182,6]]},"136":{"position":[[228,6]]},"275":{"position":[[349,6],[427,6],[447,8]]},"278":{"position":[[98,6]]},"315":{"position":[[635,6]]},"326":{"position":[[782,6]]},"328":{"position":[[116,7]]},"366":{"position":[[698,8],[834,8]]},"499":{"position":[[169,6]]},"520":{"position":[[51,7]]},"537":{"position":[[8,7]]}}}],["course.t",{"_index":1132,"t":{"168":{"position":[[125,10]]}}}],["courseopt",{"_index":960,"t":{"144":{"position":[[3329,23]]}}}],["disadvantag",{"_index":1024,"t":{"146":{"position":[[2782,12]]}}}],["disconnect",{"_index":2615,"t":{"542":{"position":[[2949,11]]}}}],["discord",{"_index":2683,"t":{"558":{"position":[[1507,7]]}}}],["discuss",{"_index":1542,"t":{"252":{"position":[[1103,10]]},"558":{"position":[[2205,10],[2330,10]]}}}],["discussion_enabled=fals",{"_index":2218,"t":{"416":{"position":[[1177,24]]}}}],["display",{"_index":975,"t":{"146":{"position":[[266,7],[2054,7],[2989,7],[3915,10],[4052,7]]},"172":{"position":[[226,7]]},"207":{"position":[[297,7]]},"416":{"position":[[163,9]]},"474":{"position":[[247,9]]},"482":{"position":[[184,9]]},"489":{"position":[[293,7]]},"512":{"position":[[372,9]]}}}],["dist/apps/server/apps/server.app.j",{"_index":644,"t":{"92":{"position":[[47,35],[138,35],[245,35],[372,35],[434,35]]}}}],["distinguish",{"_index":1533,"t":{"250":{"position":[[13,11]]},"370":{"position":[[61,13]]}}}],["distribut",{"_index":2602,"t":{"542":{"position":[[1072,11]]}}}],["div",{"_index":705,"t":{"106":{"position":[[11,4]]},"115":{"position":[[284,4],[322,6]]},"209":{"position":[[306,4]]}}}],["divid",{"_index":937,"t":{"144":{"position":[[1689,8],[1912,7],[2032,7],[2140,7],[2441,7],[2677,9],[3094,8],[3381,7],[3775,7]]}}}],["do",{"_index":1539,"t":{"252":{"position":[[559,5]]}}}],["doc",{"_index":354,"t":{"37":{"position":[[1057,5]]},"39":{"position":[[568,6]]},"59":{"position":[[52,4]]},"74":{"position":[[86,3]]},"102":{"position":[[250,5]]},"146":{"position":[[3597,5]]}}}],["doc.md",{"_index":575,"t":{"74":{"position":[[68,6]]}}}],["docker",{"_index":2109,"t":{"399":{"position":[[390,6],[608,6]]},"401":{"position":[[459,6],[833,6]]},"403":{"position":[[146,6],[250,6]]},"409":{"position":[[46,6],[306,6],[884,6],[937,6]]},"414":{"position":[[60,7],[137,6]]},"416":{"position":[[223,6],[278,6],[358,6]]},"502":{"position":[[1661,6],[1719,6]]},"535":{"position":[[20,6]]}}}],["docker'",{"_index":2408,"t":{"502":{"position":[[1494,8]]}}}],["docker.io/etherpad/etherpad:2.0.0",{"_index":2415,"t":{"502":{"position":[[1847,33],[1909,33]]}}}],["docker.io/mongo",{"_index":2181,"t":{"414":{"position":[[200,15]]}}}],["docker.io/rocketchat/rocket.chat:4.7.2",{"_index":2227,"t":{"416":{"position":[[1461,38]]}}}],["docs/hello.md",{"_index":515,"t":{"57":{"position":[[26,14],[41,13]]},"59":{"position":[[123,13]]}}}],["document",{"_index":389,"t":{"37":{"position":[[2131,13]]},"39":{"position":[[586,13]]},"45":{"position":[[1273,13]]},"49":{"position":[[18,13]]},"55":{"position":[[0,9]]},"57":{"position":[[93,11],[111,8]]},"59":{"position":[[224,11],[447,11]]},"74":{"position":[[9,9],[103,8],[134,8]]},"84":{"position":[[18,13]]},"110":{"position":[[164,10]]},"126":{"position":[[200,13]]},"183":{"position":[[557,13]]},"193":{"position":[[191,10]]},"199":{"position":[[1019,13]]},"236":{"position":[[115,13]]},"254":{"position":[[221,13]]},"288":{"position":[[293,14]]},"309":{"position":[[481,8],[526,8]]},"389":{"position":[[284,10]]},"391":{"position":[[165,13]]},"393":{"position":[[204,14]]},"409":{"position":[[711,13]]},"427":{"position":[[119,13],[198,14]]},"429":{"position":[[247,9],[282,8]]},"431":{"position":[[289,13]]},"450":{"position":[[35,14]]},"502":{"position":[[3157,13]]},"520":{"position":[[999,8]]},"542":{"position":[[184,9],[350,8],[383,9],[472,8],[782,9],[1459,9],[2844,8],[3511,9]]},"551":{"position":[[1038,9]]},"558":{"position":[[1217,13]]}}}],["document.controller.t",{"_index":2639,"t":{"551":{"position":[[122,22]]}}}],["document.service.t",{"_index":2640,"t":{"551":{"position":[[237,19]]}}}],["docusauru",{"_index":452,"t":{"47":{"position":[[36,10],[94,10]]},"49":{"position":[[207,10],[247,10]]},"51":{"position":[[0,10]]},"53":{"position":[[164,10],[289,10]]},"57":{"position":[[82,10]]},"59":{"position":[[0,10],[213,10]]},"67":{"position":[[0,10]]},"72":{"position":[[0,10]]},"78":{"position":[[139,12],[329,12]]},"82":{"position":[[0,10]]},"84":{"position":[[512,10]]}}}],["docusaurus!{{email}}).tohavebeencalledwith",{"_index":1414,"t":{"228":{"position":[[477,62]]}}}],["expect(yourmodule.getloading).tobe(tru",{"_index":1423,"t":{"228":{"position":[[943,41]]}}}],["expect.any(applicationerror",{"_index":1387,"t":{"220":{"position":[[157,28]]}}}],["expect.objectcontaininghello",{"_index":598,"t":{"80":{"position":[[148,10],[254,10]]}}}],["h1>mi",{"_index":551,"t":{"63":{"position":[[193,6]]}}}],["hand",{"_index":1754,"t":{"315":{"position":[[646,6]]}}}],["handl",{"_index":232,"t":{"25":{"position":[[75,6]]},"146":{"position":[[772,6],[5442,7]]},"160":{"position":[[141,8]]},"241":{"position":[[284,6]]},"330":{"position":[[72,6]]},"336":{"position":[[436,6],[1262,7]]},"352":{"position":[[194,6],[260,6],[1232,8]]},"375":{"position":[[936,8]]},"385":{"position":[[1890,7]]},"444":{"position":[[287,6],[349,8]]},"452":{"position":[[72,8]]},"476":{"position":[[276,8]]},"484":{"position":[[119,8]]},"505":{"position":[[14,7]]},"508":{"position":[[326,7],[510,7]]},"532":{"position":[[25,7]]},"540":{"position":[[334,6]]},"542":{"position":[[168,8],[1884,7],[2474,7],[2610,7],[4556,6]]},"544":{"position":[[443,6]]},"546":{"position":[[342,8]]},"558":{"position":[[145,7],[355,8],[424,8],[831,9],[850,7],[962,6]]}}}],["handle(ev",{"_index":1825,"t":{"334":{"position":[[407,13]]}}}],["handler",{"_index":1183,"t":{"172":{"position":[[119,7]]},"328":{"position":[[152,8]]},"334":{"position":[[507,7]]}}}],["happen",{"_index":1973,"t":{"370":{"position":[[1131,6]]}}}],["happi",{"_index":1278,"t":{"193":{"position":[[277,9]]}}}],["hard",{"_index":987,"t":{"146":{"position":[[549,4]]},"158":{"position":[[140,4]]},"211":{"position":[[374,4]]},"318":{"position":[[533,4]]},"352":{"position":[[143,4]]}}}],["hardcoded_valu",{"_index":1726,"t":{"311":{"position":[[485,16]]}}}],["hardwar",{"_index":664,"t":{"98":{"position":[[166,8]]}}}],["haspermiss",{"_index":1595,"t":{"266":{"position":[[660,16]]}}}],["have",{"_index":1579,"t":{"260":{"position":[[350,6]]}}}],["head",{"_index":576,"t":{"74":{"position":[[192,7]]},"146":{"position":[[1326,4]]},"148":{"position":[[635,5]]}}}],["header",{"_index":2149,"t":{"403":{"position":[[466,6],[491,6],[610,7]]}}}],["headless",{"_index":109,"t":{"12":{"position":[[71,8]]}}}],["heavili",{"_index":1041,"t":{"146":{"position":[[3851,7]]}}}],["hello",{"_index":516,"t":{"57":{"position":[[57,5]]},"59":{"position":[[188,5],[365,8]]}}}],["hellodocusauru",{"_index":597,"t":{"80":{"position":[[119,17],[227,17]]}}}],["helloworld.unit.t",{"_index":1322,"t":{"203":{"position":[[92,18]]}}}],["helloworld.vu",{"_index":1321,"t":{"203":{"position":[[77,14]]}}}],["help",{"_index":322,"t":{"35":{"position":[[48,4]]},"39":{"position":[[715,7]]},"41":{"position":[[83,4]]},"199":{"position":[[111,4]]},"205":{"position":[[42,7]]},"224":{"position":[[349,7]]},"284":{"position":[[12,4]]},"305":{"position":[[224,4]]},"346":{"position":[[106,4]]},"373":{"position":[[119,5]]},"375":{"position":[[191,7]]},"379":{"position":[[388,4]]},"439":{"position":[[101,4]]},"446":{"position":[[494,7]]}}}],["helper",{"_index":1942,"t":{"364":{"position":[[259,6]]},"381":{"position":[[1883,6]]}}}],["here",{"_index":672,"t":{"100":{"position":[[125,5],[502,5]]},"102":{"position":[[409,4]]},"106":{"position":[[405,5]]},"108":{"position":[[129,5]]},"112":{"position":[[394,5]]},"138":{"position":[[707,4]]},"151":{"position":[[170,5]]},"168":{"position":[[103,5]]},"172":{"position":[[843,5]]},"174":{"position":[[338,5],[362,5]]},"205":{"position":[[576,4]]},"224":{"position":[[810,5]]},"268":{"position":[[355,4],[622,4]]},"313":{"position":[[129,5]]},"328":{"position":[[408,4],[443,4]]},"352":{"position":[[166,4]]},"357":{"position":[[595,4]]},"373":{"position":[[426,4]]},"399":{"position":[[120,4]]},"427":{"position":[[259,5]]},"502":{"position":[[1243,4]]},"516":{"position":[[36,4]]},"556":{"position":[[119,4]]}}}],["here'",{"_index":1897,"t":{"348":{"position":[[310,6]]},"350":{"position":[[382,6],[931,6]]}}}],["hex",{"_index":774,"t":{"117":{"position":[[305,3]]}}}],["hi",{"_index":522,"t":{"59":{"position":[[156,5]]}}}],["hide",{"_index":2669,"t":{"558":{"position":[[703,5],[754,4]]}}}],["hierarchi",{"_index":744,"t":{"112":{"position":[[126,10]]},"252":{"position":[[612,9]]}}}],["high",{"_index":1286,"t":{"197":{"position":[[296,4]]},"262":{"position":[[234,4]]},"542":{"position":[[4692,4]]}}}],["higher",{"_index":1276,"t":{"193":{"position":[[100,6]]},"248":{"position":[[759,6]]}}}],["highli",{"_index":245,"t":{"25":{"position":[[676,6]]},"148":{"position":[[802,6]]},"542":{"position":[[1750,6]]}}}],["highlight",{"_index":594,"t":{"80":{"position":[[47,13]]},"84":{"position":[[117,9],[383,10],[449,10]]}}}],["hint",{"_index":1123,"t":{"164":{"position":[[309,4]]},"166":{"position":[[393,4]]},"185":{"position":[[787,5]]},"205":{"position":[[924,5]]},"268":{"position":[[450,5]]},"318":{"position":[[431,4]]},"366":{"position":[[53,4]]}}}],["his/her",{"_index":726,"t":{"108":{"position":[[52,7]]}}}],["histori",{"_index":871,"t":{"138":{"position":[[1050,7]]},"357":{"position":[[937,7]]},"450":{"position":[[525,8]]}}}],["history300",{"_index":196,"t":{"21":{"position":[[901,10]]}}}],["hold",{"_index":393,"t":{"39":{"position":[[200,5]]},"486":{"position":[[225,5]]},"530":{"position":[[175,5]]}}}],["homepag",{"_index":444,"t":{"45":{"position":[[1075,8],[1147,8],[1165,8]]}}}],["hook",{"_index":351,"t":{"37":{"position":[[982,5]]},"379":{"position":[[373,5]]},"554":{"position":[[178,4]]},"558":{"position":[[294,4],[581,4]]}}}],["hooks/usemultiplayerstate.t",{"_index":2655,"t":{"554":{"position":[[140,28]]}}}],["horizont",{"_index":2667,"t":{"558":{"position":[[674,10]]}}}],["host",{"_index":994,"t":{"146":{"position":[[983,4]]},"414":{"position":[[332,6]]},"502":{"position":[[2598,5],[2989,4],[3616,5],[4007,4],[4497,5],[4898,4]]},"518":{"position":[[25,4]]}}}],["host'",{"_index":2405,"t":{"502":{"position":[[1371,6]]}}}],["host.docker.intern",{"_index":2403,"t":{"502":{"position":[[1264,20]]}}}],["host=http://localhost:4000",{"_index":2127,"t":{"401":{"position":[[311,26]]}}}],["hostnam",{"_index":2404,"t":{"502":{"position":[[1285,8]]}}}],["hour",{"_index":2356,"t":{"491":{"position":[[494,6]]}}}],["hpi",{"_index":1713,"t":{"309":{"position":[[149,3]]}}}],["hr",{"_index":2668,"t":{"558":{"position":[[693,5]]}}}],["href=\"'mailto",{"_index":1044,"t":{"146":{"position":[[4532,16],[5300,16]]}}}],["href=\"'wikipedia.com'\">link",{"_index":961,"t":{"144":{"position":[[3482,28]]}}}],["html",{"_index":361,"t":{"37":{"position":[[1277,4],[1902,4]]},"39":{"position":[[665,4],[972,4]]},"67":{"position":[[99,5]]},"106":{"position":[[60,4]]},"141":{"position":[[433,4],[557,4],[584,4],[828,4]]},"144":{"position":[[832,4],[873,4],[986,4],[1033,4],[1210,4],[1777,4],[2343,4],[3266,4],[4076,5]]},"146":{"position":[[4811,4]]},"148":{"position":[[1374,4]]},"199":{"position":[[772,4],[792,4]]},"209":{"position":[[31,4],[355,4]]}}}],["html5",{"_index":691,"t":{"102":{"position":[[119,5]]}}}],["http",{"_index":1728,"t":{"311":{"position":[[526,11]]},"336":{"position":[[648,4]]},"340":{"position":[[264,4]]},"523":{"position":[[168,4]]},"551":{"position":[[170,4]]}}}],["http/1.1",{"_index":2424,"t":{"502":{"position":[[2587,8],[2662,8],[3605,8],[3680,8],[4486,8],[4561,8]]}}}],["http://127.0.0.1:9001/api",{"_index":2421,"t":{"502":{"position":[[2476,25]]}}}],["http://127.0.0.1:9001/api/1/createauthorifnotexistsfor\\?apikey\\=381d67e6347d235ac9446da3ea10a82efd6f8ae09fa2e90efeda80f82feeb4fd\\&name\\=michael\\&authormapper\\=7",{"_index":2454,"t":{"502":{"position":[[3230,160]]}}}],["http://127.0.0.1:9001/api/1/createauthorifnotexistsfor\\?apikey\\=secret\\&name\\=michael\\&authormapper\\=7",{"_index":2463,"t":{"502":{"position":[[4227,102]]}}}],["http://\\😅(catscontrol",{"_index":2019,"t":{"379":{"position":[[985,48]]}}}],["moduleref.get(sampleservic",{"_index":2018,"t":{"379":{"position":[[921,44]]}}}],["modules/featur",{"_index":1254,"t":{"187":{"position":[[64,16]]}}}],["modules/modul",{"_index":1899,"t":{"348":{"position":[[356,16],[624,15]]},"350":{"position":[[421,15],[1086,16]]}}}],["modules/modulea",{"_index":1926,"t":{"352":{"position":[[515,19],[817,19]]}}}],["modules/moduleb",{"_index":1927,"t":{"352":{"position":[[606,19],[872,19]]}}}],["modules/modulec/service.t",{"_index":1925,"t":{"352":{"position":[[416,27],[754,27]]}}}],["modules/oth",{"_index":1919,"t":{"350":{"position":[[977,14]]}}}],["modules/us",{"_index":1818,"t":{"334":{"position":[[83,17]]}}}],["mongo",{"_index":2266,"t":{"444":{"position":[[210,5]]},"530":{"position":[[369,5]]}}}],["mongo_url=mongodb://172.29.173.128:27030/rocketchat",{"_index":2200,"t":{"416":{"position":[[407,51]]}}}],["mongocli",{"_index":2289,"t":{"450":{"position":[[141,11]]}}}],["mongodb",{"_index":2065,"t":{"385":{"position":[[1081,7],[2418,7]]},"414":{"position":[[49,7],[108,7],[167,7],[251,7]]},"416":{"position":[[215,7],[305,7]]},"424":{"position":[[59,7],[205,8]]},"502":{"position":[[1163,7]]}}}],["mongomemorydatabasemodul",{"_index":2063,"t":{"385":{"position":[[957,25],[1205,25]]}}}],["mongomemorydatabasemodule.forroot",{"_index":2071,"t":{"385":{"position":[[1437,35]]}}}],["mongoos",{"_index":2272,"t":{"444":{"position":[[497,8]]}}}],["monitor",{"_index":1445,"t":{"238":{"position":[[4,10]]}}}],["more",{"_index":115,"t":{"12":{"position":[[216,4]]},"21":{"position":[[205,4]]},"47":{"position":[[114,4]]},"84":{"position":[[32,4]]},"100":{"position":[[287,4]]},"110":{"position":[[45,4]]},"121":{"position":[[15,4]]},"144":{"position":[[70,4],[79,4],[2929,4]]},"146":{"position":[[595,4],[779,4],[2215,4],[2349,4],[4956,4]]},"211":{"position":[[58,4]]},"224":{"position":[[641,4]]},"234":{"position":[[596,4]]},"248":{"position":[[502,4],[561,4]]},"258":{"position":[[424,4]]},"262":{"position":[[582,4]]},"268":{"position":[[344,4],[611,4]]},"278":{"position":[[141,4],[285,4]]},"282":{"position":[[351,4]]},"307":{"position":[[443,4]]},"315":{"position":[[541,4],[886,4]]},"326":{"position":[[609,4],[639,4]]},"340":{"position":[[37,4]]},"381":{"position":[[2001,4]]},"431":{"position":[[307,4]]}}}],["mount",{"_index":1300,"t":{"199":{"position":[[180,6],[195,6],[337,8]]},"224":{"position":[[829,5]]},"558":{"position":[[411,8],[858,8]]}}}],["mount(yourcomponenttobetest",{"_index":1412,"t":{"228":{"position":[[380,30]]}}}],["mous",{"_index":689,"t":{"102":{"position":[[87,5]]},"214":{"position":[[59,5],[86,5]]}}}],["move",{"_index":683,"t":{"100":{"position":[[402,4]]},"177":{"position":[[322,4]]},"183":{"position":[[756,4]]},"352":{"position":[[349,6]]},"439":{"position":[[299,4]]}}}],["ms",{"_index":1994,"t":{"375":{"position":[[766,2]]}}}],["much",{"_index":456,"t":{"47":{"position":[[109,4]]},"53":{"position":[[503,4]]},"144":{"position":[[1504,4]]},"146":{"position":[[1933,4]]},"148":{"position":[[145,4]]},"370":{"position":[[271,4]]}}}],["multipl",{"_index":156,"t":{"19":{"position":[[139,8],[276,8]]},"21":{"position":[[1044,8],[1271,8]]},"31":{"position":[[343,8]]},"110":{"position":[[518,8]]},"158":{"position":[[603,8]]},"248":{"position":[[619,8],[1215,8]]},"296":{"position":[[122,8]]},"315":{"position":[[763,8]]},"342":{"position":[[845,8]]},"442":{"position":[[355,8]]},"444":{"position":[[606,8]]},"458":{"position":[[93,8]]},"462":{"position":[[164,8]]},"542":{"position":[[799,8]]},"546":{"position":[[156,8]]}}}],["multiplay",{"_index":2656,"t":{"554":{"position":[[196,11],[275,11]]},"558":{"position":[[311,11]]}}}],["mutlipl",{"_index":1438,"t":{"234":{"position":[[284,8]]}}}],["myfeathersserviceadapt",{"_index":1695,"t":{"303":{"position":[[394,27],[539,24]]}}}],["mymenu.component.vu",{"_index":909,"t":{"144":{"position":[[432,20]]}}}],["mymodul",{"_index":1696,"t":{"303":{"position":[[438,8]]}}}],["myreactpag",{"_index":549,"t":{"63":{"position":[[159,13]]}}}],["name",{"_index":128,"t":{"14":{"position":[[85,6]]},"21":{"position":[[636,5]]},"25":{"position":[[234,5]]},"27":{"position":[[7,5],[46,6],[89,5],[162,5],[238,5],[331,5],[427,5],[524,6]]},"31":{"position":[[124,4],[164,6],[231,5]]},"33":{"position":[[31,5],[48,6]]},"53":{"position":[[125,5],[259,5]]},"115":{"position":[[485,4]]},"123":{"position":[[89,4]]},"136":{"position":[[148,7],[262,5]]},"144":{"position":[[3661,5]]},"146":{"position":[[147,5],[201,5],[5406,4]]},"148":{"position":[[92,5],[132,4],[291,6],[406,4],[559,5],[991,4],[1277,4],[1308,5],[1335,5],[1388,5]]},"151":{"position":[[228,4],[368,4]]},"170":{"position":[[500,5],[759,5]]},"177":{"position":[[29,5]]},"183":{"position":[[715,6]]},"187":{"position":[[111,4]]},"197":{"position":[[542,6]]},"199":{"position":[[660,4],[729,4]]},"203":{"position":[[16,5]]},"209":{"position":[[246,5]]},"266":{"position":[[4,4],[604,5]]},"268":{"position":[[121,5],[220,5],[636,5]]},"278":{"position":[[124,5],[199,5]]},"280":{"position":[[18,5]]},"282":{"position":[[113,5],[225,4]]},"296":{"position":[[67,5],[276,5]]},"311":{"position":[[197,4],[261,5],[279,4]]},"340":{"position":[[300,4]]},"342":{"position":[[1151,5]]},"348":{"position":[[304,5],[373,6]]},"350":{"position":[[1103,6]]},"364":{"position":[[237,5]]},"366":{"position":[[23,4],[217,5],[509,4]]},"368":{"position":[[433,4]]},"397":{"position":[[11,6]]},"399":{"position":[[405,4],[623,4]]},"401":{"position":[[474,4],[848,4]]},"403":{"position":[[161,4],[265,4]]},"414":{"position":[[150,4]]},"439":{"position":[[653,6]]},"458":{"position":[[195,5]]},"497":{"position":[[9,5]]},"502":{"position":[[1828,4]]},"512":{"position":[[132,4]]},"518":{"position":[[477,4],[587,4],[726,4]]},"523":{"position":[[466,4]]}}}],["name/index.t",{"_index":1902,"t":{"348":{"position":[[640,13]]},"350":{"position":[[437,13]]}}}],["name/interfac",{"_index":1916,"t":{"350":{"position":[[615,17]]}}}],["name/service.t",{"_index":1904,"t":{"348":{"position":[[704,17]]},"350":{"position":[[999,15]]}}}],["name:/default",{"_index":1730,"t":{"311":{"position":[[593,14]]}}}],["name:/templates/:appl",{"_index":1720,"t":{"311":{"position":[[168,28]]}}}],["name=some_migration_nam",{"_index":2241,"t":{"429":{"position":[[142,24]]}}}],["namespac",{"_index":69,"t":{"8":{"position":[[217,9],[361,11],[672,9],[953,9]]},"136":{"position":[[338,9]]},"311":{"position":[[294,10],[308,9]]},"355":{"position":[[386,9]]}}}],["natur",{"_index":1342,"t":{"207":{"position":[[135,7]]},"344":{"position":[[307,9]]},"450":{"position":[[207,7]]}}}],["navbar",{"_index":466,"t":{"49":{"position":[[93,6]]}}}],["navig",{"_index":80,"t":{"8":{"position":[[373,8]]},"35":{"position":[[57,8]]},"45":{"position":[[293,8],[511,8],[1131,8]]},"55":{"position":[[73,10]]}}}],["nbc",{"_index":1739,"t":{"313":{"position":[[497,5]]}}}],["near",{"_index":1229,"t":{"183":{"position":[[659,4]]},"542":{"position":[[2591,4]]}}}],["necessari",{"_index":86,"t":{"8":{"position":[[426,9],[1013,9]]},"10":{"position":[[41,9]]},"275":{"position":[[144,10]]},"303":{"position":[[108,9]]},"309":{"position":[[294,9]]},"311":{"position":[[57,9]]},"342":{"position":[[1128,10]]},"348":{"position":[[490,9]]},"389":{"position":[[166,9],[231,9]]}}}],["need",{"_index":168,"t":{"21":{"position":[[57,4]]},"106":{"position":[[255,4]]},"117":{"position":[[122,4],[542,4]]},"126":{"position":[[119,4]]},"138":{"position":[[847,4]]},"148":{"position":[[706,4]]},"158":{"position":[[207,4]]},"166":{"position":[[61,4],[431,4]]},"185":{"position":[[1952,6]]},"187":{"position":[[372,5],[634,5],[780,5],[950,4]]},"197":{"position":[[131,4]]},"205":{"position":[[1000,6]]},"211":{"position":[[51,4]]},"232":{"position":[[255,4]]},"248":{"position":[[1002,4],[1082,4]]},"252":{"position":[[333,5],[376,5]]},"258":{"position":[[366,5],[414,6],[449,5],[1205,5]]},"262":{"position":[[703,4]]},"266":{"position":[[83,4]]},"282":{"position":[[264,5]]},"286":{"position":[[285,4]]},"303":{"position":[[292,4]]},"305":{"position":[[62,4]]},"307":{"position":[[129,5]]},"315":{"position":[[7,4],[870,4]]},"318":{"position":[[225,5]]},"326":{"position":[[120,5],[348,4],[419,4]]},"328":{"position":[[214,5]]},"334":{"position":[[580,5]]},"352":{"position":[[963,4]]},"355":{"position":[[81,4],[270,4],[315,5]]},"357":{"position":[[734,4]]},"368":{"position":[[206,6]]},"450":{"position":[[366,7]]},"480":{"position":[[39,5]]},"502":{"position":[[150,6],[1063,6],[1479,5]]},"542":{"position":[[1710,4],[2333,4]]},"556":{"position":[[570,5]]}}}],["neededparam",{"_index":1615,"t":{"270":{"position":[[145,12]]}}}],["neg",{"_index":1283,"t":{"197":{"position":[[72,8],[310,8]]}}}],["nest",{"_index":957,"t":{"144":{"position":[[3244,6],[3539,6],[3611,6],[3869,6]]},"309":{"position":[[510,4]]},"350":{"position":[[87,4]]},"379":{"position":[[175,4],[349,4]]},"385":{"position":[[924,4]]},"471":{"position":[[555,4]]},"499":{"position":[[113,4]]}}}],["nest.j",{"_index":1449,"t":{"241":{"position":[[3,7]]}}}],["nest:start:consol",{"_index":2255,"t":{"439":{"position":[[68,18],[398,18]]},"512":{"position":[[73,18]]}}}],["nest:start:dev",{"_index":2236,"t":{"424":{"position":[[324,14]]},"535":{"position":[[378,14]]}}}],["nest:start:fil",{"_index":2581,"t":{"535":{"position":[[421,16]]}}}],["nestapp.get(rocketchatservic",{"_index":1708,"t":{"305":{"position":[[425,31]]}}}],["nestj",{"_index":1566,"t":{"258":{"position":[[657,7]]},"305":{"position":[[12,6],[79,6],[275,6]]},"309":{"position":[[328,6]]},"336":{"position":[[137,7],[545,6]]},"340":{"position":[[284,7]]},"379":{"position":[[0,7]]},"381":{"position":[[32,7]]},"551":{"position":[[477,6]]},"561":{"position":[[79,6],[163,6]]}}}],["nestjs/common",{"_index":1803,"t":{"328":{"position":[[287,17]]}}}],["nestjs/cqr",{"_index":1804,"t":{"328":{"position":[[332,15]]},"332":{"position":[[61,15]]},"334":{"position":[[146,15]]}}}],["nestjs/testing.test",{"_index":2009,"t":{"379":{"position":[[272,20]]}}}],["nestwinston",{"_index":1784,"t":{"320":{"position":[[1024,13]]}}}],["network",{"_index":2086,"t":{"391":{"position":[[118,8]]},"502":{"position":[[1384,7],[1503,7]]}}}],["network=\"host",{"_index":2411,"t":{"502":{"position":[[1629,14]]}}}],["never",{"_index":1962,"t":{"368":{"position":[[503,5]]},"375":{"position":[[409,5]]},"450":{"position":[[281,5]]}}}],["new",{"_index":436,"t":{"45":{"position":[[790,3]]},"53":{"position":[[520,3]]},"57":{"position":[[107,3]]},"63":{"position":[[261,3]]},"65":{"position":[[123,3]]},"123":{"position":[[32,3],[150,3],[195,3]]},"144":{"position":[[1753,3],[1900,3],[2988,3]]},"146":{"position":[[2037,3],[2133,3],[2272,3],[4373,3]]},"162":{"position":[[14,3],[95,3]]},"187":{"position":[[11,3],[40,3],[538,3],[563,3]]},"228":{"position":[[616,3]]},"243":{"position":[[72,3]]},"246":{"position":[[315,3]]},"275":{"position":[[443,3]]},"288":{"position":[[657,4]]},"294":{"position":[[184,4]]},"305":{"position":[[187,3]]},"318":{"position":[[359,3],[463,3]]},"324":{"position":[[27,3]]},"338":{"position":[[34,3],[95,3],[162,3]]},"340":{"position":[[1004,3]]},"342":{"position":[[523,3],[630,3],[795,3]]},"352":{"position":[[371,3]]},"385":{"position":[[1489,6],[2151,3]]},"429":{"position":[[45,3]]},"471":{"position":[[1215,3]]},"474":{"position":[[158,3]]},"476":{"position":[[148,3]]},"478":{"position":[[120,3]]},"482":{"position":[[51,3]]},"489":{"position":[[70,3]]},"491":{"position":[[146,3],[549,3]]},"502":{"position":[[116,3],[381,3]]},"520":{"position":[[995,3]]},"525":{"position":[[476,3]]},"527":{"position":[[419,3]]},"537":{"position":[[47,3],[62,3],[115,3]]},"542":{"position":[[614,3],[4118,3]]},"544":{"position":[[396,3]]},"556":{"position":[[499,3],[532,3]]},"558":{"position":[[1239,3]]}}}],["newer",{"_index":1230,"t":{"183":{"position":[[676,5]]}}}],["news].params.t",{"_index":1687,"t":{"296":{"position":[[44,15]]}}}],["news].response.dto",{"_index":1688,"t":{"296":{"position":[[287,20]]}}}],["newsmapper.mapcreatenewstodomain(param",{"_index":1675,"t":{"288":{"position":[[732,40]]}}}],["newsmapper.maptoresponse(new",{"_index":1676,"t":{"288":{"position":[[788,31]]}}}],["newsrepo",{"_index":2069,"t":{"385":{"position":[[1273,9],[1532,11]]}}}],["next",{"_index":881,"t":{"141":{"position":[[155,4]]},"216":{"position":[[204,9]]},"446":{"position":[[133,4]]},"502":{"position":[[1681,5]]}}}],["node",{"_index":21,"t":{"4":{"position":[[84,4]]},"94":{"position":[[125,4]]},"286":{"position":[[51,4],[339,4],[389,4]]},"424":{"position":[[51,4]]}}}],["node.j",{"_index":19,"t":{"4":{"position":[[66,8]]}}}],["node_modul",{"_index":364,"t":{"37":{"position":[[1365,13]]}}}],["node_modules/gulp/bin/gulp.j",{"_index":1662,"t":{"286":{"position":[[344,29],[394,29]]}}}],["non",{"_index":2349,"t":{"489":{"position":[[306,3]]}}}],["none",{"_index":1290,"t":{"197":{"position":[[395,4]]}}}],["note",{"_index":720,"t":{"106":{"position":[[439,5]]},"108":{"position":[[163,5]]},"126":{"position":[[0,5]]},"138":{"position":[[1058,4]]},"144":{"position":[[107,4],[254,4],[1574,4],[2983,4]]},"146":{"position":[[248,4],[2032,4],[5352,4]]},"172":{"position":[[857,5]]},"174":{"position":[[396,5]]},"183":{"position":[[542,5]]},"187":{"position":[[647,5],[1253,5]]},"250":{"position":[[135,4]]},"313":{"position":[[766,4]]},"318":{"position":[[274,4]]},"334":{"position":[[493,4]]},"344":{"position":[[993,4]]},"429":{"position":[[121,4]]},"482":{"position":[[144,4]]},"502":{"position":[[1125,4],[1888,4]]}}}],["notfoundexcept",{"_index":1474,"t":{"243":{"position":[[708,17]]},"288":{"position":[[508,17]]}}}],["noth",{"_index":1066,"t":{"148":{"position":[[460,7]]},"439":{"position":[[794,7]]}}}],["notic",{"_index":918,"t":{"144":{"position":[[794,6]]},"258":{"position":[[890,6]]}}}],["notifications\"]').text",{"_index":1358,"t":{"209":{"position":[[581,24]]}}}],["noun",{"_index":250,"t":{"27":{"position":[[110,4],[183,4],[367,4],[448,4],[456,4]]}}}],["now",{"_index":506,"t":{"53":{"position":[[537,3]]},"57":{"position":[[123,3]]},"63":{"position":[[273,3]]},"65":{"position":[[135,3]]},"70":{"position":[[70,3],[116,3]]},"117":{"position":[[76,4],[365,3],[489,4]]},"224":{"position":[[857,3]]},"344":{"position":[[568,3]]},"468":{"position":[[20,4]]},"502":{"position":[[2307,3]]},"537":{"position":[[351,4]]},"544":{"position":[[194,3]]}}}],["npm",{"_index":103,"t":{"10":{"position":[[73,3]]},"12":{"position":[[86,3],[165,3]]},"37":{"position":[[2029,3]]},"69":{"position":[[32,3]]},"70":{"position":[[36,3]]},"126":{"position":[[50,3]]},"128":{"position":[[113,3],[150,3]]},"130":{"position":[[23,3]]},"132":{"position":[[0,3]]},"166":{"position":[[105,3],[180,3],[357,3]]},"286":{"position":[[164,3],[171,3],[232,3],[424,3]]},"401":{"position":[[349,3],[370,3]]},"411":{"position":[[20,3]]},"424":{"position":[[192,3],[255,3],[316,3]]},"427":{"position":[[169,3]]},"431":{"position":[[135,3]]},"437":{"position":[[109,3]]},"439":{"position":[[0,3],[60,3],[237,3],[390,3],[753,3]]},"448":{"position":[[276,3]]},"512":{"position":[[65,3]]},"535":{"position":[[370,3],[413,3],[507,3],[548,3],[589,3],[621,3],[650,3]]},"561":{"position":[[174,3]]}}}],["npx",{"_index":635,"t":{"88":{"position":[[0,3]]},"90":{"position":[[25,3]]},"92":{"position":[[12,3],[83,3],[174,3],[281,3],[417,3]]},"405":{"position":[[92,3]]},"429":{"position":[[0,3]]},"431":{"position":[[57,3]]},"433":{"position":[[0,3]]},"437":{"position":[[69,3]]},"439":{"position":[[152,3],[705,3]]}}}],["null",{"_index":1157,"t":{"168":{"position":[[744,5]]},"342":{"position":[[920,5],[1033,5]]}}}],["nullabl",{"_index":1876,"t":{"342":{"position":[[886,9],[999,9]]}}}],["number",{"_index":828,"t":{"136":{"position":[[186,6],[286,6]]},"138":{"position":[[440,6],[465,7]]},"144":{"position":[[326,6],[1614,6],[1674,6],[3019,6],[3079,6]]},"197":{"position":[[287,8],[301,8],[319,8],[639,6]]},"355":{"position":[[308,6]]},"357":{"position":[[51,7],[77,6]]},"518":{"position":[[799,6],[1476,6]]},"542":{"position":[[4786,6]]}}}],["numer",{"_index":1967,"t":{"370":{"position":[[359,8]]},"542":{"position":[[4662,8]]}}}],["nuxt",{"_index":2126,"t":{"401":{"position":[[283,4]]},"535":{"position":[[636,5]]}}}],["nx",{"_index":1227,"t":{"183":{"position":[[539,2]]}}}],["nyc",{"_index":1330,"t":{"205":{"position":[[585,3]]}}}],["o'connel",{"_index":1333,"t":{"205":{"position":[[626,9]]}}}],["object",{"_index":253,"t":{"27":{"position":[[150,6],[256,7]]},"37":{"position":[[796,6]]},"39":{"position":[[488,8]]},"144":{"position":[[2155,6]]},"146":{"position":[[619,6],[854,6],[893,6],[1200,6],[4268,6],[4329,6]]},"243":{"position":[[1482,6],[1628,7]]},"256":{"position":[[497,7],[621,7]]},"280":{"position":[[81,6]]},"315":{"position":[[33,7],[95,6],[350,6],[452,6]]},"344":{"position":[[48,7],[366,8],[1202,6],[1281,7]]},"368":{"position":[[333,7]]},"387":{"position":[[87,8]]},"462":{"position":[[413,7]]},"542":{"position":[[589,8],[618,7],[3666,7]]}}}],["objectid",{"_index":2300,"t":{"456":{"position":[[265,9]]}}}],["observ",{"_index":2666,"t":{"558":{"position":[[601,7]]}}}],["occlud",{"_index":1599,"t":{"268":{"position":[[273,7]]}}}],["occur",{"_index":1874,"t":{"342":{"position":[[823,5]]},"352":{"position":[[22,5]]}}}],["offer",{"_index":457,"t":{"47":{"position":[[122,6]]},"546":{"position":[[273,5]]}}}],["offici",{"_index":462,"t":{"49":{"position":[[9,8]]},"556":{"position":[[476,8]]}}}],["oidc",{"_index":2120,"t":{"401":{"position":[[60,6],[428,5],[479,4],[853,4]]}}}],["oidcmock__base_url",{"_index":2123,"t":{"401":{"position":[[203,20]]}}}],["ok",{"_index":2427,"t":{"502":{"position":[[2675,2],[3693,2]]}}}],["okay",{"_index":1427,"t":{"232":{"position":[[78,4]]},"364":{"position":[[370,4]]}}}],["old",{"_index":1759,"t":{"318":{"position":[[397,3]]},"324":{"position":[[57,3]]},"471":{"position":[[813,3],[1108,3],[1163,3]]},"491":{"position":[[119,3]]},"556":{"position":[[381,3]]}}}],["olli",{"_index":1443,"t":{"234":{"position":[[712,5]]}}}],["on",{"_index":305,"t":{"31":{"position":[[443,3]]},"45":{"position":[[413,3],[457,3],[531,3],[566,3]]},"106":{"position":[[148,3]]},"110":{"position":[[146,3],[191,5]]},"144":{"position":[[381,3]]},"146":{"position":[[789,3],[2417,3],[3895,3],[4952,3]]},"151":{"position":[[96,3]]},"183":{"position":[[818,3]]},"209":{"position":[[443,3]]},"234":{"position":[[277,3],[362,3]]},"258":{"position":[[248,3],[434,3]]},"262":{"position":[[592,3]]},"326":{"position":[[364,3],[547,3]]},"336":{"position":[[860,3]]},"338":{"position":[[38,4]]},"352":{"position":[[1050,3]]},"364":{"position":[[202,3]]},"370":{"position":[[781,3],[1197,3],[1335,3]]},"385":{"position":[[2155,4]]},"431":{"position":[[30,3]]},"454":{"position":[[10,3],[42,3]]},"491":{"position":[[150,3]]},"502":{"position":[[1993,3],[2117,3]]},"523":{"position":[[51,3]]},"542":{"position":[[216,3],[993,3],[1351,3],[4314,3]]}}}],["onassetcr",{"_index":2677,"t":{"558":{"position":[[1072,14]]}}}],["onboard",{"_index":11,"t":{"2":{"position":[[107,10]]}}}],["onc",{"_index":105,"t":{"12":{"position":[[0,4]]},"209":{"position":[[795,4]]},"318":{"position":[[546,4]]},"381":{"position":[[1935,5]]},"435":{"position":[[92,4]]},"478":{"position":[[229,4]]},"489":{"position":[[188,4]]},"508":{"position":[[337,4],[521,4],[784,4]]},"542":{"position":[[3936,4],[4398,4]]}}}],["onchangepag",{"_index":2672,"t":{"558":{"position":[[886,13]]}}}],["onchangepres",{"_index":2676,"t":{"558":{"position":[[995,17]]}}}],["onclick",{"_index":622,"t":{"84":{"position":[[269,11]]}}}],["oneof",{"_index":1463,"t":{"243":{"position":[[287,6]]}}}],["onetoon",{"_index":2301,"t":{"456":{"position":[[275,12]]}}}],["onmount",{"_index":2671,"t":{"558":{"position":[[841,8]]}}}],["onredo",{"_index":2674,"t":{"558":{"position":[[954,7]]}}}],["onundo",{"_index":2673,"t":{"558":{"position":[[943,6]]}}}],["open",{"_index":1510,"t":{"248":{"position":[[410,4],[1033,4]]},"262":{"position":[[156,4]]},"385":{"position":[[1885,4]]},"482":{"position":[[23,5]]},"484":{"position":[[269,4]]},"493":{"position":[[236,4]]},"537":{"position":[[214,5]]},"542":{"position":[[3834,5]]},"558":{"position":[[522,7],[1528,4]]}}}],["open/clos",{"_index":1509,"t":{"248":{"position":[[375,11]]}}}],["openapi",{"_index":1547,"t":{"254":{"position":[[244,8]]},"288":{"position":[[135,7]]},"298":{"position":[[67,7],[136,7],[207,7]]}}}],["openid",{"_index":2096,"t":{"397":{"position":[[122,6]]},"401":{"position":[[45,6]]}}}],["openldap",{"_index":2143,"t":{"403":{"position":[[119,8],[169,8],[227,8],[273,8],[331,8]]}}}],["oper",{"_index":1536,"t":{"252":{"position":[[163,9]]},"315":{"position":[[742,9]]},"326":{"position":[[93,9],[140,9]]},"361":{"position":[[131,8]]},"444":{"position":[[225,10]]},"542":{"position":[[3325,10]]},"558":{"position":[[376,11],[506,10],[983,11]]}}}],["oplogs",{"_index":2184,"t":{"414":{"position":[[232,9]]}}}],["opt/keycloak/bin/kc.sh",{"_index":2117,"t":{"399":{"position":[[546,23],[764,23]]},"409":{"position":[[173,23],[958,23]]}}}],["optim",{"_index":2308,"t":{"462":{"position":[[432,12]]},"542":{"position":[[2175,9]]}}}],["optimis",{"_index":1523,"t":{"248":{"position":[[974,12]]}}}],["option",{"_index":32,"t":{"4":{"position":[[215,8]]},"12":{"position":[[267,8]]},"82":{"position":[[107,6],[199,6]]},"115":{"position":[[48,7],[366,7]]},"117":{"position":[[34,8]]},"119":{"position":[[480,7]]},"121":{"position":[[49,7]]},"144":{"position":[[54,7],[149,7],[304,7],[346,7],[413,7],[487,6],[497,9],[618,11],[639,7],[681,7],[1073,6],[1117,7],[1134,6],[1178,7],[1404,7],[1634,7],[1647,7],[1859,6],[2380,6],[2424,7],[2461,6],[2503,6],[2522,7],[2582,11],[2603,7],[2699,7],[3039,7],[3052,7],[3150,7],[3192,7],[3229,7],[3303,6],[3364,7],[3401,6],[3460,7],[3522,7],[3546,7],[3618,6],[3647,9],[3676,7],[3714,6],[3758,7],[3795,6],[3839,7],[3876,7]]},"146":{"position":[[3365,6],[3566,6],[3727,6]]},"148":{"position":[[1460,6]]},"170":{"position":[[233,8]]},"399":{"position":[[209,6]]},"448":{"position":[[218,7],[310,8]]}}}],["optionaldata",{"_index":1835,"t":{"336":{"position":[[382,12]]}}}],["orchestr",{"_index":1233,"t":{"185":{"position":[[168,12],[1244,12]]},"252":{"position":[[424,12]]},"260":{"position":[[521,14]]},"334":{"position":[[558,13]]},"389":{"position":[[30,14],[146,13]]}}}],["order",{"_index":802,"t":{"126":{"position":[[89,5]]},"138":{"position":[[586,5]]},"154":{"position":[[384,5]]},"156":{"position":[[140,5]]},"211":{"position":[[189,5]]},"248":{"position":[[3,5]]},"256":{"position":[[231,5]]},"278":{"position":[[38,6]]},"282":{"position":[[216,5]]},"303":{"position":[[68,5]]},"313":{"position":[[255,6]]},"357":{"position":[[474,5]]},"364":{"position":[[116,5]]},"409":{"position":[[743,5]]},"518":{"position":[[1559,5]]},"535":{"position":[[323,5]]}}}],["organ",{"_index":210,"t":{"21":{"position":[[1415,9]]},"148":{"position":[[1048,9]]},"346":{"position":[[115,8]]}}}],["organization'",{"_index":55,"t":{"6":{"position":[[159,14]]}}}],["origin",{"_index":929,"t":{"144":{"position":[[1201,8]]},"187":{"position":[[586,8]]},"338":{"position":[[51,8]]},"381":{"position":[[724,8]]},"502":{"position":[[2762,6],[3780,6],[4671,6]]}}}],["orm",{"_index":2067,"t":{"385":{"position":[[1154,3],[1601,3],[1757,3]]},"427":{"position":[[246,3]]},"429":{"position":[[10,3],[102,3]]},"431":{"position":[[67,3]]},"433":{"position":[[10,3]]},"437":{"position":[[79,3]]},"439":{"position":[[162,3],[715,3]]},"448":{"position":[[46,3]]},"462":{"position":[[487,4]]}}}],["orm.config.t",{"_index":2250,"t":{"435":{"position":[[314,13]]}}}],["orm/entitymanag",{"_index":2060,"t":{"385":{"position":[[565,17]]}}}],["orphanremov",{"_index":2303,"t":{"456":{"position":[[319,14]]}}}],["other'",{"_index":2614,"t":{"542":{"position":[[2574,7]]},"544":{"position":[[81,7]]}}}],["othermodul",{"_index":1572,"t":{"258":{"position":[[875,11],[974,12]]}}}],["out",{"_index":308,"t":{"31":{"position":[[595,3],[1043,3]]},"138":{"position":[[639,3]]},"357":{"position":[[527,3]]},"379":{"position":[[150,3]]},"397":{"position":[[199,3]]},"444":{"position":[[166,3]]},"495":{"position":[[17,3]]},"502":{"position":[[1408,3]]}}}],["outcom",{"_index":151,"t":{"17":{"position":[[341,8]]},"366":{"position":[[416,7]]}}}],["outdat",{"_index":662,"t":{"98":{"position":[[147,8]]},"450":{"position":[[177,8],[233,8]]}}}],["outer",{"_index":1881,"t":{"344":{"position":[[194,5]]},"370":{"position":[[966,5],[1071,5]]}}}],["outgo",{"_index":1551,"t":{"256":{"position":[[40,8]]}}}],["outlin",{"_index":167,"t":{"21":{"position":[[40,7],[134,7],[247,7],[525,8],[939,7],[1205,8]]},"23":{"position":[[24,9]]},"45":{"position":[[437,8]]}}}],["output",{"_index":934,"t":{"144":{"position":[[1465,6]]},"199":{"position":[[297,7]]},"248":{"position":[[547,6]]},"320":{"position":[[1012,6]]},"446":{"position":[[406,6]]}}}],["output.json",{"_index":650,"t":{"92":{"position":[[473,11]]}}}],["outsid",{"_index":997,"t":{"146":{"position":[[1055,7],[3258,7]]},"187":{"position":[[709,7]]},"252":{"position":[[126,7]]},"254":{"position":[[67,7]]},"348":{"position":[[153,7],[530,7]]},"364":{"position":[[333,8]]},"393":{"position":[[692,7]]},"551":{"position":[[191,7]]}}}],["over",{"_index":797,"t":{"123":{"position":[[145,4]]},"146":{"position":[[4776,4]]},"148":{"position":[[19,4]]},"248":{"position":[[659,4]]},"381":{"position":[[2014,4]]},"416":{"position":[[260,4]]}}}],["overal",{"_index":2161,"t":{"407":{"position":[[396,7]]}}}],["overengini",{"_index":1521,"t":{"248":{"position":[[944,15]]}}}],["overrid",{"_index":785,"t":{"119":{"position":[[399,8]]},"336":{"position":[[523,9]]},"379":{"position":[[442,11]]},"439":{"position":[[464,8],[476,8]]}}}],["overridden",{"_index":429,"t":{"45":{"position":[[70,10]]}}}],["overview",{"_index":2658,"t":{"556":{"position":[[130,8]]}}}],["overwrit",{"_index":752,"t":{"112":{"position":[[281,10]]},"123":{"position":[[49,9]]},"313":{"position":[[547,9]]}}}],["overwrite_setting_show_setup_wizard='complet",{"_index":2225,"t":{"416":{"position":[[1398,47]]}}}],["overwritten",{"_index":2013,"t":{"379":{"position":[[685,11]]}}}],["owner",{"_index":1751,"t":{"315":{"position":[[488,5]]},"456":{"position":[[306,6]]}}}],["p",{"_index":762,"t":{"115":{"position":[[389,2],[431,4],[527,2],[600,4]]},"399":{"position":[[422,1],[437,1],[640,1],[655,1]]},"401":{"position":[[499,1],[873,1]]},"403":{"position":[[188,1],[292,1]]},"416":{"position":[[1449,1]]},"502":{"position":[[1736,1]]}}}],["p27030:27017",{"_index":2180,"t":{"414":{"position":[[184,12]]}}}],["p>thi",{"_index":553,"t":{"63":{"position":[[216,7]]}}}],["packag",{"_index":384,"t":{"37":{"position":[[2009,7],[2033,7]]},"86":{"position":[[22,7]]},"286":{"position":[[134,8]]},"424":{"position":[[162,8]]},"558":{"position":[[1412,7]]}}}],["package.json",{"_index":387,"t":{"37":{"position":[[2057,12]]}}}],["pad",{"_index":618,"t":{"84":{"position":[[230,8]]},"489":{"position":[[248,4],[381,5],[453,3]]},"495":{"position":[[98,4]]}}}],["pad_options_show_chat=\"tru",{"_index":2376,"t":{"502":{"position":[[684,28]]}}}],["page",{"_index":252,"t":{"27":{"position":[[145,4],[176,4],[251,4],[590,5]]},"37":{"position":[[782,6],[791,4]]},"39":{"position":[[483,4]]},"45":{"position":[[461,4],[535,4]]},"51":{"position":[[21,4],[68,5]]},"55":{"position":[[24,5]]},"61":{"position":[[64,5]]},"63":{"position":[[265,4],[321,5]]},"65":{"position":[[92,4],[116,4],[127,4],[186,5]]},"76":{"position":[[121,6],[216,5]]},"151":{"position":[[476,4]]},"160":{"position":[[125,5]]},"170":{"position":[[200,5],[341,5],[356,4]]},"172":{"position":[[216,4]]},"183":{"position":[[856,7]]},"185":{"position":[[97,4],[112,4],[1054,4],[1080,4],[1138,5],[1146,4],[2166,4]]},"558":{"position":[[908,4]]}}}],["page.j",{"_index":544,"t":{"63":{"position":[[36,8],[64,7]]}}}],["page.md",{"_index":556,"t":{"65":{"position":[[39,8],[70,7]]},"76":{"position":[[172,9]]}}}],["page/vuetify.options.t",{"_index":753,"t":{"112":{"position":[[305,44]]}}}],["src/themes/bas",{"_index":750,"t":{"112":{"position":[[235,16]]}}}],["ssl",{"_index":2522,"t":{"518":{"position":[[902,3]]}}}],["sso",{"_index":2099,"t":{"397":{"position":[[195,3]]}}}],["stabil",{"_index":404,"t":{"41":{"position":[[153,10]]},"195":{"position":[[306,9]]}}}],["stabl",{"_index":406,"t":{"43":{"position":[[29,6]]},"164":{"position":[[453,7]]}}}],["stable_test",{"_index":405,"t":{"43":{"position":[[0,13]]},"45":{"position":[[133,12],[191,12],[676,12]]}}}],["stack",{"_index":1859,"t":{"340":{"position":[[819,6]]}}}],["stage",{"_index":88,"t":{"8":{"position":[[490,7],[664,7],[721,7]]},"43":{"position":[[247,7]]},"45":{"position":[[957,7]]},"307":{"position":[[398,7]]},"313":{"position":[[363,5]]}}}],["staging.env.json",{"_index":90,"t":{"8":{"position":[[597,16],[831,16]]}}}],["staging_test",{"_index":412,"t":{"43":{"position":[[210,14]]},"45":{"position":[[645,13],[933,13]]}}}],["stagingtemplate.env.json",{"_index":89,"t":{"8":{"position":[[538,24]]},"37":{"position":[[1233,24]]}}}],["standalon",{"_index":536,"t":{"61":{"position":[[53,10]]},"551":{"position":[[402,10]]}}}],["standard",{"_index":392,"t":{"39":{"position":[[173,10]]},"100":{"position":[[29,10],[234,10]]},"119":{"position":[[412,8]]}}}],["start",{"_index":15,"t":{"4":{"position":[[15,8]]},"128":{"position":[[120,5]]},"136":{"position":[[93,5]]},"138":{"position":[[420,5]]},"187":{"position":[[0,8]]},"199":{"position":[[1112,8]]},"207":{"position":[[187,5],[360,5]]},"286":{"position":[[205,5]]},"307":{"position":[[200,6]]},"357":{"position":[[31,5]]},"385":{"position":[[1983,7]]},"399":{"position":[[219,5]]},"401":{"position":[[418,5]]},"409":{"position":[[89,5],[261,5],[367,8],[581,5]]},"414":{"position":[[245,5]]},"424":{"position":[[199,5],[289,5]]},"446":{"position":[[191,5]]},"502":{"position":[[587,6],[1687,5]]},"510":{"position":[[49,5]]},"512":{"position":[[3,5],[163,5],[332,5]]},"520":{"position":[[535,8]]},"523":{"position":[[393,6]]},"544":{"position":[[18,5]]},"561":{"position":[[183,5]]}}}],["start:server:dev",{"_index":2583,"t":{"535":{"position":[[515,16]]}}}],["start:worker:dev",{"_index":2584,"t":{"535":{"position":[[556,16]]}}}],["startup",{"_index":2158,"t":{"407":{"position":[[17,7],[97,8]]}}}],["state",{"_index":685,"t":{"100":{"position":[[467,6]]},"144":{"position":[[3181,5]]},"146":{"position":[[2950,5],[3137,5]]},"185":{"position":[[272,8],[604,5],[1605,8]]},"385":{"position":[[340,5],[425,6],[740,5]]},"429":{"position":[[238,5]]},"446":{"position":[[517,6]]},"525":{"position":[[558,5]]},"542":{"position":[[2013,5],[2901,5]]},"551":{"position":[[1014,5]]},"554":{"position":[[208,6],[287,6]]},"556":{"position":[[297,6]]},"558":{"position":[[76,5],[323,6]]}}}],["stateless",{"_index":1238,"t":{"185":{"position":[[465,9],[1785,9]]}}}],["statement",{"_index":152,"t":{"19":{"position":[[7,10],[80,10],[155,10],[194,10],[292,11],[368,11]]},"284":{"position":[[194,9],[232,9],[302,10]]}}}],["static",{"_index":282,"t":{"29":{"position":[[37,6]]},"67":{"position":[[16,6],[92,6]]},"69":{"position":[[50,6]]},"78":{"position":[[93,6]]}}}],["static/img/docusaurus.png",{"_index":586,"t":{"78":{"position":[[110,28]]}}}],["statistics_reporting=fals",{"_index":2216,"t":{"416":{"position":[[1119,26]]}}}],["statu",{"_index":1448,"t":{"238":{"position":[[66,6]]},"243":{"position":[[264,7],[577,7],[633,7],[689,7]]},"288":{"position":[[326,7],[377,7],[433,7],[489,7]]},"435":{"position":[[68,6]]}}}],["stay",{"_index":834,"t":{"136":{"position":[[382,4]]},"141":{"position":[[531,5]]},"164":{"position":[[447,5]]},"248":{"position":[[1097,4]]},"355":{"position":[[205,4]]}}}],["step",{"_index":45,"t":{"6":{"position":[[47,6]]},"14":{"position":[[121,4]]},"21":{"position":[[276,5],[964,5],[1261,5]]},"23":{"position":[[433,5]]},"25":{"position":[[0,4],[42,4],[612,4]]},"31":{"position":[[0,4],[42,4],[186,4],[330,5],[454,4],[569,4],[610,5],[687,4],[917,4],[1054,5]]},"33":{"position":[[141,4]]},"37":{"position":[[865,4]]},"39":{"position":[[501,4]]},"141":{"position":[[160,4]]},"286":{"position":[[194,4]]},"318":{"position":[[76,4],[347,4],[380,4],[473,4],[493,4]]},"389":{"position":[[241,6]]},"401":{"position":[[107,5]]},"424":{"position":[[278,4]]},"482":{"position":[[13,5]]},"502":{"position":[[100,6]]},"542":{"position":[[3038,4],[3393,4],[3758,4]]}}}],["step_definit",{"_index":347,"t":{"37":{"position":[[845,17]]}}}],["stick",{"_index":1429,"t":{"232":{"position":[[161,5]]},"364":{"position":[[276,5]]}}}],["still",{"_index":932,"t":{"144":{"position":[[1429,5]]},"146":{"position":[[4696,5]]},"148":{"position":[[664,5]]},"252":{"position":[[1087,5]]},"262":{"position":[[124,5]]},"309":{"position":[[24,5],[123,5]]},"324":{"position":[[83,5]]},"370":{"position":[[678,5]]},"446":{"position":[[354,5]]},"450":{"position":[[494,5]]},"489":{"position":[[438,5]]}}}],["stop",{"_index":2501,"t":{"518":{"position":[[260,4]]}}}],["storag",{"_index":2514,"t":{"518":{"position":[[706,7],[859,7],[913,7]]},"520":{"position":[[915,8]]},"523":{"position":[[325,7]]},"525":{"position":[[692,7]]},"527":{"position":[[577,8]]},"532":{"position":[[140,8],[185,7],[275,8]]},"540":{"position":[[36,7],[139,7],[301,8],[404,7]]},"542":{"position":[[1497,7],[1541,8],[1633,7],[1673,7],[1927,7],[2053,8],[2628,7],[4580,7]]},"546":{"position":[[214,7],[314,8]]},"551":{"position":[[1062,8]]}}}],["storage:dev",{"_index":2582,"t":{"535":{"position":[[438,11]]}}}],["store",{"_index":395,"t":{"39":{"position":[[377,6]]},"162":{"position":[[366,10]]},"164":{"position":[[353,6]]},"168":{"position":[[75,5]]},"172":{"position":[[191,5],[702,10]]},"177":{"position":[[142,6]]},"185":{"position":[[1892,6],[2057,6]]},"195":{"position":[[149,7]]},"224":{"position":[[45,7]]},"228":{"position":[[15,5],[159,5],[550,6]]},"230":{"position":[[23,6]]},"234":{"position":[[527,7],[566,5],[585,6]]},"435":{"position":[[15,6]]},"456":{"position":[[486,5]]},"462":{"position":[[38,5],[222,6]]},"497":{"position":[[28,6]]},"499":{"position":[[180,6]]},"523":{"position":[[292,6]]},"525":{"position":[[104,6],[195,6],[654,6]]},"530":{"position":[[135,5]]},"542":{"position":[[750,5],[868,6],[1155,6],[1774,5],[1841,7],[2075,6],[2118,5],[2189,7],[2260,7],[3238,6],[3649,6],[4645,7]]},"544":{"position":[[213,6],[374,7]]},"551":{"position":[[920,7],[1124,5]]}}}],["store.t",{"_index":2662,"t":{"558":{"position":[[136,8]]}}}],["store/yourmodul",{"_index":1406,"t":{"228":{"position":[[197,21]]}}}],["stores/setup.t",{"_index":2654,"t":{"554":{"position":[[0,15]]}}}],["stori",{"_index":136,"t":{"17":{"position":[[91,5]]}}}],["straightforward",{"_index":938,"t":{"144":{"position":[[1799,16]]}}}],["strategi",{"_index":667,"t":{"100":{"position":[[17,11]]},"138":{"position":[[375,9]]},"352":{"position":[[180,10]]},"505":{"position":[[5,8],[264,8]]},"508":{"position":[[46,8]]}}}],["streamlin",{"_index":1714,"t":{"309":{"position":[[256,10]]}}}],["string",{"_index":234,"t":{"25":{"position":[[158,10],[240,10],[311,8],[418,8],[545,8]]},"141":{"position":[[290,7],[843,6]]},"144":{"position":[[853,6],[887,7]]},"146":{"position":[[5276,6]]},"197":{"position":[[460,8],[530,7],[554,7],[619,6]]},"243":{"position":[[1535,6]]},"268":{"position":[[549,6]]},"278":{"position":[[130,7],[205,6],[264,6]]},"294":{"position":[[209,7]]},"320":{"position":[[261,7]]},"324":{"position":[[72,7]]},"336":{"position":[[1002,7],[1017,6]]},"340":{"position":[[677,7],[713,7]]},"342":{"position":[[1157,7]]},"458":{"position":[[201,7]]},"502":{"position":[[1182,7]]},"518":{"position":[[440,6]]}}}],["strong",{"_index":1224,"t":{"183":{"position":[[399,6]]},"303":{"position":[[87,6]]}}}],["strongli",{"_index":874,"t":{"138":{"position":[[1092,8]]},"359":{"position":[[3,8]]},"381":{"position":[[2151,8]]}}}],["structur",{"_index":294,"t":{"31":{"position":[[23,9]]},"35":{"position":[[187,9]]},"110":{"position":[[381,10]]},"144":{"position":[[920,9],[1215,9],[1379,9]]},"146":{"position":[[812,9],[2231,9]]},"148":{"position":[[1446,10]]},"205":{"position":[[85,9]]},"234":{"position":[[144,10]]},"284":{"position":[[20,9]]},"328":{"position":[[30,9]]},"366":{"position":[[194,9]]},"370":{"position":[[20,10],[878,9],[1412,10]]},"385":{"position":[[850,9]]},"427":{"position":[[38,9]]},"458":{"position":[[72,9]]},"460":{"position":[[123,9]]},"462":{"position":[[497,9]]}}}],["stub",{"_index":1400,"t":{"224":{"position":[[847,5]]}}}],["student",{"_index":1752,"t":{"315":{"position":[[604,7]]},"366":{"position":[[672,7]]},"505":{"position":[[43,9]]}}}],["studio",{"_index":814,"t":{"134":{"position":[[20,6],[286,6]]}}}],["style",{"_index":399,"t":{"39":{"position":[[841,7]]},"84":{"position":[[160,8]]},"123":{"position":[[219,5]]},"146":{"position":[[586,8]]},"177":{"position":[[59,5]]},"373":{"position":[[214,5]]}}}],["sub",{"_index":1011,"t":{"146":{"position":[[1853,3],[5525,3]]},"205":{"position":[[298,3]]},"234":{"position":[[215,3]]}}}],["sub)class",{"_index":1591,"t":{"266":{"position":[[365,11]]}}}],["subcompon",{"_index":970,"t":{"144":{"position":[[3977,13]]},"146":{"position":[[940,13]]}}}],["subfold",{"_index":1231,"t":{"183":{"position":[[822,9]]},"187":{"position":[[44,9]]}}}],["subject",{"_index":850,"t":{"138":{"position":[[405,7]]},"357":{"position":[[16,7]]}}}],["submiss",{"_index":1755,"t":{"315":{"position":[[658,11]]}}}],["submissioncontainerelementrespons",{"_index":1459,"t":{"243":{"position":[[212,34]]}}}],["submodul",{"_index":1896,"t":{"346":{"position":[[47,10]]},"348":{"position":[[380,10],[520,9],[691,12]]},"350":{"position":[[177,11],[602,12]]}}}],["submoduleservicenam",{"_index":1903,"t":{"348":{"position":[[663,20]]}}}],["subpag",{"_index":1232,"t":{"185":{"position":[[140,7],[1179,7],[1300,8]]}}}],["subscrib",{"_index":2562,"t":{"523":{"position":[[400,11]]},"525":{"position":[[373,11]]},"542":{"position":[[1323,9],[1440,10]]},"551":{"position":[[606,11]]}}}],["subsequ",{"_index":2280,"t":{"446":{"position":[[36,10]]},"491":{"position":[[5,10]]}}}],["substitut",{"_index":178,"t":{"21":{"position":[[323,11]]},"248":{"position":[[463,12]]}}}],["subtask",{"_index":2276,"t":{"444":{"position":[[681,8]]}}}],["succe",{"_index":1992,"t":{"375":{"position":[[717,7]]}}}],["successfulli",{"_index":2452,"t":{"502":{"position":[[3092,13]]}}}],["such",{"_index":216,"t":{"23":{"position":[[114,4]]},"379":{"position":[[33,5]]},"389":{"position":[[248,4]]},"482":{"position":[[247,5]]}}}],["suffici",{"_index":1598,"t":{"268":{"position":[[246,12]]},"342":{"position":[[452,11]]},"370":{"position":[[430,10]]}}}],["suffix",{"_index":1627,"t":{"280":{"position":[[57,6]]}}}],["suggest",{"_index":1209,"t":{"177":{"position":[[310,11]]},"315":{"position":[[157,8]]},"563":{"position":[[48,9]]}}}],["suit",{"_index":678,"t":{"100":{"position":[[221,5]]},"195":{"position":[[109,6]]}}}],["suitabl",{"_index":2609,"t":{"542":{"position":[[2247,8]]}}}],["summari",{"_index":1453,"t":{"243":{"position":[[53,8],[1295,8]]},"288":{"position":[[225,8]]}}}],["super",{"_index":1856,"t":{"340":{"position":[[723,8]]}}}],["supertest",{"_index":2007,"t":{"379":{"position":[[140,9]]}}}],["support",{"_index":39,"t":{"4":{"position":[[306,7]]},"37":{"position":[[702,8]]},"39":{"position":[[447,9]]},"72":{"position":[[11,8]]},"76":{"position":[[27,10]]},"78":{"position":[[28,10]]},"80":{"position":[[25,9]]},"144":{"position":[[1962,7]]},"429":{"position":[[170,9]]},"540":{"position":[[62,7]]},"551":{"position":[[386,10]]}}}],["sure",{"_index":52,"t":{"6":{"position":[[109,4]]},"98":{"position":[[16,4]]},"138":{"position":[[600,4]]},"144":{"position":[[1442,4]]},"187":{"position":[[1270,4]]},"286":{"position":[[5,4]]},"330":{"position":[[229,4]]},"357":{"position":[[488,4]]},"393":{"position":[[70,4]]},"424":{"position":[[5,4]]},"502":{"position":[[2074,4]]},"542":{"position":[[4463,5]]}}}],["surfac",{"_index":783,"t":{"119":{"position":[[122,8],[196,8],[205,8],[424,7]]},"121":{"position":[[136,9]]}}}],["sv",{"_index":2567,"t":{"530":{"position":[[98,3]]}}}],["swagger",{"_index":1494,"t":{"243":{"position":[[1554,7]]}}}],["sync",{"_index":1998,"t":{"377":{"position":[[449,4]]},"505":{"position":[[26,4],[228,7]]},"508":{"position":[[41,4],[66,4],[321,4],[407,4],[505,4],[589,4],[711,4],[767,4]]},"512":{"position":[[92,4]]}}}],["synchron",{"_index":2275,"t":{"444":{"position":[[640,11]]},"510":{"position":[[59,15]]},"512":{"position":[[13,15],[173,15],[237,11],[307,15],[422,15]]},"514":{"position":[[8,15]]},"525":{"position":[[337,12]]},"540":{"position":[[107,16],[266,17]]},"542":{"position":[[260,12],[716,16],[760,11],[1580,15],[1907,15],[2492,15],[3340,13],[4148,12]]},"544":{"position":[[464,15]]},"546":{"position":[[115,15]]},"556":{"position":[[276,13]]}}}],["synchronisiert",{"_index":2381,"t":{"502":{"position":[[796,15]]}}}],["syncronis",{"_index":2708,"t":{"558":{"position":[[2224,14]]}}}],["synonym",{"_index":2494,"t":{"516":{"position":[[41,12]]}}}],["syntax",{"_index":242,"t":{"25":{"position":[[554,6]]},"27":{"position":[[95,7],[168,7],[264,7],[352,7],[433,7],[643,7]]},"80":{"position":[[40,6]]},"82":{"position":[[25,6]]},"241":{"position":[[86,7]]}}}],["system",{"_index":402,"t":{"41":{"position":[[34,6]]},"51":{"position":[[80,7]]},"112":{"position":[[23,6]]},"162":{"position":[[73,8]]},"246":{"position":[[156,6],[232,7],[340,6],[508,6]]},"252":{"position":[[184,6],[299,7]]},"254":{"position":[[79,7]]},"256":{"position":[[219,8],[262,7]]},"294":{"position":[[35,6]]},"326":{"position":[[703,7]]},"379":{"position":[[201,6]]},"397":{"position":[[67,6]]},"416":{"position":[[350,7]]},"486":{"position":[[34,6]]},"502":{"position":[[291,7]]},"512":{"position":[[144,6],[218,7]]},"542":{"position":[[1386,6]]},"551":{"position":[[728,7]]},"558":{"position":[[369,6],[499,6]]}}}],["systemid",{"_index":1855,"t":{"340":{"position":[[702,10],[871,9],[986,9],[1048,10]]}}}],["sébastien",{"_index":496,"t":{"53":{"position":[[265,9]]}}}],["t",{"_index":1750,"t":{"315":{"position":[[309,3]]}}}],["tab",{"_index":2339,"t":{"482":{"position":[[63,4]]},"493":{"position":[[241,4]]},"537":{"position":[[127,3]]},"567":{"position":[[117,4]]}}}],["tabl",{"_index":166,"t":{"21":{"position":[[19,6],[161,7],[365,6],[1029,5],[1110,5]]},"23":{"position":[[459,6]]},"146":{"position":[[294,5],[300,7],[499,8],[962,6],[1320,5],[1340,5],[1373,5],[1585,5],[1642,5],[1836,5],[2082,5],[2565,5],[3017,5],[3815,5],[3933,6],[4080,5],[4599,5],[4765,5],[4825,5],[5104,5],[5161,5]]},"148":{"position":[[628,6],[1411,6]]},"407":{"position":[[257,5]]}}}],["tablenam",{"_index":2296,"t":{"456":{"position":[[114,10]]}}}],["tag",{"_index":397,"t":{"39":{"position":[[604,5]]},"41":{"position":[[26,7],[78,4]]},"43":{"position":[[426,4],[494,6]]},"45":{"position":[[14,4],[1242,5]]},"51":{"position":[[76,3]]},"53":{"position":[[386,5]]},"183":{"position":[[619,4]]},"209":{"position":[[125,3]]}}}],["tags.md",{"_index":359,"t":{"37":{"position":[[1167,7]]}}}],["take",{"_index":607,"t":{"82":{"position":[[128,4],[206,4]]},"144":{"position":[[1013,4]]},"146":{"position":[[74,4]]},"172":{"position":[[158,5]]},"216":{"position":[[141,4]]},"309":{"position":[[184,5]]},"370":{"position":[[354,4]]},"377":{"position":[[35,4]]},"379":{"position":[[492,5]]},"444":{"position":[[129,4]]}}}],["taken",{"_index":343,"t":{"37":{"position":[[672,5]]},"205":{"position":[[565,5]]}}}],["talk",{"_index":791,"t":{"123":{"position":[[0,4]]},"138":{"position":[[160,4]]}}}],["target",{"_index":2237,"t":{"427":{"position":[[101,6]]},"505":{"position":[[166,6]]},"512":{"position":[[101,8],[116,8],[279,6],[356,7],[462,6]]},"558":{"position":[[642,9]]}}}],["task",{"_index":217,"t":{"23":{"position":[[122,4]]},"27":{"position":[[704,4]]},"315":{"position":[[476,4],[587,4]]},"442":{"position":[[65,5]]},"518":{"position":[[1411,4],[1486,4]]}}}],["task'",{"_index":1753,"t":{"315":{"position":[[628,6]]}}}],["tbd",{"_index":1424,"t":{"230":{"position":[[4,3]]},"234":{"position":[[718,5]]}}}],["tbodi",{"_index":981,"t":{"146":{"position":[[377,7],[490,8],[1571,7],[1653,8]]}}}],["td",{"_index":1017,"t":{"146":{"position":[[2554,4],[2615,5],[4524,4],[4582,5],[4588,4],[4649,5],[5093,4],[5144,5],[5150,4],[5211,5]]}}}],["td>{{user.email}}{{user.id}}{{user.name}}emailidname{{user.email}} Search the documentation - + - + \ No newline at end of file