Skip to content

feat(OpcDa): add Browse feature#520

Merged
ArgoZhang merged 9 commits intomasterfrom
feat-opc
Aug 7, 2025
Merged

feat(OpcDa): add Browse feature#520
ArgoZhang merged 9 commits intomasterfrom
feat-opc

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Aug 7, 2025

Link issues

fixes #519

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add browsing capabilities to the BootstrapBlazor OPC DA extension, enabling clients to enumerate channels, devices, and tags on an OPC DA server.

New Features:

  • Expose Browse and BrowseNext methods on IOpcDaServer and implement them in both mock and real server classes
  • Introduce OpcBrowseElement, OpcBrowseFilters, OpcBrowseFilterType, and OpcBrowsePosition types to represent browsing data

Enhancements:

  • Add extension methods to convert between OpcBrowseFilters and underlying Da.BrowseFilters
  • Remove unused values cache field and clean up obsolete usings in the server implementation

Tests:

  • Add a unit test to verify the Browse method returns expected elements and position

@bb-auto bb-auto Bot added the enhancement New feature or request label Aug 7, 2025
@bb-auto bb-auto Bot added this to the v9.2.0 milestone Aug 7, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 7, 2025

Reviewer's Guide

This PR adds a full Browse/BrowseNext feature to the OPC DA extension by extending the server interface, introducing domain models and filter mappings, wiring up both real and mock implementations, and covering the workflow with a new unit test.

Sequence diagram for OPC DA Browse workflow

sequenceDiagram
    participant Client
    participant IOpcDaServer
    participant OpcDaServer
    participant OPC_DA_Server
    Client->>IOpcDaServer: Browse(name, filters, out position)
    IOpcDaServer->>OpcDaServer: Browse(name, filters, out position)
    OpcDaServer->>OPC_DA_Server: server.Browse(ItemIdentifier, filters, out pos)
    OPC_DA_Server-->>OpcDaServer: BrowseElement[]
    OpcDaServer-->>IOpcDaServer: OpcBrowseElement[]
    IOpcDaServer-->>Client: OpcBrowseElement[]
    Note over Client,IOpcDaServer: position is returned for BrowseNext
Loading

Sequence diagram for OPC DA BrowseNext workflow

sequenceDiagram
    participant Client
    participant IOpcDaServer
    participant OpcDaServer
    participant OPC_DA_Server
    Client->>IOpcDaServer: BrowseNext(position)
    IOpcDaServer->>OpcDaServer: BrowseNext(position)
    OpcDaServer->>OPC_DA_Server: server.BrowseNext(ref pos)
    OPC_DA_Server-->>OpcDaServer: BrowseElement[]
    OpcDaServer-->>IOpcDaServer: OpcBrowseElement[]
    IOpcDaServer-->>Client: OpcBrowseElement[]
Loading

Class diagram for new OPC DA Browse feature

classDiagram
    class IOpcDaServer {
        +HashSet<OpcWriteItem> Write(HashSet<OpcWriteItem> items)
        +OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
        +OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
    }
    class OpcDaServer {
        -Opc.Da.Server? _server
        +OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
        +OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
    }
    class MockOpcDaServer {
        +OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
        +OpcBrowseElement[] BrowseNext(OpcBrowsePosition position)
    }
    class OpcBrowseElement {
        +string Name
        +string ItemName
        +bool IsItem
        +bool HasChildren
        +OpcBrowseElement()
        +OpcBrowseElement(BrowseElement element)
    }
    class OpcBrowseFilters {
        +int MaxElementsReturned
        +string? ElementNameFilter
        +bool ReturnAllProperties
        +bool ReturnPropertyValues
        +OpcBrowseFilterType BrowseFilter
    }
    class OpcBrowsePosition {
        +OpcBrowsePosition(Opc.Da.BrowsePosition? position)
        -Opc.Da.BrowsePosition? Position
    }
    class OpcBrowseFilterType {
        <<enum>>
        All
        Branch
        Item
    }
    IOpcDaServer <|.. OpcDaServer
    IOpcDaServer <|.. MockOpcDaServer
    OpcDaServer o-- OpcBrowseElement
    OpcDaServer o-- OpcBrowseFilters
    OpcDaServer o-- OpcBrowsePosition
    MockOpcDaServer o-- OpcBrowseElement
    MockOpcDaServer o-- OpcBrowseFilters
    MockOpcDaServer o-- OpcBrowsePosition
    OpcBrowseFilters --> OpcBrowseFilterType
    OpcBrowseElement --> BrowseElement
    OpcBrowsePosition --> Opc.Da.BrowsePosition
Loading

File-Level Changes

Change Details Files
Extend IOpcDaServer interface
  • Added Browse and BrowseNext method signatures
