Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/deactivate dialog confirm button #865

Merged
merged 9 commits into from
Mar 26, 2025

Conversation

axely123
Copy link
Collaborator

@axely123 axely123 commented Mar 25, 2025

Description

When a message is confirmed, the confirm button in arbeidsflate should deactivate. This PR makes the confirm endpoint also send a patch request to dialogporten to remove the guiAction and apiAction for confirm if they exist. Sending a patch request to remove the guiAction for confirm in dialogporten, removes the confirm button in arbeidsflate.

The PR removes the guiAction instead of deactivating it because dialogPorten currently does not support explicitly setting a guiAction as deactivated.

Related Issue(s)

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • New Features
    • When a correspondence is confirmed, dialog updates are automatically triggered.
    • Dialog retrieval and management have been enhanced, ensuring outdated actions are removed and valid data is maintained.
    • A new method allows for the construction of patch requests for dialog operations.
    • Improved error handling ensures that any issues during dialog processing are clearly reported to streamline the user experience.
    • New method added to confirm correspondence dialogs based on their ID.
    • A builder class has been introduced to simplify the construction of correspondence entities for testing purposes.

@axely123 axely123 added the kind/enhancement Improving existing feature label Mar 25, 2025
Copy link
Contributor

coderabbitai bot commented Mar 25, 2025

📝 Walkthrough

Walkthrough

This pull request introduces several enhancements to the correspondence status update functionality. A new method, PatchCorrespondenceDialog, is added to the UpdateCorrespondenceStatusHandler, which updates the correspondence dialog based on its status. The IDialogportenService interface gains a new method, PatchCorrespondenceDialogToConfirmed, along with its implementation in related service classes. Additionally, a new class, DialogPatchRequestBuilder, is introduced to facilitate the construction of patch requests for dialog operations.

Changes

File(s) Change Summary
src/Altinn.Correspondence.Application/.../UpdateCorrespondenceStatusHandler.cs Introduces a new method PatchCorrespondenceDialog in the Process method to update the correspondence dialog based on status.
src/Altinn.Correspondence.Core/.../IDialogportenService.cs Adds a new method Task PatchCorrespondenceDialogToConfirmed(Guid correspondenceId) to the interface.
src/Altinn.Correspondence.Integrations/Dialogporten/.../DialogportenDevService.cs, src/Altinn.Correspondence.Integrations/Dialogporten/.../DialogportenService.cs Implements PatchCorrespondenceDialogToConfirmed in both classes, with DialogportenService also adding a GetDialog method for dialog retrieval.
src/Altinn.Correspondence.Integrations/Dialogporten/Factories/.../PatchDialogRequestBuilder.cs Introduces the DialogPatchRequestBuilder class with methods for building patch requests, including operations to remove GUI and API actions.
src/Altinn.Correspondence.Application/.../UpdateCorrespondenceStatusHelper.cs Removes IDialogportenService dependency and adds a new method PatchCorrespondenceDialog to enqueue a job for confirming correspondence status.
Test/Altinn.Correspondence.Tests/.../DialogportenTests.cs Adds a new test method ConfirmCorrespondence_PatchesDialogToConfirmed, refactors existing tests to use a builder pattern, and simplifies enum references.
Test/Altinn.Correspondence.Tests/.../CorrespondenceEntityBuilder.cs Introduces CorrespondenceEntityBuilder class to facilitate the construction of CorrespondenceEntity objects with fluent interface methods.

Possibly related PRs

Suggested reviewers

  • Ceredron

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4778ac and cae933a.

📒 Files selected for processing (2)
  • Test/Altinn.Correspondence.Tests/Factories/CorrespondenceEntityBuilder.cs (1 hunks)
  • Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
Test/Altinn.Correspondence.Tests/Factories/CorrespondenceEntityBuilder.cs (1)

1-51: Well-implemented builder pattern for test data.

This builder class provides a clean and fluent interface for constructing CorrespondenceEntity objects in tests. The implementation includes:

  • A constructor with sensible default values
  • A Build() method to return the constructed entity
  • Fluent methods for adding statuses and external references that return this for chaining

