Repository: nautobot/nautobot-app-netbox-importer
Pull request #207: Tag Issues
Author: login: snaselj
Created: 2025-05-02T12:27:07Z | Merged: 2025-05-22T14:09:22Z

# Closes NCLOUD-2836

## What's Changed

- Added tagging all Nautobot instances with issues.



Diff:
diff --git a/changes/207.added b/changes/207.added
new file mode 100644
index 00000000..01db11a8
--- /dev/null
+++ b/changes/207.added
@@ -0,0 +1 @@
+Added tagging all Nautobot instances with issues.
diff --git a/docs/user/tagging_issues.md b/docs/user/tagging_issues.md
new file mode 100644
index 00000000..e17d4326
--- /dev/null
+++ b/docs/user/tagging_issues.md
@@ -0,0 +1,13 @@
+# Tagging Importer Issues
+
+This document outlines the feature to tag Nautobot records during import with associated issues.
+
+## Feature Overview
+
+The tagging importer feature allows users to tag Nautobot records during the import process. This is useful for investigating and managing records that were imported with some issues.
+
+## Usage
+
+To tag issues during import, users can use the `--tag-issues` option in `nautobot-server import-netbox` command. This option enables the tagging feature. It's also possible to use this flag with `invoke import-netbox` command, that is a wrapper to the previous one.
+
+After the import process, users can view the tagged records in Nautobot. To see all tags, open `Organization` > `Tags` in the Nautobot UI. The tags will be listed there, and users can see related records in the tag detail view.
diff --git a/mkdocs.yml b/mkdocs.yml
index 2dfd23e3..62c6dea8 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -109,6 +109,7 @@ nav:
       - Getting Started: "user/app_getting_started.md"
       - Using the App: "user/app_use_cases.md"
       - Importer Summary: "user/summary.md"
+      - Tagging Issues: "user/tagging_issues.md"
       - Frequently Asked Questions: "user/faq.md"
   - Administrator Guide:
       - Install and Configure: "admin/install.md"
diff --git a/nautobot_netbox_importer/diffsync/adapters/netbox.py b/nautobot_netbox_importer/diffsync/adapters/netbox.py
index 934d9b55..557a2761 100644
--- a/nautobot_netbox_importer/diffsync/adapters/netbox.py
+++ b/nautobot_netbox_importer/diffsync/adapters/netbox.py
@@ -48,6 +48,7 @@ class NetBoxImporterOptions(NamedTuple):
     update_paths: bool = False
     fix_powerfeed_locations: bool = False
     sitegroup_parent_always_region: bool = False
+    tag_issues: bool = False
     unrack_zero_uheight_devices: bool = True
     save_json_summary_path: str = ""
     save_text_summary_path: str = ""
@@ -90,23 +91,37 @@ def load(self) -> None:
 
     def import_to_nautobot(self) -> None:
         """Import a NetBox export file into Nautobot."""
+        exception = None
         commited = False
         try:
-            self._atomic_import()
+            self._atomic_import()  # type: ignore
             commited = True
-        except _DryRunException:
-            logger.warning("Dry-run mode, no data has been imported.")
-        except _ImporterIssuesDetected:
-            logger.warning("Importer issues detected, no data has been imported.")
+        except (_DryRunException, _ImporterIssuesDetected) as error:
+            exception = error
+            logger.info("Data were not saved: %s", error)
 
-        if commited and self.options.update_paths:
-            logger.info("Updating paths ...")
-            call_command("trace_paths", no_input=True)
-            logger.info(" ... Updating paths completed.")
+        if self.options.save_json_summary_path:
+            self.summary.dump(self.options.save_json_summary_path, output_format="json")
+        if self.options.save_text_summary_path:
+            self.summary.dump(self.options.save_text_summary_path, output_format="text")
+
+        if commited:
+            if self.options.update_paths:
+                logger.info("Updating paths ...")
+                call_command("trace_paths", no_input=True)
+                logger.info(" ... Updating paths completed.")
+
+            if self.options.tag_issues:
+                self.nautobot.tag_issues(self.summary)  # type: ignore
 
         if self.options.print_summary:
             self.summary.print()
 
