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": {