Using this pattern will significantly improve readability and reduce duplication in test code. The implementation is solid with no apparent issues.

Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs (4)

7-9: Good addition of namespace imports.

Adding these imports simplifies the code by allowing direct use of types without full qualification.


145-146: Simplified enum references improve readability.

Changing from fully qualified enum references to direct references makes the code more concise and readable. This is a good practice when using a consistent set of enums across multiple files.

Also applies to: 153-154, 199-200, 212-214


222-227: Excellent refactoring using the new builder pattern.

Replacing the inline entity creation with the builder pattern improves readability and maintainability. This change demonstrates the value of the new CorrespondenceEntityBuilder class.


269-318: Well-structured test for the new dialog confirmation functionality.

This test thoroughly verifies that when a correspondence status is updated to Confirmed, the dialog is correctly patched in Dialogporten. The test:

  1. Sets up a correspondence with the necessary history (Ready, Published, Fetched)
  2. Properly mocks all dependencies
  3. Executes the handler with appropriate parameters
  4. Verifies both the return value and that the background job was enqueued

The test follows the established pattern in the codebase and provides good coverage for this new feature.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@axely123 axely123 linked an issue Mar 25, 2025 that may be closed by this pull request
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/PatchDialogRequestMapper.cs (2)

3-16: Consider using strongly-typed models instead of anonymous objects.

The mapper creates JSON patch operations using anonymous objects. While this works for simple cases, using strongly-typed models would provide better type safety, IDE support, and maintainability.

+public class JsonPatchOperation
+{
+    public string op { get; set; } = string.Empty;
+    public string path { get; set; } = string.Empty;
+}

internal class PatchDialogRequestMapper
{
-    internal static List<object> CreateRemoveGuiActionPatchRequest(int guiActionToRemoveIndex)
+    internal static List<JsonPatchOperation> CreateRemoveGuiActionPatchRequest(int guiActionToRemoveIndex)
    {
-        return new List<object>
+        return new List<JsonPatchOperation>
        {
-            new 
-            {
-                op = "remove",
-                path = $"/guiActions/{guiActionToRemoveIndex}"
-            }
+            new JsonPatchOperation
+            {
+                op = "remove",
+                path = $"/guiActions/{guiActionToRemoveIndex}"
+            }
        };
    }
}

5-15: Add XML documentation to explain the method's purpose.

Adding XML documentation would improve code maintainability by clearly explaining the purpose of the method and its parameters.

+/// <summary>
+/// Creates a JSON Patch request to remove a GUI action from a dialog.
+/// </summary>
+/// <param name="guiActionToRemoveIndex">The index of the GUI action to remove.</param>
+/// <returns>A list containing a JSON Patch operation to remove the specified GUI action.</returns>
internal static List<object> CreateRemoveGuiActionPatchRequest(int guiActionToRemoveIndex)
{
    return new List<object>
    {
        new 
        {
            op = "remove",
            path = $"/guiActions/{guiActionToRemoveIndex}"
        }
    };
}
src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (1)

54-57: Consider adding specific logging for the dialog patch operation.

While the code correctly calls the dialog service when the correspondence status is confirmed, adding specific logging before and after this operation would improve debugging capabilities.

if (request.Status == CorrespondenceStatus.Confirmed)
{
+    logger.LogInformation("Attempting to patch correspondence dialog to confirmed for correspondence {CorrespondenceId}", correspondence.Id);
    await dialogportenService.PatchCorrespondenceDialogToConfirmed(correspondence.Id);
+    logger.LogInformation("Successfully patched correspondence dialog to confirmed for correspondence {CorrespondenceId}", correspondence.Id);
}
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)

41-178: Consider refactoring to reduce code duplication.

Several methods in this class share similar patterns for retrieving correspondence by ID, finding dialog ID in external references, and error handling for API calls. Consider extracting these common patterns into helper methods to improve maintainability.

For example, you could create helper methods like:

private async Task<(CorrespondenceEntity correspondence, string dialogId)> GetCorrespondenceAndDialogId(Guid correspondenceId, CancellationToken cancellationToken)
{
    var correspondence = await _correspondenceRepository.GetCorrespondenceById(correspondenceId, true, true, cancellationToken);
    if (correspondence is null)
    {
        logger.LogError("Correspondence with id {correspondenceId} not found", correspondenceId);
        throw new ArgumentException($"Correspondence with id {correspondenceId} not found", nameof(correspondenceId));
    }
    
    var dialogId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
    if (dialogId is null)
    {
        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
    }
    
    return (correspondence, dialogId);
}

private async Task<T?> SendRequestAndHandleResponse<T>(Func<CancellationToken, Task<HttpResponseMessage>> requestFunc, CancellationToken cancellationToken, string errorContext = "")
{
    var response = await requestFunc(cancellationToken);
    if (!response.IsSuccessStatusCode)
    {
        throw new Exception($"Response from Dialogporten {errorContext} was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
    }
    
    if (typeof(T) == typeof(Unit))
    {
        return default;
    }
    
    var result = await response.Content.ReadFromJsonAsync<T>(cancellationToken);
    if (result is null)
    {
        throw new Exception($"Failed to deserialize the response {errorContext}.");
    }
    
    return result;
}

This would help simplify your methods and make them more maintainable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06ec734 and c4a846b.

📒 Files selected for processing (5)
  • src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (2 hunks)
  • src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (1 hunks)
  • src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenDevService.cs (1 hunks)
  • src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3 hunks)
  • src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/PatchDialogRequestMapper.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)
Learnt from: Ceredron
PR: Altinn/altinn-correspondence#368
File: src/Altinn.Correspondence.API/Program.cs:97-97
Timestamp: 2025-03-20T07:58:04.606Z
Learning: In the Altinn.Correspondence.Integrations project, services such as AltinnAuthorizationService and AltinnAccessManagementService intentionally retrieve IHostEnvironment from dependency injection.
🧬 Code Definitions (3)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenDevService.cs (2)
src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (6)
  • Task (7-7)
  • Task (8-8)
  • Task (9-9)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (7)
  • Task (16-39)
  • Task (41-69)
  • Task (71-98)
  • Task (100-126)
  • Task (127-148)
  • Task (150-160)
  • Task (162-178)
src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (1)
src/Altinn.Correspondence.Application/Helpers/InitializeCorrespondenceHelper.cs (1)
  • CorrespondenceStatus (399-413)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (6)
src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (6)
  • Task (7-7)
  • Task (8-8)
  • Task (9-9)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenDevService.cs (6)
  • Task (8-11)
  • Task (13-16)
  • Task (18-21)
  • Task (23-26)
  • Task (28-31)
  • Task (33-36)
src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs (1)
  • Task (34-141)
src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesHandler.cs (3)
  • Task (45-170)
  • Task (172-195)
  • Task (196-312)
src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/CreateDialogRequestMapper.cs (2)
  • Content (35-82)
  • CreateDialogRequest (10-33)
src/Altinn.Correspondence.Integrations/Dialogporten/Models/CreateDialogRequest.cs (2)
  • Content (116-136)
  • CreateDialogRequest (5-66)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenDevService.cs (1)

13-16: Implementation aligns with development service pattern.

The implementation of PatchCorrespondenceDialogToConfirmed follows the established pattern for other methods in this development service class, using a minimal implementation with Task.CompletedTask. This is appropriate for a development environment where external services might not be available.

src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (1)

8-8: Method signature consistent with interface pattern.

The addition of the PatchCorrespondenceDialogToConfirmed method to the interface follows the existing pattern of asynchronous methods returning Task and uses appropriate naming conventions.

src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (2)

17-17: Dependency injection correctly implemented.

The addition of IDialogportenService as a constructor parameter follows proper dependency injection patterns.


54-57:

❓ Verification inconclusive

Verify error handling for dialog patching.

The transaction will handle retries for failures, but please verify that any exceptions from PatchCorrespondenceDialogToConfirmed are properly caught and handled in the production implementation.


🏁 Script executed:

#!/bin/bash
# Check the implementation of DialogportenService.PatchCorrespondenceDialogToConfirmed to verify exception handling