+        if commited:
+            logger.info("Import completed successfully.")
+        else:
+            logger.error("Data were not saved %s", exception)
+
     @atomic
     def _atomic_import(self) -> None:
         self.load()
@@ -121,10 +136,10 @@ def _atomic_import(self) -> None:
 
         has_issues = any(True for item in self.summary.nautobot if item.issues)
         if has_issues and not self.options.bypass_data_validation:
-            raise _ImporterIssuesDetected("Importer issues detected, aborting the transaction.")
+            raise _ImporterIssuesDetected("Aborting the transaction: importer issues detected.")
 
         if self.options.dry_run:
-            raise _DryRunException("Aborting the transaction due to the dry-run mode.")
+            raise _DryRunException("Aborting the transaction: dry-run mode.")
 
 
 def _read_stream(stream) -> Generator[SourceRecord, None, None]:
diff --git a/nautobot_netbox_importer/diffsync/models/dcim.py b/nautobot_netbox_importer/diffsync/models/dcim.py
index c5c99b3a..b6c6c1d1 100644
--- a/nautobot_netbox_importer/diffsync/models/dcim.py
+++ b/nautobot_netbox_importer/diffsync/models/dcim.py
@@ -145,7 +145,7 @@ def unrack_zero_uheight_devices(adapter: SourceAdapter) -> None:
         if getattr(item, "position", None) and getattr(item, "device_type_id") in device_type_ids:
             position.set_nautobot_value(item, None)
             adapter.update(item)
-            position.add_issue("Unracked", "Device unracked due to 0U height", item)
+            position.add_issue("UnrackedIssue", "Device unracked due to 0U height", item)
 
 
 # pylint: disable=too-many-locals
diff --git a/nautobot_netbox_importer/generator/__init__.py b/nautobot_netbox_importer/generator/__init__.py
index 641d990a..c1625421 100644
--- a/nautobot_netbox_importer/generator/__init__.py
+++ b/nautobot_netbox_importer/generator/__init__.py
@@ -13,7 +13,7 @@
     SourceDataGenerator,
     SourceField,
     SourceFieldImporterFactory,
-    SourceFieldImporterIssue,
+    SourceFieldIssue,
     SourceModelWrapper,
     SourceRecord,
     SourceReferences,
@@ -32,7 +32,7 @@
     "SourceDataGenerator",
     "SourceField",
     "SourceFieldImporterFactory",
-    "SourceFieldImporterIssue",
+    "SourceFieldIssue",
     "SourceModelWrapper",
     "SourceRecord",
     "SourceReferences",
diff --git a/nautobot_netbox_importer/generator/fields.py b/nautobot_netbox_importer/generator/fields.py
index 7b947325..edaac635 100644
--- a/nautobot_netbox_importer/generator/fields.py
+++ b/nautobot_netbox_importer/generator/fields.py
@@ -6,6 +6,7 @@
 from .base import EMPTY_VALUES, ContentTypeStr, Uid
 from .nautobot import DiffSyncBaseModel
 from .source import (
+    FallbackValueIssue,
     FieldName,
     ImporterPass,
     InternalFieldType,
@@ -17,7 +18,6 @@
     SourceField,
     SourceFieldDefinition,
     SourceFieldImporterFallback,
-    SourceFieldImporterIssue,
     SourceModelWrapper,
 )
 
@@ -62,10 +62,7 @@ def fallback_importer(source: RecordData, target: DiffSyncBaseModel) -> None:
                     field.set_nautobot_value(target, value)
                     if isinstance(error, InvalidChoiceValueIssue):
                         raise InvalidChoiceValueIssue(field, field.get_source_value(source), value) from error