IOpcDaServer.cs
Implement Browse in real OPC DA server
  • Invoke underlying OPC DA Browse/BrowseNext
  • Map filters via extension methods
  • Wrap results into OpcBrowseElement
OpcDaServer.cs
Extensions/Extensions.cs
Implement Browse in mock server
  • Return hardcoded hierarchy for Browse calls
  • Provide stubbed BrowseNext
Mock/MockOpcDaServer.cs
Introduce browse domain models and filter conversion
  • Add OpcBrowseElement, OpcBrowseFilters, OpcBrowseFilterType, OpcBrowsePosition classes
  • Provide extension to map OpcBrowseFilters to native BrowseFilters
OpcBrowseElement.cs
OpcBrowseFilters.cs
OpcBrowseFilterType.cs
OpcBrowsePosition.cs
Extensions/Extensions.cs
Add unit test for Browse feature
  • Create Browser_Ok test with Assert on elements and position
UnitTestOpcDa/UnitTest1.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#519 Implement a 'Browse' feature in the OpcDa module to allow browsing of OPC Server items (nodes/tags).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ArgoZhang - I've reviewed your changes - here's some feedback:

  • Use C# array initializer syntax (e.g., new OpcBrowseElement[] { ... }) instead of JavaScript-style [ ... ] in your Browse and BrowseNext implementations.
  • Your unit test asserts 3 elements for "Channel1" but the mock returns only one; either update the mock data or adjust the assertion to match the expected results.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Use C# array initializer syntax (e.g., new OpcBrowseElement[] { ... }) instead of JavaScript-style [ ... ] in your Browse and BrowseNext implementations.
- Your unit test asserts 3 elements for "Channel1" but the mock returns only one; either update the mock data or adjust the assertion to match the expected results.

## Individual Comments

### Comment 1
<location> `src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs:74` </location>
<code_context>
+    /// <param name="filters"></param>
+    /// <param name="position"></param>
+    /// <returns></returns>
+    public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
+    {
+        position = null;
</code_context>

<issue_to_address>
Consider refactoring the Browse method to use a static tree structure and a data-driven lookup instead of multiple hard-coded if statements.

Here is a quick data‐driven refactor that keeps the same behavior but collapses all of those hard-coded branches into a single tree lookup.

1. Define a simple node model:
```csharp
class BrowseNode
{
    public string Name { get; init; }
    public string ItemName { get; init; }
    public bool IsItem { get; init; }
    public BrowseNode[] Children { get; init; } = Array.Empty<BrowseNode>();
}
```

2. Build a static tree once:
```csharp
static readonly BrowseNode[] _tree = new[]
{
    new BrowseNode
    {
        Name = "Channel1",
        ItemName = "Channel1",
        Children = new[]
        {
            new BrowseNode
            {
                Name = "Device1",
                ItemName = "Channel1.Device1",
                Children = new[]
                {
                    new BrowseNode { Name = "Tag1", ItemName = "Channel1.Device1.Tag1", IsItem = true },
                    new BrowseNode { Name = "Tag2", ItemName = "Channel1.Device1.Tag2", IsItem = true }
                }
            }
        }
    },
    new BrowseNode { Name = "Channel2", ItemName = "Channel2" }
};
```

3. Rewrite `Browse` to just walk that tree:
```csharp
public OpcBrowseElement[] Browse(string name, OpcBrowseFilters filters, out OpcBrowsePosition? position)
{
    position = null;
    // find the node (or use root set if name is empty)
    IEnumerable<BrowseNode> list = string.IsNullOrEmpty(name)
        ? _tree
        : _tree.SelectMany(n => Flatten(n))
               .FirstOrDefault(n => n.ItemName == name)?
               .Children
        ?? Array.Empty<BrowseNode>();

    return list.Select(n => new OpcBrowseElement
    {
        Name = n.Name,
        ItemName = n.ItemName,
        IsItem = n.IsItem,
        HasChildren = n.Children.Length > 0
    }).ToArray();
}

// helper to flatten subtree including the root node
IEnumerable<BrowseNode> Flatten(BrowseNode node)
{
    yield return node;
    foreach (var c in node.Children)
        foreach (var x in Flatten(c))
            yield return x;
}
```

This:

- Eliminates all the `if(name == "...")` blocks
- Centralizes your structure in one place for easy extension
- Keeps the exact same output of channels, devices, and tags
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/extensions/BootstrapBlazor.OpcDa/Mock/MockOpcDaServer.cs
@ArgoZhang ArgoZhang merged commit 02bd40f into master Aug 7, 2025
@ArgoZhang ArgoZhang deleted the feat-opc branch August 7, 2025 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(OpcDa): add Browse feature

1 participant