Repository: useblocks/sphinx-needs
Pull request #1314 (2024-10-03 18:06:10)

👌 Add `needs_uml_process_max_time` configuration
To guard against long-running `needuml` / `needarch` processing times. In particular, these can occur if the jinja template contains many calls to the filter function.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) Report
Attention: Patch coverage is `93.75000%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 87.85%. Comparing base [(`4e10030`)](https://app.codecov.io/gh/useblocks/sphinx-needs/commit/4e10030581c03c59764e517ab750d3ddbb2695d8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) to head [(`0acae9c`)](https://app.codecov.io/gh/useblocks/sphinx-needs/commit/0acae9c9b30abc113cf9f0dd1faaf18665e14d19?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks).
> Report is 62 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) | Patch % | Lines |
|---|---|---|
| [sphinx\_needs/directives/needuml.py](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314?src=pr&el=tree&filepath=sphinx_needs%2Fdirectives%2Fneeduml.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks#diff-c3BoaW54X25lZWRzL2RpcmVjdGl2ZXMvbmVlZHVtbC5weQ==) | 90.90% | [1 Missing :warning: ](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1314      +/-   ##
==========================================
+ Coverage   86.87%   87.85%   +0.97%     
==========================================
  Files          56       60       +4     
  Lines        6532     7046     +514     
==========================================
+ Hits         5675     6190     +515     
+ Misses        857      856       -1     
```