-                    raise SourceFieldImporterIssue(
-                        f"Failed to import field: {error} | Fallback value: {value}",
-                        field,
-                    ) from error
+                    raise FallbackValueIssue(field, value) from error
                 raise
 
         field.set_importer(fallback_importer, override=True)
diff --git a/nautobot_netbox_importer/generator/nautobot.py b/nautobot_netbox_importer/generator/nautobot.py
index ad737beb..acfb7a67 100644
--- a/nautobot_netbox_importer/generator/nautobot.py
+++ b/nautobot_netbox_importer/generator/nautobot.py
@@ -8,9 +8,16 @@
 from django.db.models import Max
 from django.db.transaction import atomic
 from nautobot.core.utils.lookup import get_model_from_name
+from nautobot.extras.models import Tag
 
 from nautobot_netbox_importer.base import FieldName, RecordData, logger
-from nautobot_netbox_importer.summary import ImporterIssue, NautobotModelStats, NautobotModelSummary
+from nautobot_netbox_importer.summary import (
+    ImporterIssue,
+    ImportSummary,
+    NautobotModelStats,
+    NautobotModelSummary,
+    get_issue_tag,
+)
 
 from .base import (
     AUTO_ADD_FIELDS,
@@ -28,6 +35,7 @@
     Uid,
     get_nautobot_field_and_type,
     normalize_datetime,
+    source_pk_to_uuid,
 )
 from .exceptions import NautobotModelNotFound
 
@@ -125,6 +133,14 @@ def get_or_create_wrapper(self, content_type: ContentTypeStr) -> "NautobotModelW
 
         return NautobotModelWrapper(self, content_type)
 
+    @atomic
+    def tag_issues(self, summary: ImportSummary) -> None:
+        """Tag all records with any ImporterIssue."""
+        logger.info("Tagging all instance with issues")
+
+        for item in summary.nautobot:
+            self.get_or_create_wrapper(item.content_type).tag_issues(item.issues)
+
 
 class NautobotField:
     """Wrapper for a Nautobot field."""