rg -A 5 -B 3 "public async Task PatchCorrespondenceDialogToConfirmed" --type csharp --glob "src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs"

Length of output: 1269


Review: Confirm Exception Handling in Dialog Patching

  • In src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (lines 54–57), the call to PatchCorrespondenceDialogToConfirmed isn’t wrapped in a try-catch block.
  • The implementation in src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (as seen in the snippet) doesn’t include explicit error handling—it relies on the surrounding transaction to manage retries.
  • Action: Please verify that in production, any exceptions thrown by PatchCorrespondenceDialogToConfirmed propagate in a way that the global transaction or error-handling infrastructure catches them. Confirm that this design correctly triggers retries and logs errors as needed. If not, consider adding explicit exception handling at this boundary.
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)

7-7: LGTM: Relevant import for Dialog models.

The added import is necessary for the new methods to work with CreateDialogRequest objects.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (2)

41-69: 🛠️ Refactor suggestion

Fix resource management and add null check for GuiActions.

The implementation of PatchCorrespondenceDialogToConfirmed looks good overall, but has several issues:

  1. The CancellationTokenSource is not being properly disposed
  2. There's no check for null GuiActions before calling FindIndex
  3. There's a potential typo in the error message on line 54 (compare with line 140 which has "on on")
public async Task PatchCorrespondenceDialogToConfirmed(Guid correspondenceId)
{
-    var cancellationTokenSource = new CancellationTokenSource();
-    var cancellationToken = cancellationTokenSource.Token;
+    using var cancellationTokenSource = new CancellationTokenSource();
+    var cancellationToken = cancellationTokenSource.Token;
    var correspondence = await _correspondenceRepository.GetCorrespondenceById(correspondenceId, true, true, cancellationToken);
    if (correspondence is null)
    {
        logger.LogError("Correspondence with id {correspondenceId} not found", correspondenceId);
        throw new ArgumentException($"Correspondence with id {correspondenceId} not found", nameof(correspondenceId));
    }
    var dialogId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
    if (dialogId is null)
    {
        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
    }
    var dialog = await GetDialog(dialogId);
+    if (dialog.GuiActions == null)
+    {
+        logger.LogInformation("No GuiActions found on dialog {dialogId} for correspondence {correspondenceId}", dialogId, correspondenceId);
+        return;
+    }
    var guiActionIndexToDelete = dialog.GuiActions.FindIndex(dialog => dialog.Url == $"{generalSettings.Value.CorrespondenceBaseUrl.TrimEnd('/')}/correspondence/api/v1/correspondence/{correspondence.Id}/confirm");
    if (guiActionIndexToDelete == -1)
    {
        logger.LogInformation("No confirm guiAction found on dialog {dialogId} for correspondence {correspondenceId} when attempting to remove confirm guiAction", dialogId, correspondenceId);
        return;
    }
    var patchDialogRequest = PatchDialogRequestMapper.CreateRemoveGuiActionPatchRequest(guiActionIndexToDelete);
    var response = await _httpClient.PatchAsJsonAsync($"dialogporten/api/v1/serviceowner/dialogs/{dialogId}", patchDialogRequest, cancellationToken);
    if (!response.IsSuccessStatusCode)
    {
        throw new Exception($"Response from Dialogporten was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
    }
}

162-178: 🛠️ Refactor suggestion

Add parameter validation and proper disposal of resources.

The method looks good overall but has the following issues:

  1. No validation for the dialogId parameter
  2. CancellationTokenSource is not being properly disposed
  3. The error message could include the dialogId for easier debugging
public async Task<CreateDialogRequest> GetDialog(string dialogId)
{
-    var cancellationTokenSource = new CancellationTokenSource();
-    var cancellationToken = cancellationTokenSource.Token;
+    if (string.IsNullOrEmpty(dialogId))
+    {
+        throw new ArgumentException("DialogId cannot be null or empty", nameof(dialogId));
+    }
+
+    using var cancellationTokenSource = new CancellationTokenSource();
+    var cancellationToken = cancellationTokenSource.Token;

    var response = await _httpClient.GetAsync($"dialogporten/api/v1/serviceowner/dialogs/{dialogId}", cancellationToken);
    if (!response.IsSuccessStatusCode)
    {
-        throw new Exception($"Response from Dialogporten was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
+        throw new Exception($"Response from Dialogporten for dialog {dialogId} was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
    }
    var dialogRequest = await response.Content.ReadFromJsonAsync<CreateDialogRequest>(cancellationToken);
    if (dialogRequest is null)
    {
-        throw new Exception("Failed to deserialize the dialog request from the response.");
+        throw new Exception($"Failed to deserialize the dialog request for dialog {dialogId} from the response.");
    }
    return dialogRequest;
}
🧹 Nitpick comments (3)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3)

89-89: Fix duplicate typo in error messages.

There's a duplicate "on on" typo in the error messages on lines 89 and 140.

// Line 89
-        throw new ArgumentException($"No dialog found on on correspondence with id {correspondenceId}");
+        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");

// Line 140
-        throw new ArgumentException($"No dialog found on on correspondence with id {correspondenceId}");
+        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");

Also applies to: 140-140


41-69: Consider adding unit tests for the PatchCorrespondenceDialogToConfirmed method.

This method has multiple conditions and edge cases that should be tested, including:

  • What happens when correspondence doesn't exist
  • What happens when dialog ID is missing
  • What happens when GuiActions is null
  • What happens when the confirm action is not found
  • What happens when the API call succeeds or fails

41-69: Consider refactoring duplicate code across methods.

The first part of many methods in this class follows the same pattern: create cancellation token, get correspondence by ID, check if null, get dialog ID, check if null. Consider extracting this common logic to a private helper method.

+ private async Task<(string dialogId, Guid correspondenceId)> GetDialogIdFromCorrespondence(Guid correspondenceId, CancellationToken cancellationToken)
+ {
+     var correspondence = await _correspondenceRepository.GetCorrespondenceById(correspondenceId, true, true, cancellationToken);
+     if (correspondence is null)
+     {
+         logger.LogError("Correspondence with id {correspondenceId} not found", correspondenceId);
+         throw new ArgumentException($"Correspondence with id {correspondenceId} not found", nameof(correspondenceId));
+     }
+     
+     var dialogId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
+     if (dialogId is null)
+     {
+         throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
+     }
+     
+     return (dialogId, correspondence.Id);
+ }

Then use this method in all the places with similar code:

public async Task PatchCorrespondenceDialogToConfirmed(Guid correspondenceId)
{
    using var cancellationTokenSource = new CancellationTokenSource();
    var cancellationToken = cancellationTokenSource.Token;
-   var correspondence = await _correspondenceRepository.GetCorrespondenceById(correspondenceId, true, true, cancellationToken);
-   if (correspondence is null)
-   {
-       logger.LogError("Correspondence with id {correspondenceId} not found", correspondenceId);
-       throw new ArgumentException($"Correspondence with id {correspondenceId} not found", nameof(correspondenceId));
-   }
-   var dialogId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
-   if (dialogId is null)
-   {
-       throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
-   }
+   var (dialogId, id) = await GetDialogIdFromCorrespondence(correspondenceId, cancellationToken);
    var dialog = await GetDialog(dialogId);
    // ...remaining implementation...
}

Also applies to: 129-148

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4a846b and f65470d.

📒 Files selected for processing (1)
  • src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)
Learnt from: Ceredron
PR: Altinn/altinn-correspondence#368
File: src/Altinn.Correspondence.API/Program.cs:97-97
Timestamp: 2025-03-20T07:58:04.606Z
Learning: In the Altinn.Correspondence.Integrations project, services such as AltinnAuthorizationService and AltinnAccessManagementService intentionally retrieve IHostEnvironment from dependency injection.
🧬 Code Definitions (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (4)
src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (6)
  • Task (7-7)
  • Task (8-8)
  • Task (9-9)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs (1)
  • Task (34-141)
src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/CreateDialogRequestMapper.cs (2)
  • Content (35-82)
  • CreateDialogRequest (10-33)
src/Altinn.Correspondence.Integrations/Dialogporten/Models/CreateDialogRequest.cs (2)
  • Content (116-136)
  • CreateDialogRequest (5-66)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)

7-7: Good import addition for using the CreateDialogRequest model.

Adding the import for Altinn.Correspondence.Integrations.Dialogporten.Models is appropriate since you're now using the CreateDialogRequest model in the new GetDialog method.

…actored the patchRequestMapper to a builder to improve its usability
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Altinn.Correspondence.Integrations/Dialogporten/Factories/PatchDialogRequestBuilder.cs (1)

1-36: Improve naming conventions and code structure.

The implementation of DialogPatchRequestBuilder looks good functionally but has several issues related to naming conventions and organization:

  1. The file is in a "Factories" folder but the namespace doesn't reflect this
  2. The field name _PatchDialogRequest doesn't follow C# naming conventions for private fields (should be camelCase)
  3. Missing XML documentation for public methods
  4. No validation for input indices
-namespace Altinn.Correspondence.Integrations.Dialogporten
+namespace Altinn.Correspondence.Integrations.Dialogporten.Factories
{
    internal class DialogPatchRequestBuilder
    {
-        private List<object> _PatchDialogRequest = new List<object>();
+        private readonly List<object> _patchDialogRequest = new List<object>();

+        /// <summary>
+        /// Builds and returns the patch request operations list.
+        /// </summary>
+        /// <returns>A list of patch operations.</returns>
        public List<object> Build()
        {
-            return _PatchDialogRequest;
+            return _patchDialogRequest;
        }

+        /// <summary>
+        /// Adds an operation to remove a GUI action at the specified index.
+        /// </summary>
+        /// <param name="guiActionToRemoveIndex">The zero-based index of the GUI action to remove.</param>
+        /// <returns>The current instance for method chaining.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is negative.</exception>
        internal DialogPatchRequestBuilder WithRemoveGuiActionOperation(int guiActionToRemoveIndex)
        {
+            if (guiActionToRemoveIndex < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(guiActionToRemoveIndex), "Index cannot be negative.");
+            }
-            _PatchDialogRequest.Add(
+            _patchDialogRequest.Add(
                new 
                {
                    op = "remove",
                    path = $"/guiActions/{guiActionToRemoveIndex}"
                }
            );
            return this;
        }

+        /// <summary>
+        /// Adds an operation to remove an API action at the specified index.
+        /// </summary>
+        /// <param name="apiActionToRemoveIndex">The zero-based index of the API action to remove.</param>
+        /// <returns>The current instance for method chaining.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is negative.</exception>
        internal DialogPatchRequestBuilder WithRemoveApiActionOperation(int apiActionToRemoveIndex)
        {
+            if (apiActionToRemoveIndex < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(apiActionToRemoveIndex), "Index cannot be negative.");
+            }
-            _PatchDialogRequest.Add(
+            _patchDialogRequest.Add(
                new 
                {
                    op = "remove",
                    path = $"/apiActions/{apiActionToRemoveIndex}"
                }
            );
            return this;
        }
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f65470d and e5bfeaa.

📒 Files selected for processing (2)
  • src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3 hunks)
  • src/Altinn.Correspondence.Integrations/Dialogporten/Factories/PatchDialogRequestBuilder.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)
Learnt from: Ceredron
PR: Altinn/altinn-correspondence#368
File: src/Altinn.Correspondence.API/Program.cs:97-97
Timestamp: 2025-03-20T07:58:04.606Z
Learning: In the Altinn.Correspondence.Integrations project, services such as AltinnAuthorizationService and AltinnAccessManagementService intentionally retrieve IHostEnvironment from dependency injection.
🧬 Code Definitions (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3)
src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (6)
  • Task (7-7)
  • Task (8-8)
  • Task (9-9)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
src/Altinn.Correspondence.Integrations/Dialogporten/Factories/PatchDialogRequestBuilder.cs (3)
  • DialogPatchRequestBuilder (3-35)
  • DialogPatchRequestBuilder (12-22)
  • DialogPatchRequestBuilder (24-34)
src/Altinn.Correspondence.Integrations/Dialogporten/Models/CreateDialogRequest.cs (2)
  • Content (116-136)
  • CreateDialogRequest (5-66)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)