| [Flag](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) | Coverage Δ | |
|---|---|---|
| [pytests](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks) | `87.85% <93.75%> (+0.97%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/useblocks/sphinx-needs/pull/1314?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=useblocks).


Patch:
diff --git a/docs/configuration.rst b/docs/configuration.rst
index ec31f60d..4957108a 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -542,6 +542,15 @@ needs_filter_max_time
 
 If set, warn if any :ref:`filter processing <filter>` call takes longer than the given time in seconds.
 
+.. _needs_uml_process_max_time:
+
+needs_uml_process_max_time
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 4.0.0
+
+If set, warn if any :ref:`needuml` or :ref:`needarch` jinja content rendering takes longer than the given time in seconds.
+
 .. _needs_flow_engine:
 
 needs_flow_engine
diff --git a/docs/filter.rst b/docs/filter.rst
index 05dcba84..656d1483 100644
--- a/docs/filter.rst
+++ b/docs/filter.rst
@@ -213,6 +213,7 @@ Also filters containing ``and`` will be split into multiple filters and evaluate
 For example, ``type == 'spec' and other == 'value'`` will first be filtered performantly by ``type == 'spec'`` and then the remaining needs will be filtered by ``other == 'value'``.
 
 To guard against long running filters, the :ref:`needs_filter_max_time` configuration option can be used to set a maximum time limit for filter evaluation.
+Also see :ref:`needs_uml_process_max_time`, to guard against long running ``needuml`` / ``needarch`` processes containing :ref:`filters <needuml_jinja_filter>`.
 
 To debug which filters are being used across your project and their run times, you can enable the :ref:`needs_debug_filters` configuration option.
 
diff --git a/sphinx_needs/api/need.py b/sphinx_needs/api/need.py
index 4b8217a0..00394fe1 100644
--- a/sphinx_needs/api/need.py
+++ b/sphinx_needs/api/need.py
@@ -298,7 +298,6 @@ def add_need(
         "lineno": lineno,
         "lineno_content": lineno_content,
         "doctype": doctype,
-        "target_id": need_id,
         "content": "\n".join(content) if isinstance(content, StringList) else content,
         "type": need_type,
         "type_name": type_name,
diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py
index 37cb8589..0765f751 100644
--- a/sphinx_needs/config.py
+++ b/sphinx_needs/config.py
@@ -387,7 +387,11 @@ class NeedsSphinxConfig:
     filter_max_time: int | float | None = field(
         default=None, metadata={"rebuild": "html", "types": (type(None), int, float)}
     )
-    """Warn if a filter runs for longer than this time (in seconds)."""
+    """Warn if process_filter runs for longer than this time (in seconds)."""
+    uml_process_max_time: int | float | None = field(
+        default=None, metadata={"rebuild": "html", "types": (type(None), int, float)}
+    )
+    """Warn if process_needuml runs for longer than this time (in seconds)."""
     flow_engine: Literal["plantuml", "graphviz"] = field(
         default="plantuml", metadata={"rebuild": "env", "types": (str,)}
     )
diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py
index e44f3d94..46d7f0a3 100644
--- a/sphinx_needs/data.py
+++ b/sphinx_needs/data.py
@@ -68,7 +68,6 @@ class CoreFieldParameters(TypedDict):
 
 
 NeedsCoreFields: Final[Mapping[str, CoreFieldParameters]] = {
-    "target_id": {"description": "ID of the data.", "schema": {"type": "string"}},
     "id": {"description": "ID of the data.", "schema": {"type": "string"}},
     "docname": {
         "description": "Name of the document where the need is defined (None if external).",
@@ -293,9 +292,6 @@ NeedsCoreFields: Final[Mapping[str, CoreFieldParameters]] = {
 class NeedsInfoType(TypedDict, total=False):
     """Data for a single need."""
 
-    # TODO remove duplication target_id/id
-    target_id: Required[str]
-    """ID of the data."""
     id: Required[str]
     """ID of the data."""
 
@@ -660,6 +656,8 @@ class NeedsUmlType(NeedsBaseDataType):
     is_arch: bool
     # set in process_needuml
     content_calculated: str
+    process_time: float
+    """Time taken to process the diagram."""
 
 
 NeedsMutable = NewType("NeedsMutable", Dict[str, NeedsInfoType])
diff --git a/sphinx_needs/diagrams_common.py b/sphinx_needs/diagrams_common.py
index 82ccb30f..944dbaed 100644
--- a/sphinx_needs/diagrams_common.py
+++ b/sphinx_needs/diagrams_common.py
@@ -203,7 +203,7 @@ def calculate_link(
                 + "/"
                 + builder.get_target_uri(_docname)
                 + "#"
-                + need_info["target_id"]
+                + need_info["id_parent"]
             )
             if need_info["is_part"]:
                 link = f"{link}.{need_info['id']}"
diff --git a/sphinx_needs/directives/needlist.py b/sphinx_needs/directives/needlist.py
index 7c4e7e32..b06b4aa7 100644
--- a/sphinx_needs/directives/needlist.py
+++ b/sphinx_needs/directives/needlist.py
@@ -127,7 +127,7 @@ def process_needlist(
                     ref.append(title)
                     para += ref
                 elif _docname := need_info["docname"]:
-                    target_id = need_info["target_id"]
+                    target_id = need_info["id_parent"]
                     ref = nodes.reference("", "")
                     ref["refdocname"] = _docname
                     ref["refuri"] = builder.get_relative_uri(fromdocname, _docname)
diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py
index 8539cf58..c57b1d0f 100644
--- a/sphinx_needs/directives/needuml.py
+++ b/sphinx_needs/directives/needuml.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import html
 import os
+import time
 from typing import TYPE_CHECKING, Any, Dict, List, Sequence, TypedDict
 
 from docutils import nodes
@@ -16,7 +17,8 @@ from sphinx_needs.debug import measure_time
 from sphinx_needs.diagrams_common import calculate_link
 from sphinx_needs.directives.needflow._plantuml import make_entity_name
 from sphinx_needs.filter_common import filter_needs_view
-from sphinx_needs.utils import add_doc
+from sphinx_needs.logging import log_warning
+from sphinx_needs.utils import add_doc, logger
 
 if TYPE_CHECKING:
     from sphinxcontrib.plantuml import plantuml
@@ -125,6 +127,7 @@ class NeedumlDirective(SphinxDirective):
             "save": plantuml_code_out_path,
             "is_arch": is_arch,
             "content_calculated": "",
+            "process_time": 0,
         }
 
         add_doc(env, env.docname)
@@ -493,11 +496,13 @@ def process_needuml(
     found_nodes: list[nodes.Element],
 ) -> None:
     env = app.env
+    needs_config = NeedsSphinxConfig(app.config)
+    uml_data = SphinxNeedsData(env).get_or_create_umls()
 
     # for node in doctree.findall(Needuml):
     for node in found_nodes:
         id = node.attributes["ids"][0]
-        current_needuml = SphinxNeedsData(env).get_or_create_umls()[id]
+        current_needuml = uml_data[id]
 
         parent_need_id = None
         # Check if current needuml is needarch
@@ -518,6 +523,7 @@ def process_needuml(
                 [line.strip() for line in config.split("\n") if line.strip()]
             )
 
+        start = time.perf_counter()
         puml_node = transform_uml_to_plantuml_node(
             app=app,
             uml_content=current_needuml["content"],
@@ -526,6 +532,18 @@ def process_needuml(
             kwargs=current_needuml["extra"],
             config=config,
         )
+        duration = time.perf_counter() - start
+
+        if (
+            needs_config.uml_process_max_time is not None
+            and duration > needs_config.uml_process_max_time
+        ):
+            log_warning(
+                logger,
+                f"Uml processing took {duration:.3f}s, which is longer than the configured maximum of {needs_config.uml_process_max_time}s.",
+                "uml",
+                location=node,
+            )
 
         # Add source origin
         puml_node.line = current_needuml["lineno"]
@@ -533,6 +551,7 @@ def process_needuml(
 
         # Add calculated needuml content
         current_needuml["content_calculated"] = puml_node["uml"]
+        current_needuml["process_time"] = duration
 
         try:
             scale = int(current_needuml["scale"])
diff --git a/sphinx_needs/roles/need_incoming.py b/sphinx_needs/roles/need_incoming.py
index d3e75083..98453724 100644
--- a/sphinx_needs/roles/need_incoming.py
+++ b/sphinx_needs/roles/need_incoming.py
@@ -65,7 +65,7 @@ def process_need_incoming(
                             builder,
                             fromdocname,
                             _docname,
-                            target_need["target_id"],
+                            target_need["id_parent"],
                             node_need_backref[0].deepcopy(),
                             node_need_backref["reftarget"],
                         )


Test patch:
diff --git a/tests/__snapshots__/test_add_sections_sigs.ambr b/tests/__snapshots__/test_add_sections_sigs.ambr
index 9089cb29..3af0c744 100644
--- a/tests/__snapshots__/test_add_sections_sigs.ambr
+++ b/tests/__snapshots__/test_add_sections_sigs.ambr
@@ -18,7 +18,6 @@
         'test',
         'test2',
       ]),
-      'target_id': 'R_12345',
       'title': 'Command line interface',
       'type': 'req',
       'type_name': 'Requirement',
@@ -42,7 +41,6 @@
         'test',
         'test2',
       ]),
-      'target_id': 'R_12346',
       'title': 'Another Requirement',
       'type': 'req',
       'type_name': 'Requirement',
@@ -60,7 +58,6 @@
         '1 TEST DOCUMENT',
       ]),
       'signature': 'test_method',
-      'target_id': 'T_001',
       'title': 'test method',
       'type': 'test',
       'type_name': 'Test Case',
diff --git a/tests/__snapshots__/test_basic_doc.ambr b/tests/__snapshots__/test_basic_doc.ambr
index bdf01be1..604eda10 100644
--- a/tests/__snapshots__/test_basic_doc.ambr
+++ b/tests/__snapshots__/test_basic_doc.ambr
@@ -64,7 +64,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'ST_001',
             'template': None,
             'title': 'Test story',
             'type': 'story',
@@ -134,7 +133,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'US_38823',
             'template': None,
             'title': 'No ID',
             'type': 'story',
@@ -515,11 +513,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/__snapshots__/test_external.ambr b/tests/__snapshots__/test_external.ambr
index d6ea4319..1d8bbf3e 100644
--- a/tests/__snapshots__/test_external.ambr
+++ b/tests/__snapshots__/test_external.ambr
@@ -15,7 +15,6 @@
             'sections': list([
               'Title',
             ]),
-            'target_id': 'EXT_REQ_01',
             'title': 'REQ_01',
             'type': 'req',
             'type_name': 'Requirement',
@@ -30,7 +29,6 @@
             'sections': list([
               'Title',
             ]),
-            'target_id': 'IMP_REQ_01',
             'title': 'REQ_01',
             'type': 'req',
             'type_name': 'Requirement',
@@ -407,11 +405,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
@@ -538,7 +531,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'EXT_TEST_01',
             'template': None,
             'title': 'TEST_01 DESCRIPTION',
             'type': 'impl',
@@ -614,7 +606,6 @@
               'test_02',
               'test',
             ]),
-            'target_id': 'EXT_TEST_02',
             'template': None,
             'title': 'TEST_02 DESCRIPTION',
             'type': 'req',
@@ -686,7 +677,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_1',
             'template': None,
             'title': 'Test requirement 1',
             'type': 'req',
@@ -759,7 +749,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'SPEC_1',
             'template': None,
             'title': 'Test specification 1',
             'type': 'spec',
@@ -829,7 +818,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'SUB_002',
             'template': None,
             'title': 'Sub-Req',
             'type': 'req',
@@ -1210,11 +1198,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/__snapshots__/test_need_constraints.ambr b/tests/__snapshots__/test_need_constraints.ambr
index 4c6e8fea..fb6adc84 100644
--- a/tests/__snapshots__/test_need_constraints.ambr
+++ b/tests/__snapshots__/test_need_constraints.ambr
@@ -66,7 +66,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'SECURITY_REQ',
             'template': None,
             'title': 'test1',
             'type': 'spec',
@@ -142,7 +141,6 @@
             'tags': list([
               'critical',
             ]),
-            'target_id': 'SP_109F4',
             'template': None,
             'title': 'test2',
             'type': 'spec',
@@ -218,7 +216,6 @@
             'style': 'red_bar',
             'tags': list([
             ]),
-            'target_id': 'SP_3EBFA',
             'template': None,
             'title': 'test3',
             'type': 'spec',
@@ -292,7 +289,6 @@
             'style': 'yellow_bar,',
             'tags': list([
             ]),
-            'target_id': 'SP_CA3FB',
             'template': None,
             'title': 'FAIL_01',
             'type': 'spec',
@@ -364,7 +360,6 @@
               'test',
               'test2',
             ]),
-            'target_id': 'SP_TOO_001',
             'template': None,
             'title': 'Command line interface',
             'type': 'spec',
@@ -441,7 +436,6 @@
               'hello',
               'there',
             ]),
-            'target_id': 'SP_TOO_002',
             'template': None,
             'title': 'CLI',
             'type': 'spec',
@@ -516,7 +510,6 @@
             'style': 'red_bar',
             'tags': list([
             ]),
-            'target_id': 'TEST_STYLE',
             'template': None,
             'title': 'CLI',
             'type': 'spec',
@@ -590,7 +583,6 @@
             'style': 'blue_border, yellow_bar',
             'tags': list([
             ]),
-            'target_id': 'TEST_STYLE2',
             'template': None,
             'title': 'CLI2',
             'type': 'spec',
@@ -971,11 +963,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/__snapshots__/test_need_parts.ambr b/tests/__snapshots__/test_need_parts.ambr
index 216ccbfb..28f06ecc 100644
--- a/tests/__snapshots__/test_need_parts.ambr
+++ b/tests/__snapshots__/test_need_parts.ambr
@@ -89,7 +89,6 @@
         'test',
         'test2',
       ]),
-      'target_id': 'SP_TOO_001',
       'title': 'Command line interface',
       'type': 'spec',
       'type_name': 'Specification',
@@ -119,7 +118,6 @@
       'sections': list([
         'NEED PARTS',
       ]),
-      'target_id': 'TEST_2',
       'title': 'TEST_2',
       'type': 'spec',
       'type_name': 'Specification',
diff --git a/tests/__snapshots__/test_needextend.ambr b/tests/__snapshots__/test_needextend.ambr
index 14c69b16..f252927f 100644
--- a/tests/__snapshots__/test_needextend.ambr
+++ b/tests/__snapshots__/test_needextend.ambr
@@ -67,7 +67,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_1',
             'template': None,
             'title': 'Requirement 1',
             'type': 'req',
@@ -138,7 +137,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_A_1',
             'template': None,
             'title': 'Requirement A 1',
             'type': 'req',
@@ -209,7 +207,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_B_1',
             'template': None,
             'title': 'Requirement B 1',
             'type': 'req',
@@ -279,7 +276,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_C_1',
             'template': None,
             'title': 'Requirement C 1',
             'type': 'req',
@@ -350,7 +346,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'REQ_D_1',
             'template': None,
             'title': 'Requirement D 1',
             'type': 'req',
@@ -731,11 +726,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
@@ -865,7 +855,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'extend_test_003',
             'template': None,
             'title': 'needextend Example 3',
             'type': 'story',
@@ -941,7 +930,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'extend_test_004',
             'template': None,
             'title': 'needextend Example 4',
             'type': 'story',
@@ -1015,7 +1003,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'extend_test_005',
             'template': None,
             'title': 'needextend Example 5',
             'type': 'story',
@@ -1087,7 +1074,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'extend_test_006',
             'template': None,
             'title': 'needextend Example 6',
             'type': 'story',
@@ -1160,7 +1146,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'extend_test_007',
             'template': None,
             'title': 'needextend Example 7',
             'type': 'story',
@@ -1233,7 +1218,6 @@
               'new_tag',
               'another_tag',
             ]),
-            'target_id': 'extend_test_page',
             'template': None,
             'title': 'needextend different pages test',
             'type': 'story',
@@ -1614,11 +1598,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/__snapshots__/test_needimport.ambr b/tests/__snapshots__/test_needimport.ambr
index 1b705a8f..2db64ddf 100644
--- a/tests/__snapshots__/test_needimport.ambr
+++ b/tests/__snapshots__/test_needimport.ambr
@@ -20,7 +20,6 @@
             'sections': list([
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'IMPL_01',
             'title': 'Implementation for specification',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -43,7 +42,6 @@
             'sections': list([
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'OWN_ID_123',
             'title': 'Specification of a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -67,7 +65,6 @@
             'tags': list([
               'example',
             ]),
-            'target_id': 'REQ_001',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -83,7 +80,6 @@
               'FILTERED',
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'REQ_1',
             'title': 'Test requirement 1',
             'type': 'req',
             'type_name': 'Requirement',
@@ -100,7 +96,6 @@
             'sections': list([
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'ROLES_REQ_1',
             'title': 'Sliced Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -117,7 +112,6 @@
             'sections': list([
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'ROLES_REQ_2',
             'title': 'Butter on Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -136,7 +130,6 @@
               'B',
               'filter',
             ]),
-            'target_id': 'R_22EB2',
             'title': 'Requirement B',
             'type': 'req',
             'type_name': 'Requirement',
@@ -155,7 +148,6 @@
               'A',
               'filter',
             ]),
-            'target_id': 'R_2A9D0',
             'title': 'Requirement A',
             'type': 'req',
             'type_name': 'Requirement',
@@ -183,7 +175,6 @@
               'test',
               'awesome',
             ]),
-            'target_id': 'R_F4722',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -199,7 +190,6 @@
               'FILTERED',
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'SPEC_1',
             'title': 'Test specification 1',
             'type': 'spec',
             'type_name': 'Specification',
@@ -218,7 +208,6 @@
               'B',
               'filter',
             ]),
-            'target_id': 'S_01A67',
             'title': 'Specification B',
             'type': 'spec',
             'type_name': 'Specification',
@@ -247,7 +236,6 @@
               'important',
               'example',
             ]),
-            'target_id': 'S_503A1',
             'title': 'Spec for a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -266,7 +254,6 @@
               'A',
               'filter',
             ]),
-            'target_id': 'S_D70B0',
             'title': 'Specification A',
             'type': 'spec',
             'type_name': 'Specification',
@@ -281,7 +268,6 @@
             'sections': list([
               'TEST DOCUMENT IMPORT',
             ]),
-            'target_id': 'TEST_01',
             'title': 'TEST IMPORT DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -302,7 +288,6 @@
               'awesome',
               'filter',
             ]),
-            'target_id': 'T_5CCAA',
             'title': 'Test 1',
             'type': 'test',
             'type_name': 'Test Case',
@@ -334,7 +319,6 @@
               'user_interface',
               'python27',
             ]),
-            'target_id': 'T_C3893',
             'title': 'Test for XY',
             'type': 'test',
             'type_name': 'Test Case',
@@ -361,7 +345,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_IMPL_01',
             'title': 'Implementation for specification',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -389,7 +372,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_OWN_ID_123',
             'title': 'Specification of a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -417,7 +399,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_REQ_001',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -440,7 +421,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_ROLES_REQ_1',
             'title': 'Sliced Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -463,7 +443,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_ROLES_REQ_2',
             'title': 'Butter on Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -486,7 +465,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_R_22EB2',
             'title': 'Requirement B',
             'type': 'req',
             'type_name': 'Requirement',
@@ -509,7 +487,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_R_2A9D0',
             'title': 'Requirement A',
             'type': 'req',
             'type_name': 'Requirement',
@@ -541,7 +518,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_R_F4722',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -564,7 +540,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_S_01A67',
             'title': 'Specification B',
             'type': 'spec',
             'type_name': 'Specification',
@@ -597,7 +572,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_S_503A1',
             'title': 'Spec for a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -620,7 +594,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_S_D70B0',
             'title': 'Specification A',
             'type': 'spec',
             'type_name': 'Specification',
@@ -641,7 +614,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_TEST_01',
             'title': 'TEST IMPORT DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -666,7 +638,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_T_5CCAA',
             'title': 'Test 1',
             'type': 'test',
             'type_name': 'Test Case',
@@ -702,7 +673,6 @@
               'be',
               'collapsed',
             ]),
-            'target_id': 'collapsed_T_C3893',
             'title': 'Test for XY',
             'type': 'test',
             'type_name': 'Test Case',
@@ -724,7 +694,6 @@
             'tags': list([
               'imported',
             ]),
-            'target_id': 'filter_IMPL_01',
             'title': 'Implementation for specification',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -756,7 +725,6 @@
             'tags': list([
               'hidden',
             ]),
-            'target_id': 'hidden_IMPL_01',
             'title': 'Implementation for specification',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -789,7 +757,6 @@
             'tags': list([
               'hidden',
             ]),
-            'target_id': 'hidden_OWN_ID_123',
             'title': 'Specification of a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -822,7 +789,6 @@
               'example',
               'hidden',
             ]),
-            'target_id': 'hidden_REQ_001',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -850,7 +816,6 @@
             'tags': list([
               'hidden',
             ]),
-            'target_id': 'hidden_ROLES_REQ_1',
             'title': 'Sliced Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -878,7 +843,6 @@
             'tags': list([
               'hidden',
             ]),
-            'target_id': 'hidden_ROLES_REQ_2',
             'title': 'Butter on Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -906,7 +870,6 @@
               'filter',
               'hidden',
             ]),
-            'target_id': 'hidden_R_22EB2',
             'title': 'Requirement B',
             'type': 'req',
             'type_name': 'Requirement',
@@ -934,7 +897,6 @@
               'filter',
               'hidden',
             ]),
-            'target_id': 'hidden_R_2A9D0',
             'title': 'Requirement A',
             'type': 'req',
             'type_name': 'Requirement',
@@ -971,7 +933,6 @@
               'awesome',
               'hidden',
             ]),
-            'target_id': 'hidden_R_F4722',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -999,7 +960,6 @@
               'filter',
               'hidden',
             ]),
-            'target_id': 'hidden_S_01A67',
             'title': 'Specification B',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1037,7 +997,6 @@
               'example',
               'hidden',
             ]),
-            'target_id': 'hidden_S_503A1',
             'title': 'Spec for a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1065,7 +1024,6 @@
               'filter',
               'hidden',
             ]),
-            'target_id': 'hidden_S_D70B0',
             'title': 'Specification A',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1087,7 +1045,6 @@
             'tags': list([
               'hidden',
             ]),
-            'target_id': 'hidden_TEST_01',
             'title': 'TEST IMPORT DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -1114,7 +1071,6 @@
               'filter',
               'hidden',
             ]),
-            'target_id': 'hidden_T_5CCAA',
             'title': 'Test 1',
             'type': 'test',
             'type_name': 'Test Case',
@@ -1148,7 +1104,6 @@
               'python27',
               'hidden',
             ]),
-            'target_id': 'hidden_T_C3893',
             'title': 'Test for XY',
             'type': 'test',
             'type_name': 'Test Case',
@@ -1169,7 +1124,6 @@
             'tags': list([
               'imported',
             ]),
-            'target_id': 'ids_ROLES_REQ_1',
             'title': 'Sliced Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1190,7 +1144,6 @@
             'tags': list([
               'imported',
             ]),
-            'target_id': 'ids_ROLES_REQ_2',
             'title': 'Butter on Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1206,7 +1159,6 @@
               'Filter',
             ]),
             'status': 'open',
-            'target_id': 'small2_TEST_03',
             'title': 'AAA',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1226,7 +1178,6 @@
               'test_02',
               'test',
             ]),
-            'target_id': 'small_TEST_02',
             'title': 'TEST_02 DESCRIPTION',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1246,7 +1197,6 @@
               'test_02',
               'test',
             ]),
-            'target_id': 'small_abs_path_TEST_02',
             'title': 'TEST_02 DESCRIPTION',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1261,7 +1211,6 @@
             'sections': list([
               'Deprecated Relative path import test',
             ]),
-            'target_id': 'small_depr_rel_path_TEST_01',
             'title': 'TEST_01 DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -1276,7 +1225,6 @@
             'sections': list([
               'Relative path import test',
             ]),
-            'target_id': 'small_rel_path_TEST_01',
             'title': 'TEST_01 DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -1302,7 +1250,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_IMPL_01',
             'title': 'Implementation for specification',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -1329,7 +1276,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_OWN_ID_123',
             'title': 'Specification of a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1356,7 +1302,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_REQ_001',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1378,7 +1323,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_ROLES_REQ_1',
             'title': 'Sliced Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1400,7 +1344,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_ROLES_REQ_2',
             'title': 'Butter on Bread',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1422,7 +1365,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_R_22EB2',
             'title': 'Requirement B',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1444,7 +1386,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_R_2A9D0',
             'title': 'Requirement A',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1475,7 +1416,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_R_F4722',
             'title': 'My first requirement',
             'type': 'req',
             'type_name': 'Requirement',
@@ -1497,7 +1437,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_S_01A67',
             'title': 'Specification B',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1529,7 +1468,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_S_503A1',
             'title': 'Spec for a requirement',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1551,7 +1489,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_S_D70B0',
             'title': 'Specification A',
             'type': 'spec',
             'type_name': 'Specification',
@@ -1571,7 +1508,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_TEST_01',
             'title': 'TEST IMPORT DESCRIPTION',
             'type': 'impl',
             'type_name': 'Implementation',
@@ -1595,7 +1531,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_T_5CCAA',
             'title': 'Test 1',
             'type': 'test',
             'type_name': 'Test Case',
@@ -1630,7 +1565,6 @@
               'imported',
               'new_tag',
             ]),
-            'target_id': 'test_T_C3893',
             'title': 'Test for XY',
             'type': 'test',
             'type_name': 'Test Case',
@@ -2007,11 +1941,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
@@ -2138,7 +2067,6 @@
       'tags': list([
         'ext_test',
       ]),
-      'target_id': 'IMP_TEST_101',
       'template': None,
       'title': 'TEST_101 TITLE',
       'type': 'impl',
@@ -2217,7 +2145,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'SPEC_1',
       'template': None,
       'title': 'A Spec',
       'type': 'spec',
@@ -2296,7 +2223,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'STORY_1',
       'template': None,
       'title': 'A story',
       'type': 'story',
diff --git a/tests/__snapshots__/test_needs_builder.ambr b/tests/__snapshots__/test_needs_builder.ambr
index 9e702dba..7ce10472 100644
--- a/tests/__snapshots__/test_needs_builder.ambr
+++ b/tests/__snapshots__/test_needs_builder.ambr
@@ -64,7 +64,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'TC_001',
             'template': None,
             'title': 'Test example',
             'type': 'test',
@@ -134,7 +133,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'TC_NEG_001',
             'template': None,
             'title': 'Negative test example',
             'type': 'test',
@@ -205,7 +203,6 @@
             'tags': list([
               '1',
             ]),
-            'target_id': 'US_63252',
             'template': None,
             'title': 'A story',
             'type': 'story',
@@ -586,11 +583,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
@@ -831,7 +823,6 @@
               'TEST DOCUMENT NEEDS Builder',
             ]),
             'status': 'open',
-            'target_id': 'TC_001',
             'title': 'Test example',
             'type': 'test',
             'type_name': 'Test Case',
@@ -847,7 +838,6 @@
               'TEST DOCUMENT NEEDS Builder',
             ]),
             'status': 'closed',
-            'target_id': 'TC_NEG_001',
             'title': 'Negative test example',
             'type': 'test',
             'type_name': 'Test Case',
@@ -866,7 +856,6 @@
             'tags': list([
               '1',
             ]),
-            'target_id': 'US_63252',
             'title': 'A story',
             'type': 'story',
             'type_name': 'User Story',
@@ -1243,11 +1232,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
@@ -1537,7 +1521,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'TC_001',
             'template': None,
             'title': 'Test example',
             'type': 'test',
@@ -1607,7 +1590,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'TC_NEG_001',
             'template': None,
             'title': 'Negative test example',
             'type': 'test',
@@ -1678,7 +1660,6 @@
             'tags': list([
               '1',
             ]),
-            'target_id': 'US_63252',
             'template': None,
             'title': 'A story',
             'type': 'story',
@@ -2059,11 +2040,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/__snapshots__/test_needs_id_builder.ambr b/tests/__snapshots__/test_needs_id_builder.ambr
index 50df501e..f84654dc 100644
--- a/tests/__snapshots__/test_needs_id_builder.ambr
+++ b/tests/__snapshots__/test_needs_id_builder.ambr
@@ -65,7 +65,6 @@
               'style': None,
               'tags': list([
               ]),
-              'target_id': 'TC_001',
               'template': None,
               'title': 'Test example',
               'type': 'test',
@@ -145,7 +144,6 @@
               'style': None,
               'tags': list([
               ]),
-              'target_id': 'TC_NEG_001',
               'template': None,
               'title': 'Negative test example',
               'type': 'test',
@@ -226,7 +224,6 @@
               'tags': list([
                 '1',
               ]),
-              'target_id': 'US_63252',
               'template': None,
               'title': 'A story',
               'type': 'story',
diff --git a/tests/__snapshots__/test_needuml.ambr b/tests/__snapshots__/test_needuml.ambr
index e5d1fd38..2007a5a9 100644
--- a/tests/__snapshots__/test_needuml.ambr
+++ b/tests/__snapshots__/test_needuml.ambr
@@ -76,7 +76,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'COMP_001',
       'template': None,
       'title': 'Test component',
       'type': 'comp',
@@ -155,7 +154,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'INT_001',
       'template': None,
       'title': 'Test interface',
       'type': 'int',
@@ -267,7 +265,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'INT_002',
       'template': None,
       'title': 'Test interface2',
       'type': 'int',
@@ -378,7 +375,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'INT_003',
       'template': None,
       'title': 'Test interface3',
       'type': 'int',
@@ -504,7 +500,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'INT_004',
       'template': None,
       'title': 'Test interface4',
       'type': 'int',
@@ -589,7 +584,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'PROD_001',
       'template': None,
       'title': 'Test Product',
       'type': 'prod',
@@ -668,7 +662,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'ST_001',
       'template': None,
       'title': 'Test story',
       'type': 'story',
@@ -747,7 +740,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'ST_002',
       'template': None,
       'title': 'Test story2',
       'type': 'story',
@@ -835,7 +827,6 @@
       'style': None,
       'tags': list([
       ]),
-      'target_id': 'SYS_001',
       'template': None,
       'title': 'Test System',
       'type': 'sys',
diff --git a/tests/__snapshots__/test_service_github.ambr b/tests/__snapshots__/test_service_github.ambr
index 5be6e3cb..b164a980 100644
--- a/tests/__snapshots__/test_service_github.ambr
+++ b/tests/__snapshots__/test_service_github.ambr
@@ -67,7 +67,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'GITHUB_050bec',
             'template': None,
             'title': 'Bump actions/cache from 3 to 4 (#1092)',
             'type': 'commit',
@@ -145,7 +144,6 @@
             'tags': list([
               'bug',
             ]),
-            'target_id': 'GITHUB_1110',
             'template': None,
             'title': 'needreport usage should count needs in post-process',
             'type': 'issue',
@@ -244,7 +242,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'GITHUB_1112',
             'template': None,
             'title': '👌 Capture `only` expressions for each need',
             'type': 'pr',
@@ -328,7 +325,6 @@
             'style': None,
             'tags': list([
             ]),
-            'target_id': 'GITHUB_6abd38',
             'template': None,
             'title': '🧪 Add test for `needreport` directive (#1105)',
             'type': 'commit',
@@ -703,11 +699,6 @@
               }),
               'type': 'array',
             }),
-            'target_id': dict({
-              'description': 'ID of the data.',
-              'field_type': 'core',
-              'type': 'string',
-            }),
             'template': dict({
               'default': None,
               'description': 'Template of the need.',
diff --git a/tests/test_needarch.py b/tests/test_needarch.py
index a6a8467d..053c4a30 100644
--- a/tests/test_needarch.py
+++ b/tests/test_needarch.py
@@ -1,6 +1,7 @@
 from pathlib import Path
 
 import pytest
+from syrupy.filters import props
 
 
 @pytest.mark.parametrize(
@@ -52,7 +53,7 @@ def test_doc_needarch_jinja_import(test_app, snapshot):
 
     # check needarch
     all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
 
 @pytest.mark.parametrize(
@@ -65,7 +66,7 @@ def test_needarch_jinja_func_need(test_app, snapshot):
     app.build()
 
     all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
     html = Path(app.outdir, "index.html").read_text(encoding="utf8")
     assert "as INT_001 [[../index.html#INT_001]]" in html
diff --git a/tests/test_needuml.py b/tests/test_needuml.py
index 2e8ca0c0..a0ebfe69 100644
--- a/tests/test_needuml.py
+++ b/tests/test_needuml.py
@@ -2,6 +2,7 @@ import subprocess
 from pathlib import Path
 
 import pytest
+from syrupy.filters import props
 
 from sphinx_needs.data import SphinxNeedsData
 
@@ -17,11 +18,13 @@ def test_doc_build_html(test_app, snapshot):
 
     assert Path(app.outdir, "index.html").read_text(encoding="utf8")
 
-    all_needs = dict(SphinxNeedsData(app.env).get_needs_view())
+    data = SphinxNeedsData(app.env)
+
+    all_needs = dict(data.get_needs_view())
     assert all_needs == snapshot()
 
-    all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    all_needumls = data.get_or_create_umls()
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
 
 @pytest.mark.parametrize(
@@ -170,7 +173,7 @@ def test_needuml_filter(test_app, snapshot):
     app.build()
 
     all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
     html = Path(app.outdir, "index.html").read_text(encoding="utf8")
     assert "as ST_002 [[../index.html#ST_002]]" in html
@@ -194,7 +197,7 @@ def test_needuml_jinja_func_flow(test_app, snapshot):
     app.build()
 
     all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
     html = Path(app.outdir, "index.html").read_text(encoding="utf8")
     assert "as ST_001 [[../index.html#ST_001]]" in html
@@ -268,7 +271,7 @@ def test_needuml_jinja_func_ref(test_app, snapshot):
     app.build()
 
     all_needumls = app.env._needs_all_needumls
-    assert all_needumls == snapshot
+    assert all_needumls == snapshot(exclude=props("process_time"))
 
     html = Path(app.outdir, "index.html").read_text(encoding="utf8")
     assert "Marvel: [[../index.html#ST_001 Test story]]" in html