@@ -380,7 +396,7 @@ def _create_issue(  # noqa: PLR0912, PLR0913
         """Create an issue."""
         if not issue_type:
             if error:
-                issue_type = error.__class__.__name__
+                issue_type = getattr(error, "issue_type", None) or error.__class__.__name__
             else:
                 issue_type = "Unknown"
 
@@ -658,6 +674,41 @@ def save():
 
         return True
 
+    def tag_issues(self, issues: Iterable[ImporterIssue]) -> None:
+        """Tag all records with any ImporterIssue."""
+        if not issues:
+            return
+
+        model = self.model
+
+        if not hasattr(model, "tags"):
+            return
+
+        for issue in issues:
+            if not issue.uid:
+                continue
+
+            try:
+                nautobot_instance = model.objects.get(id=issue.uid)
+            except model.DoesNotExist:  # type: ignore
+                continue
+
+            tag_name = get_issue_tag(issue)
+            tag, _ = Tag.objects.get_or_create(
+                id=source_pk_to_uuid("extras.tag", tag_name),
+                defaults={
+                    "name": tag_name,
+                    "description": f"Import issue: {tag_name}",
+                },
+            )
+
+            logger.debug("Tagging %s %s %s", self.content_type, issue.uid, tag_name)
+
+            if not tag.content_types.filter(id=self.content_type_id).exists():
+                tag.content_types.add(self.content_type_instance)
+
+            nautobot_instance.tags.add(tag)
+
 
 class DiffSyncBaseModel(DiffSyncModel):
     """Base class for all DiffSync models."""
diff --git a/nautobot_netbox_importer/generator/source.py b/nautobot_netbox_importer/generator/source.py
index 4a52bc5e..8a9b316a 100644
--- a/nautobot_netbox_importer/generator/source.py
+++ b/nautobot_netbox_importer/generator/source.py
@@ -65,15 +65,47 @@
 from nautobot_netbox_importer.utils import get_field_choices
 
 
-class SourceFieldImporterIssue(NetBoxImporterException):
+class SourceFieldIssue(NetBoxImporterException):
     """Raised when an error occurs during field import."""
 
-    def __init__(self, message: str, field: "SourceField"):
+    def __init__(self, message: str, field: "SourceField", issue_type=""):
         """Initialize the exception."""
         super().__init__(str({field.name: message}))
 
+        if not issue_type:
+            issue_type = f"{self.__class__.__name__}-{field.name}"
 
-class InvalidChoiceValueIssue(SourceFieldImporterIssue):
+        self.issue_type = issue_type
+
+
+class UpdatedValueIssue(SourceFieldIssue):
+    """Raised when a value is updated."""
+
+    def __init__(self, field: "SourceField", source_value: Any, target_value: Any):
+        """Initialize the exception."""
+        message = f"Value `{source_value}` updated to `{target_value}`"
+        super().__init__(message, field)
+
+
+class FallbackValueIssue(SourceFieldIssue):
+    """Raised when a fallback value is used."""
+
+    def __init__(self, field: "SourceField", target_value: Any):
+        """Initialize the exception."""
+        message = f"Falling back to: `{target_value}`"
+        super().__init__(message, field)
+
+
+class TruncatedValueIssue(SourceFieldIssue):
+    """Raised when a value is truncated."""
+
+    def __init__(self, field: "SourceField", source_value: Any, target_value: Any):
+        """Initialize the exception."""
+        message = f"Value `{source_value}` truncated to `{target_value}`"
+        super().__init__(message, field)
+
+
+class InvalidChoiceValueIssue(SourceFieldIssue):
     """Raised when an invalid choice value is encountered."""
 
     def __init__(self, field: "SourceField", value: Any, replacement: Any = NOTHING):
@@ -1064,7 +1096,7 @@ def integer_importer(source: RecordData, target: DiffSyncBaseModel) -> None:
                 value = int(source_value)
                 self.set_nautobot_value(target, value)
                 if value != source_value:
-                    raise SourceFieldImporterIssue(f"Invalid source value {source_value}, truncated to {value}", self)
+                    raise TruncatedValueIssue(self, source_value, value)
 
         self.set_importer(integer_importer)
 
diff --git a/nautobot_netbox_importer/management/commands/import_netbox.py b/nautobot_netbox_importer/management/commands/import_netbox.py
index aabc0f57..02a955d1 100644
--- a/nautobot_netbox_importer/management/commands/import_netbox.py
+++ b/nautobot_netbox_importer/management/commands/import_netbox.py
@@ -46,6 +46,12 @@ def add_arguments(self, parser):
             "to be a `Region` location type. This is a workaround to fix validation errors "
             "`'A Location of type Location may only have a Location of the same type as its parent.'`.",
         )