167-183: Add parameter validation and proper resource disposal.

The GetDialog method implementation has several improvements that need to be made:

  1. No validation for the dialogId parameter
  2. CancellationTokenSource is not being properly disposed
  3. The error message could include the dialogId for easier debugging

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHelper.cs (1)

93-105: LGTM! Nice addition of the PatchCorrespondenceDialog method.

The new method correctly implements the functionality to update the dialog in Dialogporten when a correspondence is confirmed, following the established pattern in the codebase. It properly uses Hangfire to queue the job as a fire-and-forget operation.

A couple of minor suggestions:

  1. The return; statement on line 104 is redundant and could be removed
  2. Consider adding a code comment explaining the relationship between this method and ReportActivityToDialogporten since both handle the Confirmed status but serve different purposes
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5bfeaa and ecced0a.

📒 Files selected for processing (2)
  • src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs (1 hunks)
  • src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHelper.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs
🧰 Additional context used
🧠 Learnings (1)
src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHelper.cs (2)
Learnt from: CelineTrammi
PR: Altinn/altinn-correspondence#387
File: src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs:72-75
Timestamp: 2025-03-20T07:58:04.607Z
Learning: The `IsConfirmationNeeded` property in the `CorrespondenceEntity` class is no longer a required property.
Learnt from: CelineTrammi
PR: Altinn/altinn-correspondence#483
File: src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusHandler.cs:75-75
Timestamp: 2025-03-20T07:58:04.607Z
Learning: In `LegacyUpdateCorrespondenceStatusHandler.cs`, the call to `_updateCorrespondenceStatusHelper.ReportActivityToDialogporten(...)` is intended to be a fire-and-forget event and does not need to be awaited.
🧬 Code Definitions (1)
src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHelper.cs (1)
src/Altinn.Correspondence.Application/Helpers/InitializeCorrespondenceHelper.cs (1)
  • CorrespondenceStatus (399-413)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)

