diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0750d7e7..f204668e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -55,7 +55,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -68,6 +68,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/container-deploy.yml b/.github/workflows/container-deploy.yml index 0ec8f2d6..139aeb45 100644 --- a/.github/workflows/container-deploy.yml +++ b/.github/workflows/container-deploy.yml @@ -220,7 +220,7 @@ jobs: cd ${{ matrix.env }} docker system prune -af - - uses: frankie567/grafana-annotation-action@v1.0.3 + - uses: basos9/grafana-annotation-action@v1.0.3 if: ${{ always() }} with: apiHost: https://grafana.openfoodfacts.org diff --git a/README.md b/README.md index c2f2ff53..980a00fb 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ A typescript migration is in progress, and we are also trying to simplify the AP Please visit the [doc folder](./doc) for more documentation about the Taxonomy Editor. This documentation tries to follow as much as possible the documentation system from [Diataxis](https://diataxis.fr/). + + ## User interface

Screenshots

diff --git a/backend/editor/graph_db.py b/backend/editor/graph_db.py index af6c9f40..3246e76a 100644 --- a/backend/editor/graph_db.py +++ b/backend/editor/graph_db.py @@ -12,6 +12,7 @@ from .exceptions import TransactionMissingError log = logging.getLogger(__name__) +DEFAULT_DB = "neo4j" txn = contextvars.ContextVar("txn") @@ -29,7 +30,7 @@ async def TransactionCtx(): """ global txn, session try: - async with driver.session() as _session: + async with driver.session(database=DEFAULT_DB) as _session: txn_manager = await _session.begin_transaction() async with txn_manager as _txn: txn.set(_txn) @@ -86,5 +87,5 @@ def SyncTransactionCtx(): """ uri = settings.uri driver = neo4j.GraphDatabase.driver(uri) - with driver.session() as _session: + with driver.session(database=DEFAULT_DB) as _session: yield _session diff --git a/backend/poetry.lock b/backend/poetry.lock index a4eee853..3d98f04d 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -494,13 +494,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] diff --git a/doc/assets/taxonomy_editor_overview.excalidraw b/doc/assets/taxonomy_editor_overview.excalidraw new file mode 100644 index 00000000..a883fadd --- /dev/null +++ b/doc/assets/taxonomy_editor_overview.excalidraw @@ -0,0 +1,3305 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 308, + "versionNonce": 1011998789, + "isDeleted": false, + "id": "fANr1Bc2icDIYOpTCGsBi", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 968.2857142857142, + "y": -2128.857142857143, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 441.0000000000001, + "height": 372.9120879120883, + "seed": 1023828366, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "6qbZigqGvNS1W5KG9-xg_", + "type": "arrow" + } + ], + "updated": 1703000255148, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 407, + "versionNonce": 841126148, + "isDeleted": false, + "id": "xzU4P-Qdr-ME3WZs1fURh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 977.9956709956703, + "y": -2371.9523809523785, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "width": 437.2727272727268, + "height": 160.00000000000023, + "seed": 705680402, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "FWe3g91Ab40W6w118hAE0" + }, + { + "id": "6qbZigqGvNS1W5KG9-xg_", + "type": "arrow" + } + ], + "updated": 1703339331399, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 364, + "versionNonce": 490302268, + "isDeleted": false, + "id": "FWe3g91Ab40W6w118hAE0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1156.420036646194, + "y": -2314.4523809523785, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 80.42399597167969, + "height": 45, + "seed": 1632562514, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703339326192, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "User", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "xzU4P-Qdr-ME3WZs1fURh", + "originalText": "User", + "lineHeight": 1.25, + "baseline": 32 + }, + { + "type": "text", + "version": 231, + "versionNonce": 1897507429, + "isDeleted": false, + "id": "FDYYAOaIBrkSwuqa9xpla", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1075.8949916965357, + "y": -2102.285714285712, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 232.23199462890625, + "height": 35, + "seed": 209797710, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255148, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Project selection", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Project selection", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 177, + "versionNonce": 697857035, + "isDeleted": false, + "id": "lHetFPHfz0amw78-jPV3S", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 984.4285714285709, + "y": -2040.301587301585, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 101.42857142857154, + "seed": 1942339662, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ixbQPhFuAo9bi9Lni0Ff3" + }, + { + "id": "MIR80eJWiR-1JOj9fO6yP", + "type": "arrow" + }, + { + "id": "itH4WS1jJqpZ-D3_JKUj2", + "type": "arrow" + } + ], + "updated": 1703000490527, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 151, + "versionNonce": 2141263301, + "isDeleted": false, + "id": "ixbQPhFuAo9bi9Lni0Ff3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1069.1114371163499, + "y": -2024.5873015872992, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 234.91998291015625, + "height": 70, + "seed": 1989198034, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255148, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Create a new \ntaxonomy project", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "lHetFPHfz0amw78-jPV3S", + "originalText": "Create a new \ntaxonomy project", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 180, + "versionNonce": 385340683, + "isDeleted": false, + "id": "5CWjRc7ExyFGXDkFf9PND", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 987.9999999999995, + "y": -1883.714285714283, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 108.02197802197793, + "seed": 1879950674, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "b1CLKy2I8fqqU5CGxN8Ai" + }, + { + "id": "pgFsKgjvkc2Qq5kQPmCi2", + "type": "arrow" + }, + { + "id": "CBGCJoj2Qm3nKVjJ3VNYT", + "type": "arrow" + } + ], + "updated": 1703000255148, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 125, + "versionNonce": 1424233765, + "isDeleted": false, + "id": "b1CLKy2I8fqqU5CGxN8Ai", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1067.0968595232277, + "y": -1864.7032967032942, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 246.0919952392578, + "height": 70, + "seed": 1420917330, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255148, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Select an existing\ntaxonomy project", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5CWjRc7ExyFGXDkFf9PND", + "originalText": "Select an existing\ntaxonomy project", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 492, + "versionNonce": 248825765, + "isDeleted": false, + "id": "exwiuFqmxHVOKOnOrm6Xz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 961.0079365079364, + "y": -1680.904151404149, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 441.0000000000001, + "height": 498.1593406593413, + "seed": 1589700754, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "9rKeXaARa4E0yy4pBfius", + "type": "arrow" + } + ], + "updated": 1703000746431, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 423, + "versionNonce": 1011886213, + "isDeleted": false, + "id": "PeF85KfmvWyjZFoOyqhfU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1052.6121210044698, + "y": -1670.3766788766739, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 270.3960266113281, + "height": 70, + "seed": 1534723666, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "pgFsKgjvkc2Qq5kQPmCi2", + "type": "arrow" + } + ], + "updated": 1703000255148, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Taxonomy\nvisualisation/edition", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Taxonomy\nvisualisation/edition", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 354, + "versionNonce": 923471275, + "isDeleted": false, + "id": "q0Ug5lkzH3h1eLb0QQBll", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 980.2277167277161, + "y": -1444.387667887663, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 101.86813186813198, + "seed": 650734610, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "1AZu5YMggRWvtuAJPPtKP" + }, + { + "id": "9rKeXaARa4E0yy4pBfius", + "type": "arrow" + } + ], + "updated": 1703000768884, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 423, + "versionNonce": 933416933, + "isDeleted": false, + "id": "1AZu5YMggRWvtuAJPPtKP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1014.9725941342451, + "y": -1428.453601953597, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 334.79595947265625, + "height": 70, + "seed": 648419794, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Visualise/edit taxonomy \nnodes", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "q0Ug5lkzH3h1eLb0QQBll", + "originalText": "Visualise/edit taxonomy nodes", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 376, + "versionNonce": 614354155, + "isDeleted": false, + "id": "vEL9orrzcCYeoXC_uHwbs", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 979.18376068376, + "y": -1564.992063492058, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 78.79120879120887, + "seed": 1780944786, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "Q8qUlN9rmtH1enh38ljOt" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 350, + "versionNonce": 887720773, + "isDeleted": false, + "id": "Q8qUlN9rmtH1enh38ljOt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1021.7406192914609, + "y": -1543.0964590964536, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 319.1719970703125, + "height": 35, + "seed": 92491090, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Search taxonomy nodes", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "vEL9orrzcCYeoXC_uHwbs", + "originalText": "Search taxonomy nodes", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 454, + "versionNonce": 280721291, + "isDeleted": false, + "id": "QtjJvCjURh3CA-Dnb80nd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 980.5280830280813, + "y": -1295.8858363858315, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 95.71428571428555, + "seed": 1953549259, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "tRYKUTxjueQlQGI3N7NUl" + }, + { + "id": "3K008XZl2odYMqIJ6rwWj", + "type": "arrow" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 456, + "versionNonce": 75618981, + "isDeleted": false, + "id": "tRYKUTxjueQlQGI3N7NUl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1047.948940048868, + "y": -1283.0286935286886, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 269.4440002441406, + "height": 70, + "seed": 60496491, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Visualise/edit node \nproperties", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "QtjJvCjURh3CA-Dnb80nd", + "originalText": "Visualise/edit node properties", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 582, + "versionNonce": 425463333, + "isDeleted": false, + "id": "LaOsJx5P0Mcmak_mSwcGy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 958.9224664224639, + "y": -1114.7023809523735, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 441.0000000000001, + "height": 295.74175824175893, + "seed": 1203746533, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "WIvG_YyzD_qBJ6GERkEp4", + "type": "arrow" + } + ], + "updated": 1703000889696, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 534, + "versionNonce": 1972060357, + "isDeleted": false, + "id": "6ZtU5uqR8AF9xdINk3KYH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1078.2388173383936, + "y": -1101.0979853479748, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 202.66400146484375, + "height": 35, + "seed": 517050949, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "3K008XZl2odYMqIJ6rwWj", + "type": "arrow" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Project export", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Project export", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 458, + "versionNonce": 1817827851, + "isDeleted": false, + "id": "edzh23__VF9t3DpwNkueN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 978.142246642243, + "y": -914.8891941391839, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 74.39560439560444, + "seed": 567345573, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UL-x3IygvbSSoOlTjdeUI" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 554, + "versionNonce": 38854693, + "isDeleted": false, + "id": "UL-x3IygvbSSoOlTjdeUI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1017.367104517522, + "y": -895.1913919413817, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 325.83599853515625, + "height": 35, + "seed": 636192005, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Export as a Github PR", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "edzh23__VF9t3DpwNkueN", + "originalText": "Export as a Github PR", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 464, + "versionNonce": 801661099, + "isDeleted": false, + "id": "rk3KrTKzjx_T_5KWmCzIh", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 980.1752136752107, + "y": -1032.636446886436, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ebfbee", + "width": 404.2857142857142, + "height": 68.68131868131866, + "seed": 2124323941, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "mn-xQ-vdxrdEerD6x4XUA" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 454, + "versionNonce": 1475056517, + "isDeleted": false, + "id": "mn-xQ-vdxrdEerD6x4XUA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1087.6920713673842, + "y": -1015.7957875457766, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 189.2519989013672, + "height": 35, + "seed": 867682245, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Export locally", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rk3KrTKzjx_T_5KWmCzIh", + "originalText": "Export locally", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 294, + "versionNonce": 1771989131, + "isDeleted": false, + "id": "dn9RDypHbnKuKLWFMWQ_y", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1552.4512154512147, + "y": -2130.6385836385703, + "strokeColor": "#1e1e1e", + "backgroundColor": "#f8f9fa", + "width": 253.00699300699284, + "height": 1315.5315795315782, + "seed": 881329733, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "MIR80eJWiR-1JOj9fO6yP", + "type": "arrow" + }, + { + "id": "zowOBMJaozrx8DF2HOM_n", + "type": "arrow" + }, + { + "id": "IQ9V9_oIsCGcyjljN8XMn", + "type": "arrow" + }, + { + "id": "VHLxf-buve0aNr7G2Ftc8", + "type": "arrow" + }, + { + "id": "CBGCJoj2Qm3nKVjJ3VNYT", + "type": "arrow" + }, + { + "id": "2MmIJ8vhkp2QVl9JY3sP7", + "type": "arrow" + }, + { + "id": "qV51CmJYvn-QAqKjQi-VV", + "type": "arrow" + }, + { + "id": "itH4WS1jJqpZ-D3_JKUj2", + "type": "arrow" + }, + { + "id": "XWpkrUe_JzB0m9vkaWJF4", + "type": "arrow" + }, + { + "id": "c5g9NxM1c3Kbi8Zufp-kt", + "type": "arrow" + }, + { + "id": "9rKeXaARa4E0yy4pBfius", + "type": "arrow" + }, + { + "id": "Gbk2YxK_hQ5lsi2y4RPRx", + "type": "arrow" + }, + { + "id": "BWId-DUjiJe3YeCC8KrJ1", + "type": "arrow" + } + ], + "updated": 1703001197231, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 48, + "versionNonce": 673655045, + "isDeleted": false, + "id": "I-cCjTqYez6m0i-i0X-0_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1629.3769896404879, + "y": -2107.874680874665, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 111.0479965209961, + "height": 35, + "seed": 447199307, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Backend", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Backend", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 113, + "versionNonce": 29737061, + "isDeleted": false, + "id": "IkYhwHr7VwJcx2jygSwcP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1158.21072889674, + "y": -1925.0537795537505, + "strokeColor": "#868e96", + "backgroundColor": "#ebfbee", + "width": 72.56000518798828, + "height": 25, + "seed": 1345713739, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "-- or --", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "-- or --", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 148, + "versionNonce": 918594667, + "isDeleted": false, + "id": "iVFFhUaIVc12zcRygJTi1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1144.0568827428947, + "y": -1479.053779553753, + "strokeColor": "#868e96", + "backgroundColor": "#ebfbee", + "width": 72.56000518798828, + "height": 25, + "seed": 394144261, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "-- or --", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "-- or --", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 183, + "versionNonce": 240806853, + "isDeleted": false, + "id": "7ZIluMrMOQvRl_kuLKqPR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1145.595344281356, + "y": -1331.3614718614451, + "strokeColor": "#868e96", + "backgroundColor": "#ebfbee", + "width": 72.56000518798828, + "height": 25, + "seed": 212494731, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "-- or --", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "-- or --", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "text", + "version": 196, + "versionNonce": 1806098187, + "isDeleted": false, + "id": "_7MzMAy9l6OsRqsebm9tI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1145.1338058198169, + "y": -951.6691641691386, + "strokeColor": "#868e96", + "backgroundColor": "#ebfbee", + "width": 72.56000518798828, + "height": 25, + "seed": 480726661, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "-- or --", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "-- or --", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 689, + "versionNonce": 1007845252, + "isDeleted": false, + "id": "6qbZigqGvNS1W5KG9-xg_", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1203.3336663336895, + "y": -2203.282230059742, + "strokeColor": "#1971c2", + "backgroundColor": "#ebfbee", + "width": 3.2554112554157655, + "height": 67.51442992508464, + "seed": 645621637, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703339331990, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xzU4P-Qdr-ME3WZs1fURh", + "focus": -0.010904275528371117, + "gap": 8.670150892636002 + }, + "endBinding": { + "elementId": "fANr1Bc2icDIYOpTCGsBi", + "focus": 0.11820583278407426, + "gap": 6.910657277514019 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.2554112554157655, + 67.51442992508464 + ] + ] + }, + { + "type": "arrow", + "version": 147, + "versionNonce": 690800043, + "isDeleted": false, + "id": "pgFsKgjvkc2Qq5kQPmCi2", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1189.2060638523067, + "y": -1753.596792328835, + "strokeColor": "#1971c2", + "backgroundColor": "#ebfbee", + "width": 0, + "height": 70, + "seed": 1820781419, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5CWjRc7ExyFGXDkFf9PND", + "focus": 0.004634313098127138, + "gap": 22.095515363470327 + }, + "endBinding": { + "elementId": "PeF85KfmvWyjZFoOyqhfU", + "focus": 0.010325074370854666, + "gap": 13.220113452161058 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + 70 + ] + ] + }, + { + "type": "arrow", + "version": 105, + "versionNonce": 1630273157, + "isDeleted": false, + "id": "3K008XZl2odYMqIJ6rwWj", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1187.2060638523067, + "y": -1181.596792328835, + "strokeColor": "#1971c2", + "backgroundColor": "#ebfbee", + "width": 2, + "height": 70, + "seed": 1826702565, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "startBinding": { + "elementId": "QtjJvCjURh3CA-Dnb80nd", + "focus": -0.031611083239755526, + "gap": 18.57475834271088 + }, + "endBinding": { + "elementId": "6ZtU5uqR8AF9xdINk3KYH", + "focus": 0.04748291139302811, + "gap": 10.498806980860081 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2, + 70 + ] + ] + }, + { + "type": "arrow", + "version": 84, + "versionNonce": 1082483365, + "isDeleted": false, + "id": "MIR80eJWiR-1JOj9fO6yP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1394.4907314907355, + "y": -2027.887112887085, + "strokeColor": "#e03131", + "backgroundColor": "#ebfbee", + "width": 154, + "height": 0, + "seed": 362243083, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "ZEbePf1yIvRl7jfm3q-0j" + } + ], + "updated": 1703001358586, + "link": null, + "locked": false, + "startBinding": { + "elementId": "lHetFPHfz0amw78-jPV3S", + "focus": -0.7552075467563388, + "gap": 5.776445776450373 + }, + "endBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": 0.8437871468078754, + "gap": 3.9604839604791096 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 154, + 0 + ] + ] + }, + { + "type": "text", + "version": 18, + "versionNonce": 1049880037, + "isDeleted": false, + "id": "ZEbePf1yIvRl7jfm3q-0j", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1443.290730727796, + "y": -2000.387112887085, + "strokeColor": "#e03131", + "backgroundColor": "#e3fafc", + "width": 56.400001525878906, + "height": 25, + "seed": 876157061, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "MIR80eJWiR-1JOj9fO6yP", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 116, + "versionNonce": 692576581, + "isDeleted": false, + "id": "5oxeIXVPwsmPv2Gkkwplw", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1966.4907314907355, + "y": -2129.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#f3f0ff", + "width": 408.00000000000006, + "height": 308, + "seed": 263431307, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "zowOBMJaozrx8DF2HOM_n", + "type": "arrow" + }, + { + "id": "IQ9V9_oIsCGcyjljN8XMn", + "type": "arrow" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 41, + "versionNonce": 1486193035, + "isDeleted": false, + "id": "70D5RWrIZNqpDhaUxp-Nj", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2126.4887325893683, + "y": -2110.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#f3f0ff", + "width": 88.00399780273438, + "height": 35, + "seed": 1282589867, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Github", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Github", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 247, + "versionNonce": 1185247397, + "isDeleted": false, + "id": "HacRCX3-vN02J1VABXbwp", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1968.4907314907355, + "y": -1603.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff0f6", + "width": 408.00000000000006, + "height": 308, + "seed": 358353739, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "VHLxf-buve0aNr7G2Ftc8", + "type": "arrow" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 127, + "versionNonce": 1965234219, + "isDeleted": false, + "id": "3n7ajtcJtTwUucN9glZTG", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2128.5987293850226, + "y": -1584.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#f3f0ff", + "width": 91.78400421142578, + "height": 35, + "seed": 1945266667, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Parser", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Parser", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 251, + "versionNonce": 878318597, + "isDeleted": false, + "id": "xUXUQ5uwtAGvltCWcfUiz", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1969.9907314907355, + "y": -1123.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e3fafc", + "width": 408.00000000000006, + "height": 308, + "seed": 1706582667, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 189, + "versionNonce": 1694816971, + "isDeleted": false, + "id": "CTSnMmopNCE-THAXi6_-M", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2136.7247326504034, + "y": -1106.387112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "#f3f0ff", + "width": 84.53199768066406, + "height": 35, + "seed": 1188221515, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Neo4J", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Neo4J", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "arrow", + "version": 200, + "versionNonce": 679430245, + "isDeleted": false, + "id": "zowOBMJaozrx8DF2HOM_n", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1808.0532507551152, + "y": -2006.722425688199, + "strokeColor": "#e03131", + "backgroundColor": "#ebfbee", + "width": 192, + "height": 0, + "seed": 2098184715, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "jRhphoW5fyHg8FOZ63lAc" + } + ], + "updated": 1703001376353, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.8116105156601501, + "gap": 2.5950422969077636 + }, + "endBinding": { + "elementId": "iLOXlR1eQejpUrn8op3z0", + "focus": -0.062402282794943836, + "gap": 4.29462359276306 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 192, + 0 + ] + ] + }, + { + "type": "text", + "version": 9, + "versionNonce": 1427019461, + "isDeleted": false, + "id": "jRhphoW5fyHg8FOZ63lAc", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1858.8532499921757, + "y": -1934.079568545342, + "strokeColor": "#e03131", + "backgroundColor": "#e3fafc", + "width": 56.400001525878906, + "height": 25, + "seed": 2038960651, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "zowOBMJaozrx8DF2HOM_n", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 134, + "versionNonce": 452602667, + "isDeleted": false, + "id": "IQ9V9_oIsCGcyjljN8XMn", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1809.6697088603828, + "y": -2035.887112887085, + "strokeColor": "#e03131", + "backgroundColor": "#ebfbee", + "width": 190, + "height": 0, + "seed": 1899294341, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "eYtz0euCl7dDkMVW2EgIz" + } + ], + "updated": 1703001373799, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.8559495306297041, + "gap": 4.211500402175375 + }, + "endBinding": { + "elementId": "iLOXlR1eQejpUrn8op3z0", + "focus": 0.5126760563380186, + "gap": 4.678165487495448 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 190, + 0 + ] + ] + }, + { + "type": "text", + "version": 20, + "versionNonce": 49903141, + "isDeleted": false, + "id": "eYtz0euCl7dDkMVW2EgIz", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1478.4697080974433, + "y": -2002.387112887085, + "strokeColor": "#e03131", + "backgroundColor": "#e3fafc", + "width": 56.400001525878906, + "height": 25, + "seed": 1073665509, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "IQ9V9_oIsCGcyjljN8XMn", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 267, + "versionNonce": 1545349803, + "isDeleted": false, + "id": "iLOXlR1eQejpUrn8op3z0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2004.3478743478784, + "y": -2060.6013986013713, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 338.2857142857142, + "height": 101.42857142857154, + "seed": 787236485, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "UNvaDnXEPnyJMLa0Rw-0p" + }, + { + "id": "IQ9V9_oIsCGcyjljN8XMn", + "type": "arrow" + }, + { + "id": "zowOBMJaozrx8DF2HOM_n", + "type": "arrow" + } + ], + "updated": 1703000255149, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 276, + "versionNonce": 588343685, + "isDeleted": false, + "id": "UNvaDnXEPnyJMLa0Rw-0p", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2017.2927334438605, + "y": -2044.8871128870856, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 312.39599609375, + "height": 70, + "seed": 2097884645, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Import taxonomy .txt \nfiles", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "iLOXlR1eQejpUrn8op3z0", + "originalText": "Import taxonomy .txt files", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 299, + "versionNonce": 103465861, + "isDeleted": false, + "id": "z1AXnicVIIpiayk6zf6vJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2007.3478743478781, + "y": -1944.6013986013709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 338.2857142857142, + "height": 101.42857142857154, + "seed": 448150411, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "_Zj2TD2KXrM8_DpxmFy_A" + }, + { + "id": "BWId-DUjiJe3YeCC8KrJ1", + "type": "arrow" + } + ], + "updated": 1703001212987, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 323, + "versionNonce": 378794213, + "isDeleted": false, + "id": "_Zj2TD2KXrM8_DpxmFy_A", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2044.8767178188602, + "y": -1928.887112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 263.22802734375, + "height": 70, + "seed": 1729925675, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Store modifications\nas PRs", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z1AXnicVIIpiayk6zf6vJ", + "originalText": "Store modifications\nas PRs", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 636, + "versionNonce": 167392037, + "isDeleted": false, + "id": "VHLxf-buve0aNr7G2Ftc8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1808.4907314907355, + "y": -1979.8871128870853, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 194, + "height": 482.0000000000002, + "seed": 1763575435, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "1_BndhuQECmjHXTjV5rqq" + } + ], + "updated": 1703001380502, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.7878153613267621, + "gap": 3.0325230325280472 + }, + "endBinding": { + "elementId": "Yoqozon-EmDMwf79Jc0Hc", + "focus": -0.7678345032599023, + "gap": 2.857142857142435 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 138.14285714285734, + 51.71428571428578 + ], + [ + 150.64285714285688, + 429.5714285714289 + ], + [ + 194, + 482.0000000000002 + ] + ] + }, + { + "type": "text", + "version": 16, + "versionNonce": 990809157, + "isDeleted": false, + "id": "1_BndhuQECmjHXTjV5rqq", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1853.064778573396, + "y": -1735.5104271234968, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 69, + "height": 25, + "seed": 459004965, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Stream", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "VHLxf-buve0aNr7G2Ftc8", + "originalText": "Stream", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 318, + "versionNonce": 1928509189, + "isDeleted": false, + "id": "Yoqozon-EmDMwf79Jc0Hc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2005.3478743478781, + "y": -1536.6013986013713, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 338.2857142857142, + "height": 101.42857142857154, + "seed": 670653195, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "PP7ohQaJez-WLvXYV9SHu" + }, + { + "id": "VHLxf-buve0aNr7G2Ftc8", + "type": "arrow" + }, + { + "id": "qV51CmJYvn-QAqKjQi-VV", + "type": "arrow" + } + ], + "updated": 1703000416264, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 334, + "versionNonce": 1616821157, + "isDeleted": false, + "id": "PP7ohQaJez-WLvXYV9SHu", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2027.7007229458134, + "y": -1503.3871128870856, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 293.58001708984375, + "height": 35, + "seed": 768590251, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Decode taxonomy file", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Yoqozon-EmDMwf79Jc0Hc", + "originalText": "Decode taxonomy file", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 326, + "versionNonce": 289850821, + "isDeleted": false, + "id": "u9CQ2RvgXDF1aVsr73EGT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2005.3478743478781, + "y": -1414.6013986013709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 338.2857142857142, + "height": 101.42857142857154, + "seed": 1565538469, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ioqAP6ZBvp91aliBhU1Hx" + }, + { + "id": "XjXVPiU3EbQIcpKpbMNq7", + "type": "arrow" + } + ], + "updated": 1703001093078, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 356, + "versionNonce": 2125594373, + "isDeleted": false, + "id": "ioqAP6ZBvp91aliBhU1Hx", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2030.3047268520634, + "y": -1381.387112887085, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 288.37200927734375, + "height": 35, + "seed": 980147205, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Encode taxonomy file", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "u9CQ2RvgXDF1aVsr73EGT", + "originalText": "Encode taxonomy file", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 386, + "versionNonce": 289377931, + "isDeleted": false, + "id": "MWXt-Qyy-QMC5XQUVmjBt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2010.3478743478781, + "y": -1041.6013986013709, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 338.2857142857142, + "height": 186.4285714285716, + "seed": 604816549, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "-AxJnWKwyHnEnU15y2Ss5" + }, + { + "id": "ANPaDw9pEeSFRQtLuS6v2", + "type": "arrow" + }, + { + "id": "2MmIJ8vhkp2QVl9JY3sP7", + "type": "arrow" + }, + { + "id": "XWpkrUe_JzB0m9vkaWJF4", + "type": "arrow" + }, + { + "id": "lMyxbMlWv8oDAUyPIb6Le", + "type": "arrow" + } + ], + "updated": 1703000947520, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 434, + "versionNonce": 674557547, + "isDeleted": false, + "id": "-AxJnWKwyHnEnU15y2Ss5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2065.040734542493, + "y": -983.3871128870851, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 228.89999389648438, + "height": 70, + "seed": 297199621, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Store taxonomy \nas a graph", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "MWXt-Qyy-QMC5XQUVmjBt", + "originalText": "Store taxonomy \nas a graph", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "arrow", + "version": 411, + "versionNonce": 1223228965, + "isDeleted": false, + "id": "ANPaDw9pEeSFRQtLuS6v2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2339.4907314907355, + "y": -1488.387112887085, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 112.5, + "height": 495, + "seed": 1418133349, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "fubwZvNPssWFGZS475-8k" + } + ], + "updated": 1703001394648, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "MWXt-Qyy-QMC5XQUVmjBt", + "focus": 0.3012507949968213, + "gap": 13.357142857143344 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 112.5, + 45 + ], + [ + 112.5, + 445 + ], + [ + 22.5, + 495 + ] + ] + }, + { + "type": "text", + "version": 9, + "versionNonce": 741687589, + "isDeleted": false, + "id": "fubwZvNPssWFGZS475-8k", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1868.2833573518096, + "y": -1247.1504108581871, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 69, + "height": 25, + "seed": 2088292869, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Stream", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ANPaDw9pEeSFRQtLuS6v2", + "originalText": "Stream", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 102, + "versionNonce": 1524353221, + "isDeleted": false, + "id": "CBGCJoj2Qm3nKVjJ3VNYT", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1393.2857142857138, + "y": -1854.696636696608, + "strokeColor": "#9c36b5", + "backgroundColor": "#ebfbee", + "width": 153.02685171752637, + "height": 0, + "seed": 1856170091, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "5qKcNGlrkCp6ISRMxfgM8" + } + ], + "updated": 1703001363274, + "link": null, + "locked": false, + "startBinding": { + "elementId": "5CWjRc7ExyFGXDkFf9PND", + "focus": -0.46274546071038886, + "gap": 1 + }, + "endBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": 0.5804860160936358, + "gap": 6.138649447974444 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 153.02685171752637, + 0 + ] + ] + }, + { + "type": "text", + "version": 22, + "versionNonce": 899218405, + "isDeleted": false, + "id": "5qKcNGlrkCp6ISRMxfgM8", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1491.1125652403007, + "y": -2001.4823509823223, + "strokeColor": "#9c36b5", + "backgroundColor": "#e3fafc", + "width": 56.400001525878906, + "height": 25, + "seed": 1990238987, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000255149, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "CBGCJoj2Qm3nKVjJ3VNYT", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 460, + "versionNonce": 1740672677, + "isDeleted": false, + "id": "2MmIJ8vhkp2QVl9JY3sP7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1807.886779886779, + "y": -1851.2278713036176, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffffff", + "width": 192.67538017538504, + "height": 855.1427570286768, + "seed": 332628875, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001416218, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.6750262953343155, + "gap": 2.428571428571672 + }, + "endBinding": { + "elementId": "MWXt-Qyy-QMC5XQUVmjBt", + "focus": -0.5559961781852024, + "gap": 9.785714285713766 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 71.24680874681326, + 107.43608640949492 + ], + [ + 128.38966588967082, + 779.770634803434 + ], + [ + 192.67538017538504, + 855.1427570286768 + ] + ] + }, + { + "type": "arrow", + "version": 834, + "versionNonce": 1347534251, + "isDeleted": false, + "id": "qV51CmJYvn-QAqKjQi-VV", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1809.3639544721527, + "y": -1949.7342641332953, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 192.5714285714282, + "height": 483.42857142857156, + "seed": 205117547, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "yG4DSSLmcrUBzxlHLjwXz" + } + ], + "updated": 1703001380502, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.7520758493933689, + "gap": 3.905746013945077 + }, + "endBinding": { + "elementId": "Yoqozon-EmDMwf79Jc0Hc", + "focus": -0.8191970523608981, + "gap": 3.412491304296964 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 90.99999999999977, + 46 + ], + [ + 109.21428571428555, + 429.57142857142867 + ], + [ + 192.5714285714282, + 483.42857142857156 + ] + ] + }, + { + "type": "text", + "version": 4, + "versionNonce": 327045963, + "isDeleted": false, + "id": "yG4DSSLmcrUBzxlHLjwXz", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1893.7952945742502, + "y": -1699.141419893967, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 26.780000686645508, + "height": 25, + "seed": 1442053637, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000469754, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "OK", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qV51CmJYvn-QAqKjQi-VV", + "originalText": "OK", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 96, + "versionNonce": 1621051595, + "isDeleted": false, + "id": "itH4WS1jJqpZ-D3_JKUj2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1550.5621600621641, + "y": -1946.1252081251794, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 160.84787434787904, + "height": 1.4234325163531594, + "seed": 475513637, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Gh572BszUaCJiIiF3VdFv" + } + ], + "updated": 1703001360903, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": 0.7165377711938371, + "gap": 1.8890553890504407 + }, + "endBinding": { + "elementId": "lHetFPHfz0amw78-jPV3S", + "focus": 0.7664478884686072, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -160.84787434787904, + -1.4234325163531594 + ] + ] + }, + { + "type": "text", + "version": 11, + "versionNonce": 2018005573, + "isDeleted": false, + "id": "Gh572BszUaCJiIiF3VdFv", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1456.4578740045556, + "y": -1966.4823509823223, + "strokeColor": "#e03131", + "backgroundColor": "#ffffff", + "width": 26.780000686645508, + "height": 25, + "seed": 1831565579, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000503630, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "OK", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "itH4WS1jJqpZ-D3_JKUj2", + "originalText": "OK", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 558, + "versionNonce": 2136266300, + "isDeleted": false, + "id": "XWpkrUe_JzB0m9vkaWJF4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1807.5519713828403, + "y": -1806.9563320479954, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffffff", + "width": 195.71428571428578, + "height": 844.5714795368538, + "seed": 1246911621, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "oluFgflZu2ZD_Pt8x771h" + } + ], + "updated": 1703339344462, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.6279215336413413, + "gap": 2.0937629246328697 + }, + "endBinding": { + "elementId": "MWXt-Qyy-QMC5XQUVmjBt", + "focus": -0.6011218581174367, + "gap": 7.081617250751833 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 50, + 80.28576525113999 + ], + [ + 101.42857142857156, + 756.0000509654251 + ], + [ + 195.71428571428578, + 844.5714795368538 + ] + ] + }, + { + "type": "text", + "version": 8, + "versionNonce": 706475324, + "isDeleted": false, + "id": "oluFgflZu2ZD_Pt8x771h", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1868.9967335319077, + "y": -1390.6812056403667, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffffff", + "width": 24.260000228881836, + "height": 25, + "seed": 1603031467, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703339342448, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Ok", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "XWpkrUe_JzB0m9vkaWJF4", + "originalText": "Ok", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 127, + "versionNonce": 472582987, + "isDeleted": false, + "id": "c5g9NxM1c3Kbi8Zufp-kt", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1551.2392297028032, + "y": -1805.4109224108936, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffffff", + "width": 161.42857142857156, + "height": 1.4285714285715585, + "seed": 1479297003, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "3F7RjvUeJGDsVLOcwRy2X" + } + ], + "updated": 1703001370432, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": 0.5029827015334123, + "gap": 1.2119857484113936 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -161.42857142857156, + -1.4285714285715585 + ] + ] + }, + { + "type": "text", + "version": 16, + "versionNonce": 905093573, + "isDeleted": false, + "id": "3F7RjvUeJGDsVLOcwRy2X", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1507.1349436451947, + "y": -1952.9109224108938, + "strokeColor": "#9c36b5", + "backgroundColor": "#ffffff", + "width": 26.780000686645508, + "height": 25, + "seed": 919736971, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000729634, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "OK", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c5g9NxM1c3Kbi8Zufp-kt", + "originalText": "OK", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 53, + "versionNonce": 1482056421, + "isDeleted": false, + "id": "9rKeXaARa4E0yy4pBfius", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1403.4193029193068, + "y": -1401.8394938394651, + "strokeColor": "#f08c00", + "backgroundColor": "#ffffff", + "width": 148.03191253190812, + "height": 1.423383774344984, + "seed": 304644683, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Qw_B8bHPILTh-EASBP7DC" + } + ], + "updated": 1703001370432, + "link": null, + "locked": false, + "startBinding": { + "elementId": "q0Ug5lkzH3h1eLb0QQBll", + "focus": -0.11839428989663676, + "gap": 18.905871905876438 + }, + "endBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.10377203551354504, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": "triangle", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 148.03191253190812, + -1.423383774344984 + ] + ] + }, + { + "type": "text", + "version": 5, + "versionNonce": 1047858789, + "isDeleted": false, + "id": "Qw_B8bHPILTh-EASBP7DC", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1449.5050164420818, + "y": -1415.0537795537507, + "strokeColor": "#f08c00", + "backgroundColor": "#ffffff", + "width": 56.400001525878906, + "height": 25, + "seed": 1899131563, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000777606, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9rKeXaARa4E0yy4pBfius", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 185, + "versionNonce": 1422467755, + "isDeleted": false, + "id": "Gbk2YxK_hQ5lsi2y4RPRx", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1806.2764457764497, + "y": -1396.1252081251794, + "strokeColor": "#f08c00", + "backgroundColor": "#ffffff", + "width": 207.1428571428571, + "height": 457.1428571428571, + "seed": 182843397, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001394648, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.055462264024373716, + "gap": 1 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "triangle", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 48.571428571428896, + 45.71428571428578 + ], + [ + 82.85714285714289, + 411.42857142857156 + ], + [ + 207.1428571428571, + 457.1428571428571 + ] + ] + }, + { + "type": "arrow", + "version": 73, + "versionNonce": 1222732267, + "isDeleted": false, + "id": "WIvG_YyzD_qBJ6GERkEp4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1403.4193029193068, + "y": -870.4109224108931, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 155.71428571428578, + "height": 1.4285714285715585, + "seed": 1427630827, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "QQwhMuZn5TQ_CrjkTc55I" + } + ], + "updated": 1703001370432, + "link": null, + "locked": false, + "startBinding": { + "elementId": "LaOsJx5P0Mcmak_mSwcGy", + "focus": 0.6295494089233936, + "gap": 3.4968364968428887 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 155.71428571428578, + 1.4285714285715585 + ] + ] + }, + { + "type": "text", + "version": 5, + "versionNonce": 512348427, + "isDeleted": false, + "id": "QQwhMuZn5TQ_CrjkTc55I", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1451.647873584939, + "y": -956.4823509823218, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 56.400001525878906, + "height": 25, + "seed": 925927653, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1703000892365, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "HTTP", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "WIvG_YyzD_qBJ6GERkEp4", + "originalText": "HTTP", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 83, + "versionNonce": 1129512587, + "isDeleted": false, + "id": "-IoeLPcV6vMsm1SjJnnEa", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1804.8478743478784, + "y": -866.1252081251789, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 207.1428571428571, + "height": 4.285714285714221, + "seed": 702557701, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001397633, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 207.1428571428571, + -4.285714285714221 + ] + ] + }, + { + "type": "arrow", + "version": 209, + "versionNonce": 298840453, + "isDeleted": false, + "id": "lMyxbMlWv8oDAUyPIb6Le", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2351.277460092735, + "y": -883.268065268036, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 168.95037922870583, + "height": 492.8571428571429, + "seed": 1968103109, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001394648, + "link": null, + "locked": false, + "startBinding": { + "elementId": "MWXt-Qyy-QMC5XQUVmjBt", + "focus": 0.8175168409783978, + "gap": 2.643871459142929 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 159.78252919303938, + -52.85714285714312 + ], + [ + 161.09222205527777, + -440 + ], + [ + -7.8581571734280615, + -492.8571428571429 + ] + ] + }, + { + "type": "arrow", + "version": 397, + "versionNonce": 679843147, + "isDeleted": false, + "id": "XjXVPiU3EbQIcpKpbMNq7", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2003.419302919307, + "y": -1368.9823509823218, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 200.00000000000023, + "height": 202.8571428571429, + "seed": 1752416395, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001394648, + "link": null, + "locked": false, + "startBinding": { + "elementId": "u9CQ2RvgXDF1aVsr73EGT", + "focus": -0.4848821698532791, + "gap": 1.9285714285708764 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -64.28571428571468, + -21.42857142857156 + ], + [ + -148.57142857142867, + -184.28571428571445 + ], + [ + -200.00000000000023, + -202.8571428571429 + ] + ] + }, + { + "type": "arrow", + "version": 264, + "versionNonce": 1175195691, + "isDeleted": false, + "id": "BWId-DUjiJe3YeCC8KrJ1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1810.5621600621641, + "y": -1633.268065268036, + "strokeColor": "#099268", + "backgroundColor": "#ffffff", + "width": 647.2888770596506, + "height": 265.7142857142858, + "seed": 1021977349, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1703001386143, + "link": null, + "locked": false, + "startBinding": { + "elementId": "dn9RDypHbnKuKLWFMWQ_y", + "focus": -0.23540274766509467, + "gap": 5.103951603956716 + }, + "endBinding": { + "elementId": "z1AXnicVIIpiayk6zf6vJ", + "focus": -0.5397668105150776, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 581.0087523934113, + -20 + ], + [ + 647.2888770596506, + -230 + ], + [ + 530.2409973299096, + -265.7142857142858 + ] + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/doc/assets/taxonomy_editor_overview.png b/doc/assets/taxonomy_editor_overview.png new file mode 100644 index 00000000..a2072679 Binary files /dev/null and b/doc/assets/taxonomy_editor_overview.png differ diff --git a/docker-compose.yml b/docker-compose.yml index d1857a2d..93808b77 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.9" services: neo4j: restart: ${RESTART_POLICY:-no} - image: neo4j:5.3.0-community + image: neo4j:5.14.0-community ports: # admin console - "${NEO4J_ADMIN_EXPOSE:-127.0.0.1:7474}:7474" diff --git a/parser/Makefile b/parser/Makefile index 83a0b6d6..dc6e744e 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -13,7 +13,7 @@ quality: tests: cd .. && docker compose up -d neo4j - pytest . + poetry run pytest . # we do not shutdown neo4j checks: quality tests \ No newline at end of file diff --git a/parser/openfoodfacts_taxonomy_parser/normalizer.py b/parser/openfoodfacts_taxonomy_parser/normalizer.py index 68e1e635..7a16b663 100644 --- a/parser/openfoodfacts_taxonomy_parser/normalizer.py +++ b/parser/openfoodfacts_taxonomy_parser/normalizer.py @@ -7,7 +7,7 @@ import unidecode -def normalizing(line, lang="default", char="-"): +def normalizing(line: str, lang="default", char="-"): """Normalize a string depending on the language code""" line = unicodedata.normalize("NFC", line) diff --git a/parser/openfoodfacts_taxonomy_parser/parser.py b/parser/openfoodfacts_taxonomy_parser/parser.py deleted file mode 100644 index cd346928..00000000 --- a/parser/openfoodfacts_taxonomy_parser/parser.py +++ /dev/null @@ -1,495 +0,0 @@ -import logging -import os -import re -import sys - -import iso639 -from neo4j import GraphDatabase - -from .exception import DuplicateIDError -from .normalizer import normalizing - - -def ellipsis(text, max=20): - """Cut a text adding eventual ellipsis if we do not display it fully""" - return text[:max] + ("..." if len(text) > max else "") - - -class ParserConsoleLogger: - def __init__(self): - self.parsing_warnings = [] # Stores all warning logs - self.parsing_errors = [] # Stores all error logs - - def info(self, msg, *args, **kwargs): - """Stores all parsing info logs""" - logging.info(msg, *args, **kwargs) - - def warning(self, msg, *args, **kwargs): - """Stores all parsing warning logs""" - self.parsing_warnings.append(msg % args) - logging.warning(msg, *args, **kwargs) - - def error(self, msg, *args, **kwargs): - """Stores all parsing error logs""" - self.parsing_errors.append(msg % args) - logging.error(msg, *args, **kwargs) - - -class Parser: - """Parse a taxonomy file and build a neo4j graph""" - - def __init__(self, session): - self.session = session - self.parser_logger = ParserConsoleLogger() - - def create_headernode(self, header, multi_label): - """Create the node for the header""" - query = f""" - CREATE (n:{multi_label}:TEXT) - SET n.id = '__header__' - SET n.preceding_lines= $header - SET n.src_position= 1 - """ - self.session.run(query, header=header) - - def create_node(self, data, multi_label): - """Run the query to create the node with data dictionary""" - position_query = """ - SET n.id = $id - SET n.is_before = $is_before - SET n.preceding_lines = $preceding_lines - SET n.src_position = $src_position - """ - entry_query = "" - if data["id"] == "__footer__": - id_query = f" CREATE (n:{multi_label}:TEXT) \n " - elif data["id"].startswith("synonyms"): - id_query = f" CREATE (n:{multi_label}:SYNONYMS) \n " - elif data["id"].startswith("stopwords"): - id_query = f" CREATE (n:{multi_label}:STOPWORDS) \n " - else: - id_query = f" CREATE (n:{multi_label}:ENTRY) \n " - position_query += " SET n.main_language = $main_language " - if data["parent_tag"]: - entry_query += " SET n.parents = $parent_tag \n" - for key in data: - if key.startswith("prop_"): - entry_query += " SET n." + key + " = $" + key + "\n" - - for key in data: - if key.startswith("tags_"): - entry_query += " SET n." + key + " = $" + key + "\n" - - query = id_query + entry_query + position_query - self.session.run(query, data) - - def normalized_filename(self, filename): - """Add the .txt extension if it is missing in the filename""" - return filename + (".txt" if (len(filename) < 4 or filename[-4:] != ".txt") else "") - - def get_project_name(self, taxonomy_name, branch_name): - """Create a project name for given branch and taxonomy""" - return "p_" + taxonomy_name + "_" + branch_name - - def create_multi_label(self, taxonomy_name, branch_name): - """Create a combined label with taxonomy name and branch name""" - project_name = self.get_project_name(taxonomy_name, branch_name) - return project_name + ":" + ("t_" + taxonomy_name) + ":" + ("b_" + branch_name) - - def file_iter(self, filename, start=0): - """Generator to get the file line by line""" - with open(filename, "r", encoding="utf8") as file: - for line_number, line in enumerate(file): - if line_number < start: - continue - # sanitizing - # remove any space characters at end of line - line = line.rstrip() - # replace ’ (typographique quote) to simple quote ' - line = line.replace("’", "'") - # replace commas that have no space around by a lower comma character - # and do the same for escaped comma (preceded by a \) - # (to distinguish them from commas acting as tags separators) - line = re.sub(r"(\d),(\d)", r"\1‚\2", line) - line = re.sub(r"\\,", "\\‚", line) - # removes parenthesis for roman numeral - line = re.sub(r"\(([ivx]+)\)", r"\1", line, flags=re.I) - yield line_number, line - yield line_number, "" # to end the last entry if not ended - - def remove_stopwords(self, lc, words): - """Remove the stopwords that were read at the beginning of the file""" - # First check if this language has stopwords - if lc in self.stopwords: - words_to_remove = self.stopwords[lc] - new_words = [] - for word in words.split("-"): - if word not in words_to_remove: - new_words.append(word) - return ("-").join(new_words) - else: - return words - - def add_line(self, line): - """ - Get a normalized string but keeping the language code "lc:", - used for id and parent tag - """ - lc, line = line.split(":", 1) - new_line = lc + ":" - new_line += self.remove_stopwords(lc, normalizing(line, lc)) - return new_line - - def get_lc_value(self, line): - """Get the language code "lc" and a list of normalized values""" - lc, line = line.split(":", 1) - new_line = [] - for word in line.split(","): - new_line.append(self.remove_stopwords(lc, normalizing(word, lc))) - return lc, new_line - - def new_node_data(self, is_before): - """To create an empty dictionary that will be used to create node""" - data = { - "id": "", - "main_language": "", - "preceding_lines": [], - "parent_tag": [], - "src_position": None, - "is_before": is_before, - } - return data - - def set_data_id(self, data, id, line_number): - if not data["id"]: - data["id"] = id - else: - raise DuplicateIDError(line_number) - return data - - def header_harvest(self, filename): - """ - Harvest the header (comment with #), - it has its own function because some header has multiple blocks - """ - h = 0 - header = [] - for _, line in self.file_iter(filename): - if not (line) or line[0] == "#": - header.append(line) - else: - break - h += 1 - - # we don't want to eat the comments of the next block - # and it removes the last separating line - for i in range(len(header)): - if header.pop(): - h -= 1 - else: - break - - return header, h - - def entry_end(self, line, data): - """Return True if the block ended""" - # stopwords and synonyms are one-liner, entries are separated by a blank line - if line.startswith("stopwords") or line.startswith("synonyms") or not line: - # can be the end of an block or just additional line separator, - # file_iter() always end with '' - if data["id"]: # to be sure that it's an end - return True - return False - - def remove_separating_line(self, data): - """ - To remove the one separating line that is always there, - between synonyms part and stopwords part and before each entry - """ - is_before = data["is_before"] - # first, check if there is at least one preceding line - if data["preceding_lines"] and not data["preceding_lines"][0]: - if data["id"].startswith("synonyms"): - # it's a synonyms block, - # if the previous block is a stopwords block, - # there is at least one separating line - if "stopwords" in is_before: - data["preceding_lines"].pop(0) - - elif data["id"].startswith("stopwords"): - # it's a stopwords block, - # if the previous block is a synonyms block, - # there is at least one separating line - if "synonyms" in is_before: - data["preceding_lines"].pop(0) - - else: - # it's an entry block, there is always a separating line - data["preceding_lines"].pop(0) - return data - - def harvest(self, filename): - """Transform data from file to dictionary - """ - saved_nodes = [] - index_stopwords = 0 - index_synonyms = 0 - language_code_prefix = re.compile( - r"[a-zA-Z][a-zA-Z][a-zA-Z]?([-_][a-zA-Z][a-zA-Z][a-zA-Z]?)?:" - ) - # Check if it is correctly written - correctly_written = re.compile(r"\w+\Z") - # stopwords will contain a list of stopwords with their language code as key - self.stopwords = {} - - # header - header, next_line = self.header_harvest(filename) - yield header - - # the other entries - data = self.new_node_data(is_before="__header__") - data["is_before"] = "__header__" - for line_number, line in self.file_iter(filename, next_line): - # yield data if block ended - if self.entry_end(line, data): - if data["id"] in saved_nodes: - msg = ( - "Entry with same id %s already created, " - "duplicate id in file at line %s. " - "Node creation cancelled." - ) - self.parser_logger.error(msg, data['id'], data['src_position']) - else: - data = self.remove_separating_line(data) - yield data # another function will use this dictionary to create a node - saved_nodes.append(data["id"]) - data = self.new_node_data(is_before=data["id"]) - - # harvest the line - if not (line) or line[0] == "#": - # comment or blank - data["preceding_lines"].append(line) - else: - line = line.rstrip(",") - if not data["src_position"]: - data["src_position"] = line_number + 1 - if line.startswith("stopwords"): - # general stopwords definition for a language - id = "stopwords:" + str(index_stopwords) - data = self.set_data_id(data, id, line_number) - index_stopwords += 1 - try: - lc, value = self.get_lc_value(line[10:]) - except ValueError: - self.parser_logger.error( - "Missing language code at line %d ? '%s'", - line_number + 1, - ellipsis(line), - ) - else: - data["tags_" + lc] = value - # add the list with its lc - self.stopwords[lc] = value - elif line.startswith("synonyms"): - # general synonyms definition for a language - id = "synonyms:" + str(index_synonyms) - data = self.set_data_id(data, id, line_number) - index_synonyms += 1 - line = line[9:] - tags = [words.strip() for words in line[3:].split(",")] - try: - lc, value = self.get_lc_value(line) - except ValueError: - self.parser_logger.error( - "Missing language code at line %d ? '%s'", - line_number + 1, - ellipsis(line), - ) - else: - data["tags_" + lc] = tags - data["tags_ids_" + lc] = value - elif line[0] == "<": - # parent definition - data["parent_tag"].append(self.add_line(line[1:])) - elif language_code_prefix.match(line): - # synonyms definition - if not data["id"]: - data["id"] = self.add_line(line.split(",", 1)[0]) - # first 2-3 characters before ":" are the language code - data["main_language"] = data["id"].split(":", 1)[0] - # add tags and tagsid - lang, line = line.split(":", 1) - # to transform '-' from language code to '_' - lang = lang.strip().replace("-", "_") - tags_list = [] - tagsids_list = [] - for word in line.split(","): - tags_list.append(word.strip()) - word_normalized = self.remove_stopwords(lang, normalizing(word, lang)) - if word_normalized not in tagsids_list: - # in case 2 normalized synonyms are the same - tagsids_list.append(word_normalized) - data["tags_" + lang] = tags_list - data["tags_ids_" + lang] = tagsids_list - else: - # property definition - property_name = None - try: - property_name, lc, property_value = line.split(":", 2) - except ValueError: - self.parser_logger.error( - "Reading error at line %d, unexpected format: '%s'", - line_number + 1, - ellipsis(line), - ) - else: - # in case there is space before or after the colons - property_name = property_name.strip() - lc = lc.strip().replace("-", "_") - if not ( - correctly_written.match(property_name) and correctly_written.match(lc) - ): - self.parser_logger.error( - "Reading error at line %d, unexpected format: '%s'", - line_number + 1, - ellipsis(line), - ) - if property_name: - data["prop_" + property_name + "_" + lc] = property_value - - data["id"] = "__footer__" - data["preceding_lines"].pop(0) - data["src_position"] = line_number + 1 - len(data["preceding_lines"]) - yield data - - def create_nodes(self, filename, multi_label): - """Adding nodes to database""" - self.parser_logger.info("Creating nodes") - harvested_data = self.harvest(filename) - self.create_headernode(next(harvested_data), multi_label) - for entry in harvested_data: - self.create_node(entry, multi_label) - - def create_previous_link(self, multi_label): - self.parser_logger.info("Creating 'is_before' links") - query = f"MATCH(n:{multi_label}) WHERE n.is_before IS NOT NULL return n.id, n.is_before" - results = self.session.run(query) - for result in results: - id = result["n.id"] - id_previous = result["n.is_before"] - - query = f""" - MATCH(n:{multi_label}) WHERE n.id = $id - MATCH(p:{multi_label}) WHERE p.id= $id_previous - CREATE (p)-[r:is_before]->(n) - RETURN r - """ - results = self.session.run(query, id=id, id_previous=id_previous) - relation = results.values() - if len(relation) > 1: - self.parser_logger.error( - "2 or more 'is_before' links created for ids %s and %s, " - "one of the ids isn't unique", - id, - id_previous, - ) - elif not relation[0]: - self.parser_logger.error("link not created between %s and %s", id, id_previous) - - def parent_search(self, multi_label): - """Get the parent and the child to link""" - query = f"MATCH (n:{multi_label}:ENTRY) WHERE SIZE(n.parents)>0 RETURN n.id, n.parents" - results = self.session.run(query) - for result in results: - id = result["n.id"] - parent_list = result["n.parents"] - for parent in parent_list: - yield parent, id - - def create_child_link(self, multi_label): - """Create the relations between nodes""" - self.parser_logger.info("Creating 'is_child_of' links") - for parent, child_id in self.parent_search(multi_label): - lc, parent_id = parent.split(":") - query = f""" MATCH (p:{multi_label}:ENTRY) WHERE $parent_id IN p.tags_ids_""" + lc - query += f""" - MATCH (c:{multi_label}) WHERE c.id= $child_id - CREATE (c)-[r:is_child_of]->(p) - RETURN r - """ - result = self.session.run(query, parent_id=parent_id, child_id=child_id) - if not result.value(): - self.parser_logger.warning( - f"parent not found for child {child_id} with parent {parent_id}" - ) - - def delete_used_properties(self): - query = "MATCH (n) SET n.is_before = null, n.parents = null" - self.session.run(query) - - def create_fulltext_index(self, taxonomy_name, branch_name): - """Create indexes for search""" - project_name = self.get_project_name(taxonomy_name, branch_name) - query = [ - f"""CREATE FULLTEXT INDEX {project_name+'_SearchIds'} IF NOT EXISTS - FOR (n:{project_name}) ON EACH [n.id]\n""" - ] - query.append("""OPTIONS {indexConfig: {`fulltext.analyzer`: 'keyword'}}""") - self.session.run("".join(query)) - - language_codes = [lang.alpha2 for lang in list(iso639.languages) if lang.alpha2 != ""] - tags_prefixed_lc = ["n.tags_" + lc for lc in language_codes] - tags_prefixed_lc = ", ".join(tags_prefixed_lc) - query = f"""CREATE FULLTEXT INDEX {project_name+'_SearchTags'} IF NOT EXISTS - FOR (n:{project_name}) ON EACH [{tags_prefixed_lc}]""" - self.session.run(query) - - def create_parsing_errors_node(self, taxonomy_name, branch_name): - """Create node to list parsing errors""" - multi_label = self.create_multi_label(taxonomy_name, branch_name) - query = f""" - CREATE (n:{multi_label}:ERRORS) - SET n.id = $project_name - SET n.branch_name = $branch_name - SET n.taxonomy_name = $taxonomy_name - SET n.created_at = datetime() - SET n.warnings = $warnings_list - SET n.errors = $errors_list - """ - params = { - "project_name": self.get_project_name(taxonomy_name, branch_name), - "branch_name": branch_name, - "taxonomy_name": taxonomy_name, - "warnings_list": self.parser_logger.parsing_warnings, - "errors_list": self.parser_logger.parsing_errors, - } - self.session.run(query, params) - - def __call__(self, filename, branch_name, taxonomy_name): - """Process the file""" - filename = self.normalized_filename(filename) - branch_name = normalizing(branch_name, char="_") - multi_label = self.create_multi_label(taxonomy_name, branch_name) - self.create_nodes(filename, multi_label) - self.create_child_link(multi_label) - self.create_previous_link(multi_label) - self.create_fulltext_index(taxonomy_name, branch_name) - self.create_parsing_errors_node(taxonomy_name, branch_name) - # self.delete_used_properties() - - -if __name__ == "__main__": - # Setup logs - logging.basicConfig(handlers=[logging.StreamHandler()], level=logging.INFO) - filename = sys.argv[1] if len(sys.argv) > 1 else "test" - branch_name = sys.argv[2] if len(sys.argv) > 1 else "branch" - taxonomy_name = sys.argv[3] if len(sys.argv) > 1 else filename.rsplit(".", 1)[0] - - # Initialize neo4j - uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687") - driver = GraphDatabase.driver(uri) - session = driver.session() - - # Pass session variable to parser object - parse = Parser(session) - parse(filename, branch_name, taxonomy_name) diff --git a/parser/openfoodfacts_taxonomy_parser/parser/__init__.py b/parser/openfoodfacts_taxonomy_parser/parser/__init__.py new file mode 100644 index 00000000..9da82bc2 --- /dev/null +++ b/parser/openfoodfacts_taxonomy_parser/parser/__init__.py @@ -0,0 +1,2 @@ +from .parser import Parser +from .taxonomy_parser import TaxonomyParser diff --git a/parser/openfoodfacts_taxonomy_parser/exception.py b/parser/openfoodfacts_taxonomy_parser/parser/exception.py similarity index 100% rename from parser/openfoodfacts_taxonomy_parser/exception.py rename to parser/openfoodfacts_taxonomy_parser/parser/exception.py diff --git a/parser/openfoodfacts_taxonomy_parser/parser/logger.py b/parser/openfoodfacts_taxonomy_parser/parser/logger.py new file mode 100644 index 00000000..881a4761 --- /dev/null +++ b/parser/openfoodfacts_taxonomy_parser/parser/logger.py @@ -0,0 +1,26 @@ +import logging + + +class ParserConsoleLogger: + @staticmethod + def ellipsis(text, max=20): + """Cut a text adding eventual ellipsis if we do not display it fully""" + return text[:max] + ("..." if len(text) > max else "") + + def __init__(self): + self.parsing_warnings = [] # Stores all warning logs + self.parsing_errors = [] # Stores all error logs + + def info(self, msg, *args, **kwargs): + """Stores all parsing info logs""" + logging.info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + """Stores all parsing warning logs""" + self.parsing_warnings.append(msg % args) + logging.warning(msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + """Stores all parsing error logs""" + self.parsing_errors.append(msg % args) + logging.error(msg, *args, **kwargs) diff --git a/parser/openfoodfacts_taxonomy_parser/parser/parser.py b/parser/openfoodfacts_taxonomy_parser/parser/parser.py new file mode 100644 index 00000000..a2c97255 --- /dev/null +++ b/parser/openfoodfacts_taxonomy_parser/parser/parser.py @@ -0,0 +1,311 @@ +import collections +import logging +import os +import sys +import timeit + +import iso639 +from neo4j import GraphDatabase, Session, Transaction + +from .logger import ParserConsoleLogger +from ..normalizer import normalizing +from .taxonomy_parser import ( + NodeType, + PreviousLink, + Taxonomy, + TaxonomyParser, + NodeData, + ChildLink, +) + + +class Parser: + """Parse a taxonomy file and build a neo4j graph""" + + def __init__(self, session: Session): + self.session = session + self.parser_logger = ParserConsoleLogger() + + def _get_project_name(self, taxonomy_name: str, branch_name: str): + """Create a project name for given branch and taxonomy""" + return "p_" + taxonomy_name + "_" + branch_name + + def _create_other_node(self, tx: Transaction, node_data: NodeData, project_label: str): + """Create a TEXT, SYNONYMS or STOPWORDS node""" + position_query = """ + SET n.id = $id + SET n.preceding_lines = $preceding_lines + SET n.src_position = $src_position + """ + if node_data.get_node_type() == NodeType.TEXT: + id_query = f"CREATE (n:{project_label}:TEXT) \n" + elif node_data.get_node_type() == NodeType.SYNONYMS: + id_query = f"CREATE (n:{project_label}:SYNONYMS) \n" + elif node_data.get_node_type() == NodeType.STOPWORDS: + id_query = f"CREATE (n:{project_label}:STOPWORDS) \n" + else: + raise ValueError(f"ENTRY nodes should not be passed to this function") + + entry_queries = [f"SET n.{key} = ${key}" for key in node_data.tags] + entry_query = "\n".join(entry_queries) + "\n" + + query = id_query + entry_query + position_query + tx.run(query, node_data.to_dict()) + + def _create_other_nodes(self, other_nodes: list[NodeData], project_label: str): + """Create all TEXT, SYNONYMS and STOPWORDS nodes""" + self.parser_logger.info("Creating TEXT, SYNONYMS and STOPWORDS nodes") + start_time = timeit.default_timer() + + with self.session.begin_transaction() as tx: + for node in other_nodes: + self._create_other_node(tx, node, project_label) + + self.parser_logger.info( + f"Created {len(other_nodes)} TEXT, SYNONYMS and STOPWORDS nodes in {timeit.default_timer() - start_time} seconds" + ) + + def _create_entry_nodes(self, entry_nodes: list[NodeData], project_label: str): + """Create all ENTRY nodes in a single batch query""" + self.parser_logger.info("Creating ENTRY nodes") + start_time = timeit.default_timer() + + base_query = f""" + WITH $entry_nodes as entry_nodes + UNWIND entry_nodes as entry_node + CREATE (n:{project_label}:ENTRY) + SET n.id = entry_node.id + SET n.preceding_lines = entry_node.preceding_lines + SET n.src_position = entry_node.src_position + SET n.main_language = entry_node.main_language + """ + + # we don't know in advance which properties and tags + # we will encounter in the batch + # so we accumulate them in this set + seen_properties_and_tags = set() + + for entry_node in entry_nodes: + if entry_node.get_node_type() != NodeType.ENTRY: + raise ValueError(f"Only ENTRY nodes should be passed to this function") + seen_properties_and_tags.update(entry_node.tags) + seen_properties_and_tags.update(entry_node.properties) + + additional_query = "\n" + "\n".join( + [f"SET n.{key} = entry_node.{key}" for key in seen_properties_and_tags] + ) + + query = base_query + additional_query + self.session.run(query, entry_nodes=[entry_node.to_dict() for entry_node in entry_nodes]) + + self.parser_logger.info( + f"Created {len(entry_nodes)} ENTRY nodes in {timeit.default_timer() - start_time} seconds" + ) + + def _create_previous_links(self, previous_links: list[PreviousLink], project_label: str): + """Create the 'is_before' relations between nodes""" + self.parser_logger.info("Creating 'is_before' links") + start_time = timeit.default_timer() + + # The previous links creation is batched in a single query + # We also use the ID index to speed up the MATCH queries + query = f""" + UNWIND $previous_links as previous_link + MATCH(n:{project_label}) USING INDEX n:{project_label}(id) + WHERE n.id = previous_link.id + MATCH(p:{project_label}) USING INDEX p:{project_label}(id) + WHERE p.id = previous_link.before_id + CREATE (p)-[relations:is_before]->(n) + WITH relations + UNWIND relations AS relation + RETURN COUNT(relation) + """ + result = self.session.run(query, previous_links=previous_links) + count = result.value()[0] + + self.parser_logger.info( + f"Created {count} 'is_before' links in {timeit.default_timer() - start_time} seconds" + ) + + if count != len(previous_links): + self.parser_logger.error( + f"Created {count} 'is_before' links, {len(previous_links)} links expected" + ) + + def _create_child_links( + self, child_links: list[ChildLink], entry_nodes: list[NodeData], project_label: str + ): + """Create the 'is_child_of' relations between nodes""" + self.parser_logger.info("Creating 'is_child_of' links") + start_time = timeit.default_timer() + + node_ids = set([node.id for node in entry_nodes]) + # we collect nodes with a parent id which is the id of an entry (normalised) + # and nodes where a synonym was used to designate the parent (unnormalised) + normalised_child_links = [] + unnormalised_child_links = [] + for child_link in child_links: + if child_link["parent_id"] in node_ids: + normalised_child_links.append(child_link) + else: + unnormalised_child_links.append(child_link) + + # adding normalised links is easy as we can directly match parent entries + normalised_query = f""" + UNWIND $normalised_child_links as child_link + MATCH (p:{project_label}) USING INDEX p:{project_label}(id) + WHERE p.id = child_link.parent_id + MATCH (c:{project_label}) USING INDEX c:{project_label}(id) + WHERE c.id = child_link.id + CREATE (c)-[relations:is_child_of]->(p) + WITH relations + UNWIND relations AS relation + RETURN COUNT(relation) + """ + + # for unnormalised links, we need to group them by language code of the parent id + lc_child_links_map = collections.defaultdict(list) + for child_link in unnormalised_child_links: + lc, parent_id = child_link["parent_id"].split(":") + child_link["parent_id"] = parent_id + lc_child_links_map[lc].append(child_link) + + # we create a query for each language code + lc_queries = [] + for lc, lc_child_links in lc_child_links_map.items(): + lc_query = f""" + UNWIND $lc_child_links as child_link + MATCH (p:{project_label}) + WHERE child_link.parent_id IN p.tags_ids_{lc} + MATCH (c:{project_label}) USING INDEX c:{project_label}(id) + WHERE c.id = child_link.id + CREATE (c)-[relations:is_child_of]->(p) + WITH relations + UNWIND relations AS relation + RETURN COUNT(relation) + """ + lc_queries.append((lc_query, lc_child_links)) + + count = 0 + + if normalised_child_links: + normalised_result = self.session.run( + normalised_query, normalised_child_links=normalised_child_links + ) + count += normalised_result.value()[0] + + if lc_queries: + for lc_query, lc_child_links in lc_queries: + lc_result = self.session.run(lc_query, lc_child_links=lc_child_links) + count += lc_result.value()[0] + + self.parser_logger.info( + f"Created {count} 'is_child_of' links in {timeit.default_timer() - start_time} seconds" + ) + + if count != len(child_links): + self.parser_logger.error( + f"Created {count} 'is_child_of' links, {len(child_links)} links expected" + ) + + def _create_parsing_errors_node(self, taxonomy_name: str, branch_name: str, project_label: str): + """Create node to list parsing errors""" + self.parser_logger.info("Creating 'ERRORS' node") + start_time = timeit.default_timer() + + query = f""" + CREATE (n:{project_label}:ERRORS) + SET n.id = $project_name + SET n.branch_name = $branch_name + SET n.taxonomy_name = $taxonomy_name + SET n.created_at = datetime() + SET n.warnings = $warnings_list + SET n.errors = $errors_list + """ + params = { + "project_name": project_label, + "branch_name": branch_name, + "taxonomy_name": taxonomy_name, + "warnings_list": self.parser_logger.parsing_warnings, + "errors_list": self.parser_logger.parsing_errors, + } + self.session.run(query, params) + + self.parser_logger.info( + f"Created 'ERRORS' node in {timeit.default_timer() - start_time} seconds" + ) + + def _create_node_id_index(self, project_label: str): + """Create index for search query optimization""" + query = f""" + CREATE INDEX {project_label}_id_index IF NOT EXISTS FOR (n:{project_label}) ON (n.id) + """ + self.session.run(query) + + def _create_node_fulltext_index(self, project_label: str): + """Create indexes for text search""" + query = ( + f"""CREATE FULLTEXT INDEX {project_label+'_SearchIds'} IF NOT EXISTS + FOR (n:{project_label}) ON EACH [n.id]\n""" + + """ + OPTIONS {indexConfig: {`fulltext.analyzer`: 'keyword'}}""" + ) + self.session.run(query) + + language_codes = [lang.alpha2 for lang in list(iso639.languages) if lang.alpha2 != ""] + tags_prefixed_lc = ["n.tags_" + lc for lc in language_codes] + tags_prefixed_lc = ", ".join(tags_prefixed_lc) + query = f"""CREATE FULLTEXT INDEX {project_label+'_SearchTags'} IF NOT EXISTS + FOR (n:{project_label}) ON EACH [{tags_prefixed_lc}]""" + self.session.run(query) + + def _create_node_indexes(self, project_label: str): + """Create node indexes""" + self.parser_logger.info("Creating indexes") + start_time = timeit.default_timer() + + self._create_node_id_index(project_label) + self._create_node_fulltext_index(project_label) + + self.parser_logger.info(f"Created indexes in {timeit.default_timer() - start_time} seconds") + + def _write_to_database(self, taxonomy: Taxonomy, taxonomy_name: str, branch_name: str): + project_label = self._get_project_name(taxonomy_name, branch_name) + # First create nodes, then create node indexes to accelerate relationship creation, then create relationships + self._create_other_nodes(taxonomy.other_nodes, project_label) + self._create_entry_nodes(taxonomy.entry_nodes, project_label) + self._create_node_indexes(project_label) + self._create_child_links(taxonomy.child_links, taxonomy.entry_nodes, project_label) + self._create_previous_links(taxonomy.previous_links, project_label) + # Lastly create the parsing errors node + self._create_parsing_errors_node(taxonomy_name, branch_name, project_label) + + def __call__(self, filename: str, branch_name: str, taxonomy_name: str): + """Process the file""" + start_time = timeit.default_timer() + + branch_name = normalizing(branch_name, char="_") + taxonomy_parser = TaxonomyParser() + taxonomy = taxonomy_parser.parse_file(filename, self.parser_logger) + self._write_to_database(taxonomy, taxonomy_name, branch_name) + + self.parser_logger.info( + f"Finished parsing {taxonomy_name} in {timeit.default_timer() - start_time} seconds" + ) + + +if __name__ == "__main__": + # Setup logs + logging.basicConfig(handlers=[logging.StreamHandler()], level=logging.INFO) + filename = sys.argv[1] if len(sys.argv) > 1 else "test" + branch_name = sys.argv[2] if len(sys.argv) > 1 else "branch" + taxonomy_name = sys.argv[3] if len(sys.argv) > 1 else filename.rsplit(".", 1)[0] + + # Initialize neo4j + uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687") + driver = GraphDatabase.driver(uri) + session = driver.session(database="neo4j") + + # Pass session variable to parser object + parse = Parser(session) + parse(filename, branch_name, taxonomy_name) diff --git a/parser/openfoodfacts_taxonomy_parser/parser/taxonomy_parser.py b/parser/openfoodfacts_taxonomy_parser/parser/taxonomy_parser.py new file mode 100644 index 00000000..c864469a --- /dev/null +++ b/parser/openfoodfacts_taxonomy_parser/parser/taxonomy_parser.py @@ -0,0 +1,377 @@ +import logging +import re +import sys +import timeit +from enum import Enum +from dataclasses import dataclass, field +from typing import Iterator, TypedDict + +from .logger import ParserConsoleLogger +from .exception import DuplicateIDError +from ..normalizer import normalizing + + +class NodeType(str, Enum): + TEXT = "TEXT" + SYNONYMS = "SYNONYMS" + STOPWORDS = "STOPWORDS" + ENTRY = "ENTRY" + + +@dataclass(slots=True) +class NodeData: + id: str = "" + is_before: str | None = None + main_language: str | None = None + preceding_lines: list[str] = field(default_factory=list) + parent_tag: list[str] = field(default_factory=list) + src_position: int | None = None + properties: dict[str, str] = field(default_factory=dict) + tags: dict[str, list[str]] = field(default_factory=dict) + + def to_dict(self): + return { + "id": self.id, + "main_language": self.main_language, + "preceding_lines": self.preceding_lines, + "src_position": self.src_position, + **self.properties, + **self.tags, + } + + def get_node_type(self): + if self.id in ["__header__", "__footer__"]: + return NodeType.TEXT + elif self.id.startswith("synonyms"): + return NodeType.SYNONYMS + elif self.id.startswith("stopwords"): + return NodeType.STOPWORDS + else: + return NodeType.ENTRY + + +class PreviousLink(TypedDict): + before_id: str + id: str + + +class ChildLink(TypedDict): + parent_id: str + id: str + + +@dataclass(slots=True) +class Taxonomy: + entry_nodes: list[NodeData] + other_nodes: list[NodeData] + previous_links: list[PreviousLink] + child_links: list[ChildLink] + + +class TaxonomyParser: + """Parse a taxonomy file""" + + def __init__(self): + self.parser_logger = ParserConsoleLogger() + + def _normalized_filename(self, filename: str) -> str: + """Add the .txt extension if it is missing in the filename""" + return filename + (".txt" if (len(filename) < 4 or filename[-4:] != ".txt") else "") + + def _file_iter(self, filename: str, start: int = 0) -> Iterator[tuple[int, str]]: + """Generator to get the file line by line""" + with open(filename, "r", encoding="utf8") as file: + line_count = 0 + for line_number, line in enumerate(file): + if line_number < start: + continue + # sanitizing + # remove any space characters at end of line + line = line.rstrip() + # replace ’ (typographique quote) to simple quote ' + line = line.replace("’", "'") + # replace commas that have no space around by a lower comma character + # and do the same for escaped comma (preceded by a \) + # (to distinguish them from commas acting as tags separators) + line = re.sub(r"(\d),(\d)", r"\1‚\2", line) + line = re.sub(r"\\,", "\\‚", line) + # removes parenthesis for roman numeral + line = re.sub(r"\(([ivx]+)\)", r"\1", line, flags=re.I) + yield line_number, line + line_count += 1 + yield line_count, "" # to end the last entry if not ended + + def _remove_stopwords(self, lc: str, words: str) -> str: + """Remove the stopwords that were read at the beginning of the file""" + # First check if this language has stopwords + if lc in self.stopwords: + words_to_remove = self.stopwords[lc] + new_words = [] + for word in words.split("-"): + if word not in words_to_remove: + new_words.append(word) + return ("-").join(new_words) + else: + return words + + def _add_line(self, line: str) -> str: + """ + Get a normalized string but keeping the language code "lc:", + used for id and parent tag + """ + lc, line = line.split(":", 1) + new_line = lc + ":" + new_line += self._remove_stopwords(lc, normalizing(line, lc)) + return new_line + + def _get_lc_value(self, line: str) -> tuple[str, list[str]]: + """Get the language code "lc" and a list of normalized values""" + lc, line = line.split(":", 1) + new_line: list[str] = [] + for word in line.split(","): + new_line.append(self._remove_stopwords(lc, normalizing(word, lc))) + return lc, new_line + + def _set_data_id(self, data: NodeData, id: str, line_number: int) -> NodeData: + if not data.id: + data.id = id + else: + raise DuplicateIDError(line_number) + return data + + def _header_harvest(self, filename: str) -> tuple[list[str], int]: + """ + Harvest the header (comment with #), + it has its own function because some header has multiple blocks + """ + h = 0 + header: list[str] = [] + for _, line in self._file_iter(filename): + if not (line) or line[0] == "#": + header.append(line) + else: + break + h += 1 + + # we don't want to eat the comments of the next block + # and it removes the last separating line + for i in range(len(header)): + if header.pop(): + h -= 1 + else: + break + + return header, h + + def _entry_end(self, line: str, data: NodeData) -> bool: + """Return True if the block ended""" + # stopwords and synonyms are one-liner, entries are separated by a blank line + if line.startswith("stopwords") or line.startswith("synonyms") or not line: + # can be the end of an block or just additional line separator, + # file_iter() always end with '' + if data.id: # to be sure that it's an end + return True + return False + + def _remove_separating_line(self, data: NodeData) -> NodeData: + """ + To remove the one separating line that is always there, + between synonyms part and stopwords part and before each entry + """ + is_before = data.is_before + # first, check if there is at least one preceding line + if data.preceding_lines and not data.preceding_lines[0]: + if data.id.startswith("synonyms"): + # it's a synonyms block, + # if the previous block is a stopwords block, + # there is at least one separating line + if is_before and "stopwords" in is_before: + data.preceding_lines.pop(0) + + elif data.id.startswith("stopwords"): + # it's a stopwords block, + # if the previous block is a synonyms block, + # there is at least one separating line + if is_before and "synonyms" in is_before: + data.preceding_lines.pop(0) + + else: + # it's an entry block, there is always a separating line + data.preceding_lines.pop(0) + return data + + def _harvest_entries(self, filename: str, entries_start_line: int) -> Iterator[NodeData]: + """Transform data from file to dictionary""" + saved_nodes = [] + index_stopwords = 0 + index_synonyms = 0 + language_code_prefix = re.compile( + r"[a-zA-Z][a-zA-Z][a-zA-Z]?([-_][a-zA-Z][a-zA-Z][a-zA-Z]?)?:" + ) + # Check if it is correctly written + correctly_written = re.compile(r"\w+\Z") + # stopwords will contain a list of stopwords with their language code as key + self.stopwords = {} + # the other entries + data = NodeData(is_before="__header__") + line_number = ( + entries_start_line # if the iterator is empty, line_number will not be unbound + ) + for line_number, line in self._file_iter(filename, entries_start_line): + # yield data if block ended + if self._entry_end(line, data): + if data.id in saved_nodes: + msg = ( + f"Entry with same id {data.id} already created, " + f"duplicate id in file at line {data.src_position}. " + "Node creation cancelled." + ) + self.parser_logger.error(msg) + else: + data = self._remove_separating_line(data) + yield data # another function will use this dictionary to create a node + saved_nodes.append(data.id) + data = NodeData(is_before=data.id) + + # harvest the line + if not (line) or line[0] == "#": + # comment or blank + data.preceding_lines.append(line) + else: + line = line.rstrip(",") + if not data.src_position: + data.src_position = line_number + 1 + if line.startswith("stopwords"): + # general stopwords definition for a language + id = "stopwords:" + str(index_stopwords) + data = self._set_data_id(data, id, line_number) + index_stopwords += 1 + try: + lc, value = self._get_lc_value(line[10:]) + except ValueError: + self.parser_logger.error( + f"Missing language code at line {line_number + 1} ? '{self.parser_logger.ellipsis(line)}'" + ) + else: + data.tags["tags_" + lc] = value + # add the list with its lc + self.stopwords[lc] = value + elif line.startswith("synonyms"): + # general synonyms definition for a language + id = "synonyms:" + str(index_synonyms) + data = self._set_data_id(data, id, line_number) + index_synonyms += 1 + line = line[9:] + tags = [words.strip() for words in line[3:].split(",")] + try: + lc, value = self._get_lc_value(line) + except ValueError: + self.parser_logger.error( + f"Missing language code at line {line_number + 1} ? '{self.parser_logger.ellipsis(line)}'" + ) + else: + data.tags["tags_" + lc] = tags + data.tags["tags_ids_" + lc] = value + elif line[0] == "<": + # parent definition + data.parent_tag.append(self._add_line(line[1:])) + elif language_code_prefix.match(line): + # synonyms definition + if not data.id: + data.id = self._add_line(line.split(",", 1)[0]) + # first 2-3 characters before ":" are the language code + data.main_language = data.id.split(":", 1)[0] + # add tags and tagsid + lang, line = line.split(":", 1) + # to transform '-' from language code to '_' + lang = lang.strip().replace("-", "_") + tags_list = [] + tagsids_list = [] + for word in line.split(","): + tags_list.append(word.strip()) + word_normalized = self._remove_stopwords(lang, normalizing(word, lang)) + if word_normalized not in tagsids_list: + # in case 2 normalized synonyms are the same + tagsids_list.append(word_normalized) + data.tags["tags_" + lang] = tags_list + data.tags["tags_ids_" + lang] = tagsids_list + else: + # property definition + property_name = None + try: + property_name, lc, property_value = line.split(":", 2) + except ValueError: + self.parser_logger.error( + f"Reading error at line {line_number + 1}, unexpected format: '{self.parser_logger.ellipsis(line)}'" + ) + else: + # in case there is space before or after the colons + property_name = property_name.strip() + lc = lc.strip().replace("-", "_") + if not ( + correctly_written.match(property_name) and correctly_written.match(lc) + ): + self.parser_logger.error( + f"Reading error at line {line_number + 1}, unexpected format: '{self.parser_logger.ellipsis(line)}'" + ) + if property_name: + data.properties["prop_" + property_name + "_" + lc] = property_value + + data.id = "__footer__" + data.preceding_lines.pop(0) + data.src_position = line_number + 1 - len(data.preceding_lines) + yield data + + def _create_taxonomy(self, filename: str) -> Taxonomy: + """Create the taxonomy from the file""" + self.parser_logger.info(f"Parsing {filename}") + harvested_header_data, entries_start_line = self._header_harvest(filename) + entry_nodes: list[NodeData] = [] + other_nodes = [ + NodeData(id="__header__", preceding_lines=harvested_header_data, src_position=1) + ] + previous_links: list[PreviousLink] = [] + child_links: list[ChildLink] = [] + harvested_data = self._harvest_entries(filename, entries_start_line) + for entry in harvested_data: + if entry.get_node_type() == NodeType.ENTRY: + entry_nodes.append(entry) + else: + other_nodes.append(entry) + if entry.is_before: + previous_links.append(PreviousLink(before_id=entry.is_before, id=entry.id)) + if entry.parent_tag: + for parent in entry.parent_tag: + child_links.append(ChildLink(parent_id=parent, id=entry.id)) + return Taxonomy( + entry_nodes=entry_nodes, + other_nodes=other_nodes, + previous_links=previous_links, + child_links=child_links, + ) + + def parse_file(self, filename: str, logger: ParserConsoleLogger | None = None) -> Taxonomy: + if logger: + self.parser_logger = logger + """Process the file into a Taxonomy object""" + start_time = timeit.default_timer() + filename = self._normalized_filename(filename) + taxonomy = self._create_taxonomy(filename) + self.parser_logger.info(f"Parsing done in {timeit.default_timer() - start_time} seconds.") + self.parser_logger.info( + f"Found {len(taxonomy.entry_nodes) + len(taxonomy.other_nodes)} nodes" + ) + self.parser_logger.info(f"Found {len(taxonomy.previous_links)} previous links") + self.parser_logger.info(f"Found {len(taxonomy.child_links)} child links") + + return taxonomy + + +if __name__ == "__main__": + # Setup logs + logging.basicConfig(handlers=[logging.StreamHandler()], level=logging.INFO) + filename = sys.argv[1] if len(sys.argv) > 1 else "test" + + # Pass session variable to parser object + parse = TaxonomyParser() + parse.parse_file(filename) diff --git a/parser/tests/conftest.py b/parser/tests/conftest.py index cf53d30b..8e8a87d5 100644 --- a/parser/tests/conftest.py +++ b/parser/tests/conftest.py @@ -11,7 +11,7 @@ def neo4j(): """waiting for neo4j to be ready""" uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687") driver = GraphDatabase.driver(uri) - session = driver.session() + session = driver.session(database="neo4j") connected = False while not connected: try: diff --git a/parser/tests/integration/test_parse_unparse_integration.py b/parser/tests/integration/test_parse_unparse_integration.py index a21a7456..6bf91bb8 100644 --- a/parser/tests/integration/test_parse_unparse_integration.py +++ b/parser/tests/integration/test_parse_unparse_integration.py @@ -11,21 +11,21 @@ @pytest.fixture(autouse=True) def test_setup(neo4j): # delete all the nodes, relations and search indexes in the database - query = "MATCH (n:p_test_branch:t_test:b_branch) DETACH DELETE n" + query = "MATCH (n:p_test_branch) DETACH DELETE n" neo4j.session().run(query) query = "DROP INDEX p_test_branch_SearchIds IF EXISTS" neo4j.session().run(query) query = "DROP INDEX p_test_branch_SearchTags IF EXISTS" neo4j.session().run(query) - query1 = "MATCH (n:p_test_branch1:t_test:b_branch1) DETACH DELETE n" + query1 = "MATCH (n:p_test_branch1) DETACH DELETE n" neo4j.session().run(query1) query1 = "DROP INDEX p_test_branch1_SearchIds IF EXISTS" neo4j.session().run(query1) query1 = "DROP INDEX p_test_branch1_SearchTags IF EXISTS" neo4j.session().run(query1) - query2 = "MATCH (n:p_test_branch2:t_test:b_branch2) DETACH DELETE n" + query2 = "MATCH (n:p_test_branch2) DETACH DELETE n" neo4j.session().run(query2) query2 = "DROP INDEX p_test_branch2_SearchIds IF EXISTS" neo4j.session().run(query2) @@ -35,22 +35,20 @@ def test_setup(neo4j): def test_round_trip(neo4j): """test parsing and dumping back a taxonomy""" - session = neo4j.session() - test_parser = parser.Parser(session) + with neo4j.session() as session: + test_parser = parser.Parser(session) - # parse taxonomy - test_parser(TEST_TAXONOMY_TXT, "branch", "test") - # just quick check it runs ok with total number of nodes - query = "MATCH (n:p_test_branch:t_test:b_branch) RETURN COUNT(*)" - result = session.run(query) - number_of_nodes = result.value()[0] - assert number_of_nodes == 14 + # parse taxonomy + test_parser(TEST_TAXONOMY_TXT, "branch", "test") + # just quick check it runs ok with total number of nodes + query = "MATCH (n:p_test_branch) RETURN COUNT(*)" + result = session.run(query) + number_of_nodes = result.value()[0] + assert number_of_nodes == 14 - # dump taxonomy back - test_dumper = unparser.WriteTaxonomy(session) - lines = list(test_dumper.iter_lines("p_test_branch:t_test:b_branch")) - - session.close() + # dump taxonomy back + test_dumper = unparser.WriteTaxonomy(session) + lines = list(test_dumper.iter_lines("p_test_branch")) original_lines = [line.rstrip("\n") for line in open(TEST_TAXONOMY_TXT)] # expected result is close to original file with a few tweaks @@ -75,32 +73,29 @@ def test_round_trip(neo4j): def test_two_branch_round_trip(neo4j): """test parsing and dumping the same taxonomy with two different branches""" - session = neo4j.session() - - test_parser = parser.Parser(session) - - # parse taxonomy with branch1 - test_parser(TEST_TAXONOMY_TXT, "branch1", "test") - # parse taxonomy with branch2 - test_parser(TEST_TAXONOMY_TXT, "branch2", "test") - - # just quick check it runs ok with total number of nodes - query = "MATCH (n:p_test_branch1:t_test:b_branch1) RETURN COUNT(*)" - result = session.run(query) - number_of_nodes = result.value()[0] - assert number_of_nodes == 14 - - query = "MATCH (n:p_test_branch2:t_test:b_branch2) RETURN COUNT(*)" - result = session.run(query) - number_of_nodes = result.value()[0] - assert number_of_nodes == 14 - - # dump taxonomy back - test_dumper = unparser.WriteTaxonomy(session) - lines_branch1 = list(test_dumper.iter_lines("p_test_branch1:t_test:b_branch1")) - lines_branch2 = list(test_dumper.iter_lines("p_test_branch2:t_test:b_branch2")) - - session.close() + with neo4j.session() as session: + test_parser = parser.Parser(session) + + # parse taxonomy with branch1 + test_parser(TEST_TAXONOMY_TXT, "branch1", "test") + # parse taxonomy with branch2 + test_parser(TEST_TAXONOMY_TXT, "branch2", "test") + + # just quick check it runs ok with total number of nodes + query = "MATCH (n:p_test_branch1) RETURN COUNT(*)" + result = session.run(query) + number_of_nodes = result.value()[0] + assert number_of_nodes == 14 + + query = "MATCH (n:p_test_branch2) RETURN COUNT(*)" + result = session.run(query) + number_of_nodes = result.value()[0] + assert number_of_nodes == 14 + + # dump taxonomy back + test_dumper = unparser.WriteTaxonomy(session) + lines_branch1 = list(test_dumper.iter_lines("p_test_branch1")) + lines_branch2 = list(test_dumper.iter_lines("p_test_branch2")) original_lines = [line.rstrip("\n") for line in open(TEST_TAXONOMY_TXT)] # expected result is close to original file with a few tweaks diff --git a/parser/tests/integration/test_parser_integration.py b/parser/tests/integration/test_parser_integration.py index c5223107..5dd4e090 100644 --- a/parser/tests/integration/test_parser_integration.py +++ b/parser/tests/integration/test_parser_integration.py @@ -13,7 +13,9 @@ @pytest.fixture(autouse=True) def test_setup(neo4j): # delete all the nodes and relations in the database - query = "MATCH (n:p_test_branch:t_test:b_branch) DETACH DELETE n" + query = "MATCH (n:p_test_branch) DETACH DELETE n" + neo4j.session().run(query) + query = "DROP INDEX p_test_branch_id_index IF EXISTS" neo4j.session().run(query) query = "DROP INDEX p_test_branch_SearchIds IF EXISTS" neo4j.session().run(query) @@ -22,192 +24,185 @@ def test_setup(neo4j): def test_calling(neo4j): - session = neo4j.session() - test_parser = parser.Parser(session) - - # Create node test - test_parser.create_nodes(TEST_TAXONOMY_TXT, "p_test_branch:t_test:b_branch") - - # total number of nodes - query = "MATCH (n:p_test_branch:t_test:b_branch) RETURN COUNT(*)" - result = session.run(query) - number_of_nodes = result.value()[0] - assert number_of_nodes == 13 - - # header correctly added - query = ( - "MATCH (n:p_test_branch:t_test:b_branch) WHERE n.id = '__header__' RETURN n.preceding_lines" - ) - result = session.run(query) - header = result.value()[0] - assert header == ["# test taxonomy"] - - # synonyms correctly added - query = "MATCH (n:p_test_branch:t_test:b_branch:SYNONYMS) RETURN n ORDER BY n.src_position" - results = session.run(query) - expected_synonyms = [ - { - "id": "synonyms:0", - "tags_en": ["passion fruit", "passionfruit"], - "tags_ids_en": ["passion-fruit", "passionfruit"], - "preceding_lines": [], - "src_position": 5, - }, - { - "id": "synonyms:1", - "tags_fr": ["fruit de la passion", "maracuja", "passion"], - "tags_ids_fr": ["fruit-passion", "maracuja", "passion"], - "preceding_lines": [""], - "src_position": 7, - }, - ] - for i, result in enumerate(results): - node = result.value() - for key in expected_synonyms[i]: - assert node[key] == expected_synonyms[i][key] - - # stopwords correctly added - query = "MATCH (n:p_test_branch:t_test:b_branch:STOPWORDS) RETURN n" - results = session.run(query) - expected_stopwords = { - "id": "stopwords:0", - "tags_fr": ["aux", "au", "de", "le", "du", "la", "a", "et"], - "preceding_lines": [], - } - for result in results: - node = result.value() - for key in expected_stopwords: - assert node[key] == expected_stopwords[key] - - # entries correctly added - # check for two of them - query = """ - MATCH (n:p_test_branch:t_test:b_branch:ENTRY) - WHERE n.id='en:banana-yogurts' - OR n.id='en:meat' - RETURN n - ORDER BY n.src_position - """ - results = session.run(query) - expected_entries = [ - { - "tags_en": ["banana yogurts"], - "tags_ids_en": ["banana-yogurts"], - "tags_fr": ["yaourts à la banane"], - "tags_ids_fr": ["yaourts-banane"], + with neo4j.session() as session: + test_parser = parser.Parser(session) + test_parser(TEST_TAXONOMY_TXT, "branch", "test") + + # total number of nodes (TEXT, ENTRY, SYNONYMS, STOPWORDS) + 1 ERROR node + query = "MATCH (n:p_test_branch) RETURN COUNT(*)" + result = session.run(query) + number_of_nodes = result.value()[0] + assert number_of_nodes == 14 + + # header correctly added + query = "MATCH (n:p_test_branch) WHERE n.id = '__header__' RETURN n.preceding_lines" + result = session.run(query) + header = result.value()[0] + assert header == ["# test taxonomy"] + + # synonyms correctly added + query = "MATCH (n:p_test_branch:SYNONYMS) RETURN n ORDER BY n.src_position" + results = session.run(query) + expected_synonyms = [ + { + "id": "synonyms:0", + "tags_en": ["passion fruit", "passionfruit"], + "tags_ids_en": ["passion-fruit", "passionfruit"], + "preceding_lines": [], + "src_position": 5, + }, + { + "id": "synonyms:1", + "tags_fr": ["fruit de la passion", "maracuja", "passion"], + "tags_ids_fr": ["fruit-passion", "maracuja", "passion"], + "preceding_lines": [""], + "src_position": 7, + }, + ] + for i, result in enumerate(results): + node = result.value() + for key in expected_synonyms[i]: + assert node[key] == expected_synonyms[i][key] + + # stopwords correctly added + query = "MATCH (n:p_test_branch:STOPWORDS) RETURN n" + results = session.run(query) + expected_stopwords = { + "id": "stopwords:0", + "tags_fr": ["aux", "au", "de", "le", "du", "la", "a", "et"], "preceding_lines": [], - }, - { - "tags_en": ["meat"], - "tags_ids_en": ["meat"], - "preceding_lines": ["# meat", ""], - "prop_vegan_en": "no", - "prop_carbon_footprint_fr_foodges_value_fr": "10", - }, - ] - for i, result in enumerate(results): - node = result.value() - for key in expected_entries[i]: - assert node[key] == expected_entries[i][key] - - # Child link test - test_parser.create_child_link("p_test_branch:t_test:b_branch") # nodes already added - query = """ - MATCH (c:p_test_branch:t_test:b_branch)-[:is_child_of]->(p:p_test_branch:t_test:b_branch) - RETURN c.id, p.id - """ - results = session.run(query) - created_pairs = results.values() - - # correct number of links - number_of_links = len(created_pairs) - assert number_of_links == 6 - - # correctly linked - expected_pairs = [ - ["en:banana-yogurts", "en:yogurts"], - ["en:passion-fruit-yogurts", "en:yogurts"], - ["fr:yaourts-fruit-passion-alleges", "en:passion-fruit-yogurts"], - ["en:fake-meat", "en:meat"], - ["en:fake-duck-meat", "en:fake-meat"], - ["en:fake-duck-meat", "en:fake-stuff"], - ] - for pair in created_pairs: - assert pair in expected_pairs - - # Order link test - test_parser.create_previous_link("p_test_branch:t_test:b_branch") - query = """ - MATCH (n:p_test_branch:t_test:b_branch)-[:is_before]->(p:p_test_branch:t_test:b_branch) - RETURN n.id, p.id - """ - results = session.run(query) - created_pairs = results.values() - - # correct number of links - number_of_links = len(created_pairs) - assert number_of_links == 12 - - # correctly linked - expected_pairs = [ - ["__header__", "stopwords:0"], - ["stopwords:0", "synonyms:0"], - ["synonyms:0", "synonyms:1"], - ["synonyms:1", "en:yogurts"], - ["en:yogurts", "en:banana-yogurts"], - ["en:banana-yogurts", "en:passion-fruit-yogurts"], - ["en:passion-fruit-yogurts", "fr:yaourts-fruit-passion-alleges"], - ["fr:yaourts-fruit-passion-alleges", "en:meat"], - ["en:meat", "en:fake-meat"], - ["en:fake-meat", "en:fake-stuff"], - ["en:fake-stuff", "en:fake-duck-meat"], - ["en:fake-duck-meat", "__footer__"], - ] - for pair in created_pairs: - assert pair in expected_pairs - session.close() + } + for result in results: + node = result.value() + for key in expected_stopwords: + assert node[key] == expected_stopwords[key] + + # entries correctly added + # check for two of them + query = """ + MATCH (n:p_test_branch:ENTRY) + WHERE n.id='en:banana-yogurts' + OR n.id='en:meat' + RETURN n + ORDER BY n.src_position + """ + results = session.run(query) + expected_entries = [ + { + "tags_en": ["banana yogurts"], + "tags_ids_en": ["banana-yogurts"], + "tags_fr": ["yaourts à la banane"], + "tags_ids_fr": ["yaourts-banane"], + "preceding_lines": [], + }, + { + "tags_en": ["meat"], + "tags_ids_en": ["meat"], + "preceding_lines": ["# meat", ""], + "prop_vegan_en": "no", + "prop_carbon_footprint_fr_foodges_value_fr": "10", + }, + ] + for i, result in enumerate(results): + node = result.value() + for key in expected_entries[i]: + assert node[key] == expected_entries[i][key] + + query = """ + MATCH (c:p_test_branch)-[:is_child_of]->(p:p_test_branch) + RETURN c.id, p.id + """ + results = session.run(query) + created_pairs = results.values() + + # correct number of links + number_of_links = len(created_pairs) + assert number_of_links == 6 + + # correctly linked + expected_pairs = [ + ["en:banana-yogurts", "en:yogurts"], + ["en:passion-fruit-yogurts", "en:yogurts"], + ["fr:yaourts-fruit-passion-alleges", "en:passion-fruit-yogurts"], + ["en:fake-meat", "en:meat"], + ["en:fake-duck-meat", "en:fake-meat"], + ["en:fake-duck-meat", "en:fake-stuff"], + ] + for pair in created_pairs: + assert pair in expected_pairs + + query = """ + MATCH (n:p_test_branch)-[:is_before]->(p:p_test_branch) + RETURN n.id, p.id + """ + results = session.run(query) + created_pairs = results.values() + + # correct number of links + number_of_links = len(created_pairs) + assert number_of_links == 12 + + # correctly linked + expected_pairs = [ + ["__header__", "stopwords:0"], + ["stopwords:0", "synonyms:0"], + ["synonyms:0", "synonyms:1"], + ["synonyms:1", "en:yogurts"], + ["en:yogurts", "en:banana-yogurts"], + ["en:banana-yogurts", "en:passion-fruit-yogurts"], + ["en:passion-fruit-yogurts", "fr:yaourts-fruit-passion-alleges"], + ["fr:yaourts-fruit-passion-alleges", "en:meat"], + ["en:meat", "en:fake-meat"], + ["en:fake-meat", "en:fake-stuff"], + ["en:fake-stuff", "en:fake-duck-meat"], + ["en:fake-duck-meat", "__footer__"], + ] + for pair in created_pairs: + assert pair in expected_pairs def test_error_log(neo4j, tmp_path, caplog): # error entries with same id - session = neo4j.session() - test_parser = parser.Parser(session) - - taxonomy_txt = textwrap.dedent(""" - # a fake taxonomy - stopwords:fr: aux,au,de,le,du,la,a,et - - # meat - en:meat - - =12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@mui/material": "^5.0.0", @@ -4886,9 +4886,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", + "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", "engines": { "node": ">=14.0.0" } @@ -5330,16 +5330,16 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz", - "integrity": "sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", + "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", "dependencies": { - "@adobe/css-tools": "^4.3.1", + "@adobe/css-tools": "^4.3.2", "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", + "dom-accessibility-api": "^0.6.3", "lodash": "^4.17.15", "redent": "^3.0.0" }, @@ -5419,6 +5419,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, "node_modules/@testing-library/jest-dom/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6343,19 +6348,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -6601,15 +6593,6 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -7360,38 +7343,32 @@ "node": ">=0.10.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7409,27 +7386,33 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", "dev": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { "ansi-regex": "^6.0.1" @@ -7510,9 +7493,9 @@ "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==" }, "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -8734,12 +8717,6 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10186,9 +10163,9 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==" }, "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -10499,6 +10476,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -16107,74 +16096,95 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/lint-staged": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.0.tgz", - "integrity": "sha512-pn/sR8IrcF/T0vpWLilih8jmVouMlxqXxKuAojmbiGX5n/gDnz+abdPptlj0vYnbfE0SQNl3CY/HwtM0+yfOVQ==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz", + "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==", "dev": true, "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.19", - "commander": "^9.4.1", - "debug": "^4.3.4", - "execa": "^6.1.0", - "lilconfig": "2.0.6", - "listr2": "^5.0.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.2", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.1.3" + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.0", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/lint-staged/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "engines": { - "node": "^12.20.0 || >=14" + "node": ">=16" } }, "node_modules/lint-staged/node_modules/execa": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", - "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^3.0.1", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lint-staged/node_modules/human-signals": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", - "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=12.20.0" + "node": ">=16.17.0" } }, "node_modules/lint-staged/node_modules/is-stream": { @@ -16189,6 +16199,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/lint-staged/node_modules/mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -16243,6 +16262,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/lint-staged/node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -16256,102 +16287,114 @@ } }, "node_modules/lint-staged/node_modules/yaml": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", - "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "dev": true, "engines": { "node": ">= 14" } }, "node_modules/listr2": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.6.tgz", - "integrity": "sha512-u60KxKBy1BR2uLJNTWNptzWQ1ob/gjMzIJPZffAENzpZqbMZ/5PrXXOomDcevIS/+IB7s1mmCEtSlT2qHWMqag==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz", + "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==", "dev": true, "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", "rfdc": "^1.3.0", - "rxjs": "^7.5.7", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^9.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/listr2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", "dev": true, "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/listr2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/listr2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/loader-runner": { @@ -16420,85 +16463,159 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", "dev": true, "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "type-fest": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/loose-envify": { @@ -17160,21 +17277,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -19160,11 +19262,11 @@ } }, "node_modules/react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz", + "integrity": "sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==", "dependencies": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.14.2" }, "engines": { "node": ">=14.0.0" @@ -19174,12 +19276,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.2.tgz", + "integrity": "sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==", "dependencies": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.14.2", + "react-router": "6.21.2" }, "engines": { "node": ">=14.0.0" @@ -20621,16 +20723,19 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/retry": { @@ -20768,15 +20873,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -21343,9 +21439,9 @@ ] }, "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "engines": { "node": ">=0.6.19" @@ -21859,12 +21955,6 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -24479,9 +24569,9 @@ } }, "@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", "requires": { "regenerator-runtime": "^0.14.0" }, @@ -26491,11 +26581,11 @@ "integrity": "sha512-fXoGe8VOrIYajqALysFuyal1q1YmBARqJ3tmnWYDVl0scu8f6h6tZQbS2K8BY28QwkWNGyv4WRfuUkzN5HR3Ow==" }, "@mui/icons-material": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.15.tgz", - "integrity": "sha512-Dqu21vN/mVNzebJ+ofnKG+CeJYIhHuDs5+0fMEpdpzRt6UojelzdrEkNv+XkO0e1JMclzeXIRx404FirK/CFRw==", + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.4.tgz", + "integrity": "sha512-q/Yk7aokN8qGMpR7bwoDpBSeaNe6Bv7vaY9yHYodP37c64TM6ime05ueb/wgksOVszrKkNXC67E/XYbRWOoUFA==", "requires": { - "@babel/runtime": "^7.23.2" + "@babel/runtime": "^7.23.7" } }, "@mui/material": { @@ -26660,9 +26750,9 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==" + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", + "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==" }, "@rollup/plugin-babel": { "version": "5.3.1", @@ -26944,16 +27034,16 @@ } }, "@testing-library/jest-dom": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz", - "integrity": "sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", + "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", "requires": { - "@adobe/css-tools": "^4.3.1", + "@adobe/css-tools": "^4.3.2", "@babel/runtime": "^7.9.2", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", + "dom-accessibility-api": "^0.6.3", "lodash": "^4.17.15", "redent": "^3.0.0" }, @@ -26993,6 +27083,11 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -27747,16 +27842,6 @@ "debug": "4" } }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -27932,12 +28017,6 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", @@ -28491,29 +28570,23 @@ } } }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^4.0.0" } }, "cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "requires": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "dependencies": { "ansi-regex": { @@ -28522,21 +28595,27 @@ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", "dev": true, "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" } }, "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { "ansi-regex": "^6.0.1" @@ -28600,9 +28679,9 @@ "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==" }, "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "combined-stream": { "version": "1.0.8", @@ -29492,12 +29571,6 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -30577,9 +30650,9 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==" }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "for-each": { "version": "0.3.3", @@ -30779,6 +30852,12 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true + }, "get-intrinsic": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -35048,53 +35127,62 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "lint-staged": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.1.0.tgz", - "integrity": "sha512-pn/sR8IrcF/T0vpWLilih8jmVouMlxqXxKuAojmbiGX5n/gDnz+abdPptlj0vYnbfE0SQNl3CY/HwtM0+yfOVQ==", + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz", + "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==", "dev": true, "requires": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.19", - "commander": "^9.4.1", - "debug": "^4.3.4", - "execa": "^6.1.0", - "lilconfig": "2.0.6", - "listr2": "^5.0.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.2", - "pidtree": "^0.6.0", - "string-argv": "^0.3.1", - "yaml": "^2.1.3" + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.0", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" }, "dependencies": { + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, "commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true }, "execa": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", - "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "requires": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^3.0.1", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, "human-signals": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", - "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true }, "is-stream": { @@ -35103,6 +35191,12 @@ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true }, + "lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true + }, "mimic-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", @@ -35133,6 +35227,12 @@ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, "strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -35140,72 +35240,80 @@ "dev": true }, "yaml": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", - "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "dev": true } } }, "listr2": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.6.tgz", - "integrity": "sha512-u60KxKBy1BR2uLJNTWNptzWQ1ob/gjMzIJPZffAENzpZqbMZ/5PrXXOomDcevIS/+IB7s1mmCEtSlT2qHWMqag==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz", + "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==", "dev": true, "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.19", - "log-update": "^4.0.0", - "p-map": "^4.0.0", + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", "rfdc": "^1.3.0", - "rxjs": "^7.5.7", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^9.0.0" }, "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "string-width": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", "dev": true, "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } } } @@ -35264,61 +35372,99 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", "dev": true, "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "dependencies": { + "ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "requires": { + "type-fest": "^3.0.0" + } + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "get-east-asian-width": "^1.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "string-width": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.0.0.tgz", + "integrity": "sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-regex": "^6.0.1" } }, + "type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true + }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } } } @@ -35791,15 +35937,6 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -37025,20 +37162,20 @@ "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, "react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz", + "integrity": "sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==", "requires": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.14.2" } }, "react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.2.tgz", + "integrity": "sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==", "requires": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.14.2", + "react-router": "6.21.2" } }, "react-scripts": { @@ -38128,9 +38265,9 @@ "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==" }, "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "dev": true, "requires": { "onetime": "^5.1.0", @@ -38227,15 +38364,6 @@ "queue-microtask": "^1.2.2" } }, - "rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -38666,9 +38794,9 @@ } }, "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true }, "string-length": { @@ -39044,12 +39172,6 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", diff --git a/taxonomy-editor-frontend/package.json b/taxonomy-editor-frontend/package.json index 76c1d37b..0626e755 100644 --- a/taxonomy-editor-frontend/package.json +++ b/taxonomy-editor-frontend/package.json @@ -6,10 +6,10 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.10.4", "@material-table/core": "^6.2.11", - "@mui/icons-material": "^5.8.4", + "@mui/icons-material": "^5.15.4", "@mui/material": "^5.14.20", "@tanstack/react-query": "^5.17.9", - "@testing-library/jest-dom": "^6.1.4", + "@testing-library/jest-dom": "^6.2.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", "@types/jest": "^29.2.6", @@ -23,7 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^11.18.3", - "react-router-dom": "^6.17.0", + "react-router-dom": "^6.21.2", "react-scripts": "5.0.1", "typescript": "^4.9.4", "web-vitals": "^3.5.0" @@ -58,7 +58,7 @@ }, "devDependencies": { "husky": "^8.0.3", - "lint-staged": "^13.1.0", + "lint-staged": "^15.2.0", "prettier": "2.8.2" }, "lint-staged": {