+        parser.add_argument(
+            "--tag-issues",
+            action="store_true",
+            dest="tag_issues",
+            help="Whether to tag Nautobot records with any importer issues.",
+        )
         parser.add_argument(
             "--fix-powerfeed-locations",
             action="store_true",
diff --git a/nautobot_netbox_importer/summary.py b/nautobot_netbox_importer/summary.py
index 69a21b9a..eee54035 100644
--- a/nautobot_netbox_importer/summary.py
+++ b/nautobot_netbox_importer/summary.py
@@ -1,11 +1,19 @@
 """Importer summary module."""
 
 import json
+import re
 from pathlib import Path
-from typing import Callable, Dict, Generator, Iterable, List, Mapping, NamedTuple, Optional, Union
+from typing import Callable, Dict, Generator, Iterable, List, Mapping, NamedTuple, Optional, Pattern, Union
 
 from .base import ContentTypeStr, FieldName, Pathable
 
+_TAG_EXPRESSIONS: Mapping[str, Pattern[str]] = {
+    "InvalidCircuit": re.compile("A circuit termination must attach to either a location or a provider network"),
+    "IncompatibleTerminationTypes": re.compile("Incompatible termination types"),
+    "InvalidPlatform": re.compile("assigned platform is limited to"),
+    "MissingParentLocation": re.compile(r"A Location of type .* must have a parent Location"),
+}
+
 
 class ImporterIssue(NamedTuple):
     """Importer issue."""
@@ -324,3 +332,13 @@ def get_field(field: FieldSummary):
                 for field in summary.fields:
                     if "DATA" in field.sources:
                         yield " ".join(get_field(field))
+
+
+def get_issue_tag(issue: ImporterIssue) -> str:
+    """Get a tag for an issue."""
+    if issue.issue_type == "ValidationError":
+        for key, expression in _TAG_EXPRESSIONS.items():
+            if re.search(expression, issue.message):
+                return key
+
+    return issue.issue_type
diff --git a/tasks.py b/tasks.py
index 41a15075..8f348c3a 100644
--- a/tasks.py
+++ b/tasks.py
@@ -1047,6 +1047,7 @@ def validate_app_config(context):
         "sitegroup-parent-always-region": "When importing `dcim.sitegroup` to `dcim.locationtype`, always set the parent of a site group, to be a `Region` location type. This is a workaround to fix validation errors `'A Location of type Location may only have a Location of the same type as its parent.'`. (default: False)",
         "update-paths": "Call management command `trace_paths` to update paths after the import. (default: False)",
         "unrack-zero-uheight-devices": "Cleans the `position` field in `dcim.device` instances with `u_height == 0`. (default: True)",
+        "tag-issues": "Whether to tag Nautobot records with any importer issues. (default: False)",
         "trace-issues": "Show a detailed trace of issues originated from any `Exception` found during the import.",
     }
 )
@@ -1063,6 +1064,7 @@ def import_netbox(  # noqa: PLR0913
     print_summary=True,
     update_paths=False,
     unrack_zero_uheight_devices=True,
+    tag_issues=False,
     trace_issues=False,
 ):
     """Import NetBox data into Nautobot."""
@@ -1089,6 +1091,7 @@ def import_netbox(  # noqa: PLR0913
         "--update-paths" if update_paths else "",
         "--no-color",
         "" if unrack_zero_uheight_devices else "--no-unrack-zero-uheight-devices",
+        "--tag-issues" if tag_issues else "",
         "--trace-issues" if trace_issues else "",
         file,
     ]


Reviews:
user: login: HanlinMiao
body: Is it too much to ask to see some of this feature in action?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T14:46:23Z
user: login: snaselj
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T06:28:31Z

Review comments:
diff_hunk: @@ -0,0 +1,13 @@
+# Tagging Importer Issues
+
+This document outlines the feature to tag Nautobot records during import with associated issues.
+
+## Feature Overview
+
+The tagging importer feature allows users to tag Nautobot records during the import process. This is useful for investigating and managing records that were imported with some issues.
+
+## Usage
+
+To tag issues during import, users can use the `--tag-issues` option in `nautobot-server import-netbox` command. This option enables the tagging feature. It's also possible to use this flag with `invoke import-netbox` command, that is a wrapper to the previous one.
+
+After the import process, users can view the tagged records in Nautobot. To see all tags, open `Organization` > `Tags` in the Nautobot UI. The tags will be listed there, and users can show related records in the tag detail view.
path: docs/user/tagging_issues.md
user: login: HanlinMiao
body: ```suggestion
After the import process, users can view the tagged records in Nautobot. To see all tags, open `Organization` > `Tags` in the Nautobot UI. The tags will be listed there, and users can see related records in the tag detail view.
```
created_at: 2025-05-20T14:11:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,15 +65,47 @@
 from nautobot_netbox_importer.utils import get_field_choices
 
 
-class SourceFieldImporterIssue(NetBoxImporterException):
+class SourceFieldIssue(NetBoxImporterException):
     """Raised when an error occurs during field import."""
 
-    def __init__(self, message: str, field: "SourceField"):
+    def __init__(self, message: str, field: "SourceField", issue_type=""):
         """Initialize the exception."""
         super().__init__(str({field.name: message}))
 