41-80: 🛠️ Refactor suggestion

Ensure proper resource disposal in PatchCorrespondenceDialogToConfirmed method.

The implementation looks good overall, with proper null checks, error handling, and empty request validation. However, the CancellationTokenSource is not being properly disposed, which could lead to resource leaks.

public async Task PatchCorrespondenceDialogToConfirmed(Guid correspondenceId)
{
-    var cancellationTokenSource = new CancellationTokenSource();
-    var cancellationToken = cancellationTokenSource.Token;
+    using var cancellationTokenSource = new CancellationTokenSource();
+    var cancellationToken = cancellationTokenSource.Token;
    var correspondence = await _correspondenceRepository.GetCorrespondenceById(correspondenceId, true, true, cancellationToken);
    // Rest of the method remains unchanged
🧹 Nitpick comments (4)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (4)

51-55: Add null check for ExternalReferences collection.

The code assumes ExternalReferences is not null when calling FirstOrDefault. While this may be true in most cases, adding a null check would make the code more robust.

-    var dialogId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
+    var dialogId = correspondence.ExternalReferences?.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenDialogId)?.ReferenceValue;
     if (dialogId is null)
     {
         throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
     }

99-101: Fix typo in error message.

There's a typo in the error message: "No dialog found on on correspondence" (duplicated word "on").

    if (dialogId is null)
    {
-        throw new ArgumentException($"No dialog found on on correspondence with id {correspondenceId}");
+        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
    }