+        if not issue_type:
+            issue_type = f"{self.__class__.__name__}-{field.name}"
 
-class InvalidChoiceValueIssue(SourceFieldImporterIssue):
+        self.issue_type = issue_type
+
+
+class UpdatedValueIssue(SourceFieldIssue):
+    """Raised when a value is updated."""
+
+    def __init__(self, field: "SourceField", source_value: Any, target_value: Any):
+        """Initialize the exception."""
+        message = f"Value `{source_value}` updated to `{target_value}`"
+        super().__init__(message, field)
+
+
+class FallbackValueIssue(SourceFieldIssue):
+    """Raised when a fallback value is used."""
+
+    def __init__(self, field: "SourceField", target_value: Any):
+        """Initialize the exception."""
+        message = f"Falling back to: `{target_value}`"
+        super().__init__(message, field)
path: nautobot_netbox_importer/generator/source.py
user: login: HanlinMiao
body: Perhaps I missed it, but are these two issues used anywhere?
created_at: 2025-05-20T14:38:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,15 +65,47 @@
 from nautobot_netbox_importer.utils import get_field_choices
 
 
-class SourceFieldImporterIssue(NetBoxImporterException):
+class SourceFieldIssue(NetBoxImporterException):
     """Raised when an error occurs during field import."""
 
-    def __init__(self, message: str, field: "SourceField"):
+    def __init__(self, message: str, field: "SourceField", issue_type=""):
         """Initialize the exception."""
         super().__init__(str({field.name: message}))
 
+        if not issue_type:
+            issue_type = f"{self.__class__.__name__}-{field.name}"
 
-class InvalidChoiceValueIssue(SourceFieldImporterIssue):
+        self.issue_type = issue_type
+
+
+class UpdatedValueIssue(SourceFieldIssue):
+    """Raised when a value is updated."""
+
+    def __init__(self, field: "SourceField", source_value: Any, target_value: Any):
+        """Initialize the exception."""
+        message = f"Value `{source_value}` updated to `{target_value}`"
+        super().__init__(message, field)
+
+
+class FallbackValueIssue(SourceFieldIssue):
+    """Raised when a fallback value is used."""
+
+    def __init__(self, field: "SourceField", target_value: Any):
+        """Initialize the exception."""
+        message = f"Falling back to: `{target_value}`"
+        super().__init__(message, field)
path: nautobot_netbox_importer/generator/source.py
user: login: snaselj
body: `FallbackValueIssue` is used in the code.

Removed `UpdatedValueIssue` as unused.
created_at: 2025-05-21T06:28:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: snaselj
created_at: 2025-05-21T06:17:31Z
body: > Is it too much to ask to see some of this feature in action?

Yeah, this will happen with these PRs, but don't have a clear solution to this.

It's used in the followup PR:

https://github.com/nautobot/nautobot-app-netbox-importer/pull/197/files#diff-6ae0de9a74173d82078de595d7be741d67d185e69d3dbfa120a0fec1530b907cR80

Explanation:

I did many changes to be able to import customers data and support NetBox 4 data structures. PR mentioned above contains all changes, that are split into individual PRs based on separate features the way to pass CI.

The implementation part (nautobot_netbox_importer/diffsync) uses multiple new features at once, and it's not possible to split it in a meaningful way.

Once these feature PRs will be merged, we can get the final "implementation" PR to be prepared.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T06:42:13Z
body: ## Coverage report


<img title="Coverage for the whole project is 85.66%. Previous coverage rate is not available, cannot report on evolution." src="https://img.shields.io/badge/Coverage-85%25-orange.svg"> <img title="47.56% of the statement lines added by this PR are covered" src="https://img.shields.io/badge/PR%20Coverage-47%25-red.svg"><details><summary>Click to see where and how coverage changed</summary><table><thead>
  <tr><th>File</th><th>Statements</th><th>Missing</th><th>Coverage</th><th>Coverage<br>(new stmts)</th><th>Lines missing</th></tr>
</thead>
<tbody><tr>
<td colspan="6">&nbsp;&nbsp;<b>nautobot_netbox_importer</b></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269">summary.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269"><img title="This PR adds 199 statements to nautobot_netbox_importer/summary.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/199-%28%2B199%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269"><img title="This PR adds 15 statements missing coverage to nautobot_netbox_importer/summary.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/15-%28%2B15%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269"><img title="The coverage rate of nautobot_netbox_importer/summary.py is 92.46% (184/199). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/92%25-%28184/199%29-brightgreen.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269"><img title="In this PR, 9 new statements are added to nautobot_netbox_importer/summary.py, 4 of which are covered (44.44%)." src="https://img.shields.io/badge/44%25-%284/9%29-red.svg"></a></td><td><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-e6254e05b3e1a9a77362c5cffa7c9cb3dc0cb58abfa8ae3b93853f5da1845269R339-R344">339-344</a></td><tr>
<td colspan="6">&nbsp;&nbsp;<b>nautobot_netbox_importer/diffsync/adapters</b></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337">netbox.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337"><img title="This PR adds 125 statements to nautobot_netbox_importer/diffsync/adapters/netbox.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/125-%28%2B125%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337"><img title="This PR adds 23 statements missing coverage to nautobot_netbox_importer/diffsync/adapters/netbox.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/23-%28%2B23%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337"><img title="The coverage rate of nautobot_netbox_importer/diffsync/adapters/netbox.py is 81.6% (102/125). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/81%25-%28102/125%29-orange.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337"><img title="In this PR, 22 new statements are added to nautobot_netbox_importer/diffsync/adapters/netbox.py, 10 of which are covered (45.45%)." src="https://img.shields.io/badge/45%25-%2810/22%29-red.svg"></a></td><td><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R99-R101">99-101</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R104-R104">104</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R106-R106">106</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R110-R112">110-112</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R115-R115">115</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R123-R123">123</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R139-R139">139</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-d46800d1b114a093f9167206b174e627f77b2ffe32e49bb1993be2770767e337R142-R142">142</a></td><tr>
<td colspan="6">&nbsp;&nbsp;<b>nautobot_netbox_importer/diffsync/models</b></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-b2291081e18b7cc9df7debbcb732c99af7fc75380e5d9a9bfdfed8f8c5323d54">dcim.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-b2291081e18b7cc9df7debbcb732c99af7fc75380e5d9a9bfdfed8f8c5323d54"><img title="This PR adds 81 statements to nautobot_netbox_importer/diffsync/models/dcim.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/81-%28%2B81%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-b2291081e18b7cc9df7debbcb732c99af7fc75380e5d9a9bfdfed8f8c5323d54"><img title="This PR adds 35 statements missing coverage to nautobot_netbox_importer/diffsync/models/dcim.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/35-%28%2B35%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-b2291081e18b7cc9df7debbcb732c99af7fc75380e5d9a9bfdfed8f8c5323d54"><img title="The coverage rate of nautobot_netbox_importer/diffsync/models/dcim.py is 56.79% (46/81). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/56%25-%2846/81%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-b2291081e18b7cc9df7debbcb732c99af7fc75380e5d9a9bfdfed8f8c5323d54"><img title="In this PR, 1 new statements are added to nautobot_netbox_importer/diffsync/models/dcim.py, 1 of which are covered (100%)." src="https://img.shields.io/badge/100%25-%281/1%29-brightgreen.svg"></a></td><td></td><tr>
<td colspan="6">&nbsp;&nbsp;<b>nautobot_netbox_importer/generator</b></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23">fields.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23"><img title="This PR adds 104 statements to nautobot_netbox_importer/generator/fields.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/104-%28%2B104%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23"><img title="This PR adds 23 statements missing coverage to nautobot_netbox_importer/generator/fields.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/23-%28%2B23%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23"><img title="The coverage rate of nautobot_netbox_importer/generator/fields.py is 77.88% (81/104). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/77%25-%2881/104%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23"><img title="In this PR, 1 new statements are added to nautobot_netbox_importer/generator/fields.py, 0 of which are covered (0%)." src="https://img.shields.io/badge/0%25-%280/1%29-red.svg"></a></td><td><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-53f1bcfb5f8c03c2fb0413f5234a684a46c1419636672e0c3580025d80872a23R65-R65">65</a></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cd">nautobot.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cd"><img title="This PR adds 372 statements to nautobot_netbox_importer/generator/nautobot.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/372-%28%2B372%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cd"><img title="This PR adds 67 statements missing coverage to nautobot_netbox_importer/generator/nautobot.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/67-%28%2B67%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cd"><img title="The coverage rate of nautobot_netbox_importer/generator/nautobot.py is 81.98% (305/372). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/81%25-%28305/372%29-orange.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cd"><img title="In this PR, 27 new statements are added to nautobot_netbox_importer/generator/nautobot.py, 6 of which are covered (22.22%)." src="https://img.shields.io/badge/22%25-%286/27%29-red.svg"></a></td><td><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cdR139-R142">139-142</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-5b3333509f61bf1ffa68e4d314073a1863b94f2c33fae12df9dba3e9171609cdR679-R710">679-710</a></td><tr>
<td>&nbsp;&nbsp;<a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39d">source.py</a></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39d"><img title="This PR adds 764 statements to nautobot_netbox_importer/generator/source.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/764-%28%2B764%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39d"><img title="This PR adds 93 statements missing coverage to nautobot_netbox_importer/generator/source.py. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/93-%28%2B93%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39d"><img title="The coverage rate of nautobot_netbox_importer/generator/source.py is 87.82% (671/764). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/87%25-%28671/764%29-orange.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39d"><img title="In this PR, 22 new statements are added to nautobot_netbox_importer/generator/source.py, 18 of which are covered (81.81%)." src="https://img.shields.io/badge/81%25-%2818/22%29-orange.svg"></a></td><td><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39dR86-R87">86-87</a>, <a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-fe77f230ddce96ba9a9d7b349e5e7acfe3c00818cd1818c543d2c1f29504d39dR95-R96">95-96</a></td></tbody>
<tfoot>
<tr>
<td><b>Project Total</b></td>
<td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-4b0bf2efa3367c0072ac2bf1e234e703dc46b47aaa4fe9d3b01737b1a15752b1"><img title="This PR adds 2121 statements to the whole project. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/2.1k-%28%2B2121%29-007ec6.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-4b0bf2efa3367c0072ac2bf1e234e703dc46b47aaa4fe9d3b01737b1a15752b1"><img title="This PR adds 304 statements missing coverage to the whole project. The file did not seem to exist on the base branch." src="https://img.shields.io/badge/304-%28%2B304%29-red.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-4b0bf2efa3367c0072ac2bf1e234e703dc46b47aaa4fe9d3b01737b1a15752b1"><img title="The coverage rate of the whole project is 85.66% (1817/2121). The file did not seem to exist on the base branch." src="https://img.shields.io/badge/85%25-%281.8k/2.1k%29-orange.svg"></a></td><td align="center"><a href="https://github.com/nautobot/nautobot-app-netbox-importer/pull/207/files#diff-4b0bf2efa3367c0072ac2bf1e234e703dc46b47aaa4fe9d3b01737b1a15752b1"><img title="In this PR, 82 new statements are added to the whole project, 39 of which are covered (47.56%)." src="https://img.shields.io/badge/47%25-%2839/82%29-red.svg"></a></td><td>&nbsp;</td>
</tr>
</tfoot>
</table>

<sub>

This report was generated by [python-coverage-comment-action](https://github.com/py-cov-action/python-coverage-comment-action)

</sub>
</details>



<!-- This comment was produced by python-coverage-comment-action -->
author_association: NONE