151-152: Fix typo in error message.

There's a typo in the error message: "No dialog found on on correspondence" (duplicated word "on").

    if (dialogId is null)
    {
-        throw new ArgumentException($"No dialog found on on correspondence with id {correspondenceId}");
+        throw new ArgumentException($"No dialog found on correspondence with id {correspondenceId}");
    }

82-109: Apply consistent resource management across all methods.

Multiple methods in this class are using CancellationTokenSource without proper disposal. This pattern should be fixed consistently across all methods in the class.

All methods that create a CancellationTokenSource should use the using keyword to ensure proper disposal, for example:

public async Task CreateInformationActivity(Guid correspondenceId, DialogportenActorType actorType, DialogportenTextType textType, params string[] tokens)
{
    logger.LogInformation("CreateInformationActivity {actorType}: {textType} for correspondence {correspondenceId}",
        Enum.GetName(typeof(DialogportenActorType), actorType),
        Enum.GetName(typeof(DialogportenTextType), textType),
        correspondenceId
    );
-    var cancellationTokenSource = new CancellationTokenSource();
-    var cancellationToken = cancellationTokenSource.Token;
+    using var cancellationTokenSource = new CancellationTokenSource();
+    var cancellationToken = cancellationTokenSource.Token;
    // Rest of the method remains unchanged

Apply similar changes to all methods in this class.

Also applies to: 117-137, 138-159, 161-171

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecced0a and e4778ac.

📒 Files selected for processing (1)
  • src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (1)
Learnt from: Ceredron
PR: Altinn/altinn-correspondence#368
File: src/Altinn.Correspondence.API/Program.cs:97-97
Timestamp: 2025-03-20T07:58:04.606Z
Learning: In the Altinn.Correspondence.Integrations project, services such as AltinnAuthorizationService and AltinnAccessManagementService intentionally retrieve IHostEnvironment from dependency injection.
🧬 Code Definitions (1)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (3)
src/Altinn.Correspondence.Core/Services/IDialogportenService.cs (6)
  • Task (7-7)
  • Task (8-8)
  • Task (9-9)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
src/Altinn.Correspondence.Integrations/Dialogporten/Factories/PatchDialogRequestBuilder.cs (3)
  • DialogPatchRequestBuilder (3-35)
  • DialogPatchRequestBuilder (12-22)
  • DialogPatchRequestBuilder (24-34)
src/Altinn.Correspondence.Integrations/Dialogporten/Models/CreateDialogRequest.cs (2)
  • Content (116-136)
  • CreateDialogRequest (5-66)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (2)
src/Altinn.Correspondence.Integrations/Dialogporten/DialogportenService.cs (2)

69-74: Looks good!

The code correctly checks if the patch request is empty and returns early if there are no actions to remove, which avoids unnecessary API calls. This is a good optimization.


173-189: Add parameter validation and proper disposal of resources.

The method looks good overall but has the following issues:

  1. No validation for the dialogId parameter
  2. CancellationTokenSource is not being properly disposed
  3. The error message could include the dialogId for easier debugging
public async Task<CreateDialogRequest> GetDialog(string dialogId)
{
-    var cancellationTokenSource = new CancellationTokenSource();
-    var cancellationToken = cancellationTokenSource.Token;
+    if (string.IsNullOrEmpty(dialogId))
+    {
+        throw new ArgumentException("DialogId cannot be null or empty", nameof(dialogId));
+    }
+
+    using var cancellationTokenSource = new CancellationTokenSource();
+    var cancellationToken = cancellationTokenSource.Token;

    var response = await _httpClient.GetAsync($"dialogporten/api/v1/serviceowner/dialogs/{dialogId}", cancellationToken);
    if (!response.IsSuccessStatusCode)
    {
-        throw new Exception($"Response from Dialogporten was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
+        throw new Exception($"Response from Dialogporten for dialog {dialogId} was not successful: {response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
    }
    var dialogRequest = await response.Content.ReadFromJsonAsync<CreateDialogRequest>(cancellationToken);
    if (dialogRequest is null)
    {
-        throw new Exception("Failed to deserialize the dialog request from the response.");
+        throw new Exception($"Failed to deserialize the dialog request for dialog {dialogId} from the response.");
    }
    return dialogRequest;
}

…cation on UpdateCorrespondenceStatusHandler confirm request
Copy link
Collaborator

@mSunberg mSunberg left a comment

Choose a reason for hiding this comment

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

Good work, you can merge after trying it from test env.

@axely123 axely123 merged commit c1179ea into main Mar 26, 2025
9 checks passed
@axely123 axely123 deleted the enhancement/deactivate-dialog-confirm-button branch March 26, 2025 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement Improving existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Etter bekreft aksjon skal bekreftknappen deaktiveres
2 participants