From 468c311a8d0801d8cf681d274347af1734dfc3f9 Mon Sep 17 00:00:00 2001 From: firedragon Date: Thu, 16 Jul 2026 20:15:22 -0300 Subject: [PATCH 01/17] chore: change Publisher --- .../Conviso.Platform.VisualStudio.csproj | 5 ++++- .../Conviso.Platform.VisualStudioPackage.vsct | 2 +- .../source.extension.vsixmanifest | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj index 566de14..b2ccbcf 100644 --- a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj +++ b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj @@ -27,6 +27,9 @@ + @@ -40,4 +43,4 @@ - + \ No newline at end of file diff --git a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudioPackage.vsct b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudioPackage.vsct index 4dcc7a0..b768838 100644 --- a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudioPackage.vsct +++ b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudioPackage.vsct @@ -6,7 +6,7 @@ - + diff --git a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest index c5db6ff..69140aa 100644 --- a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest +++ b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Conviso Platform Conviso Platform integration for Visual Studio. From c668ba7ecead13088766d868e786e1656f917979 Mon Sep 17 00:00:00 2001 From: firedragon Date: Mon, 20 Jul 2026 21:32:44 -0300 Subject: [PATCH 02/17] refactor: UI and settings improvements and chat fixes --- .../Commands/OpenToolWindowCommandBase.cs | 17 +++- .../Configuration/ConvisoOptions.cs | 2 +- .../Conviso.Platform.VisualStudio.csproj | 12 ++- .../ConvisoPlatformPackage.cs | 1 + .../Infrastructure/AsyncDelegateCommand.cs | 6 ++ .../Services/Broker/BrokerClient.cs | 28 +++++-- .../Services/Platform/GraphQlDocuments.cs | 2 - .../Services/Platform/PlatformApiClient.cs | 3 +- .../Services/Platform/PlatformFacade.cs | 10 +-- .../ToolWindows/ChatToolWindowControl.xaml | 54 ++++++++----- .../ToolWindows/ChatToolWindowControl.xaml.cs | 12 +++ .../PipelineBreaksToolWindowControl.xaml | 26 +++--- .../PipelineBreaksToolWindowControl.xaml.cs | 9 ++- .../RequirementsToolWindowControl.xaml | 21 ++--- .../RequirementsToolWindowControl.xaml.cs | 9 ++- .../SettingsToolWindowControl.xaml | 77 +++++------------- .../ToolWindows/ToolWindowStyles.xaml | 79 +++++++++++++++++++ .../VulnerabilitiesToolWindowControl.xaml | 33 ++++---- .../VulnerabilitiesToolWindowControl.xaml.cs | 9 ++- .../ViewModels/ChatToolWindowViewModel.cs | 19 ++++- .../PipelineBreaksToolWindowViewModel.cs | 27 +++++-- .../RequirementsToolWindowViewModel.cs | 28 +++++-- .../ViewModels/SettingsToolWindowViewModel.cs | 23 +++--- .../VulnerabilitiesToolWindowViewModel.cs | 26 ++++-- .../VulnerabilityDetailsViewModel.cs | 4 +- 25 files changed, 359 insertions(+), 178 deletions(-) create mode 100644 src/Conviso.Platform.VisualStudio/ToolWindows/ToolWindowStyles.xaml diff --git a/src/Conviso.Platform.VisualStudio/Commands/OpenToolWindowCommandBase.cs b/src/Conviso.Platform.VisualStudio/Commands/OpenToolWindowCommandBase.cs index b532c4b..d111268 100644 --- a/src/Conviso.Platform.VisualStudio/Commands/OpenToolWindowCommandBase.cs +++ b/src/Conviso.Platform.VisualStudio/Commands/OpenToolWindowCommandBase.cs @@ -32,10 +32,21 @@ private void Execute(object? sender, EventArgs e) { ThreadHelper.JoinableTaskFactory.RunAsync(async delegate { - ToolWindowPane window = await package.ShowToolWindowAsync(typeof(TToolWindow), 0, true, package.DisposalToken); - if (window == null) + try { - throw new InvalidOperationException($"Unable to open tool window {typeof(TToolWindow).Name}."); + ToolWindowPane window = await package.ShowToolWindowAsync(typeof(TToolWindow), 0, true, package.DisposalToken); + if (window == null) + { + throw new InvalidOperationException($"Unable to open tool window {typeof(TToolWindow).Name}."); + } + } + catch (OperationCanceledException) when (package.DisposalToken.IsCancellationRequested) + { + // Visual Studio is shutting down. + } + catch (Exception error) + { + Infrastructure.DiagnosticsLogger.LogError($"Unable to open {typeof(TToolWindow).Name}: {error}"); } }); } diff --git a/src/Conviso.Platform.VisualStudio/Configuration/ConvisoOptions.cs b/src/Conviso.Platform.VisualStudio/Configuration/ConvisoOptions.cs index 210b00d..e5eb568 100644 --- a/src/Conviso.Platform.VisualStudio/Configuration/ConvisoOptions.cs +++ b/src/Conviso.Platform.VisualStudio/Configuration/ConvisoOptions.cs @@ -3,7 +3,7 @@ namespace Conviso.Platform.VisualStudio.Configuration internal static class ConvisoOptions { public const string DefaultApiBaseUrl = "https://api.convisoappsec.com"; - public const string DefaultBrokerEndpoint = "wss://conviso.docker.localhost/ws"; + public const string DefaultBrokerEndpoint = "wss://securecode.convisoappsec.com/ws"; public const string CollectionPath = "ConvisoPlatform"; public const string ApiBaseUrlKey = "Conviso.ApiBaseUrl"; diff --git a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj index b2ccbcf..7361918 100644 --- a/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj +++ b/src/Conviso.Platform.VisualStudio/Conviso.Platform.VisualStudio.csproj @@ -24,7 +24,15 @@ - + + + + + + + + - \ No newline at end of file + diff --git a/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs b/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs index 6ab3dd7..3fb0342 100644 --- a/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs +++ b/src/Conviso.Platform.VisualStudio/ConvisoPlatformPackage.cs @@ -17,6 +17,7 @@ namespace Conviso.Platform.VisualStudio [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("Conviso Platform", "Conviso Platform integration for Visual Studio", "0.1.4")] [ProvideMenuResource("Menus.ctmenu", 1)] + [ProvideBindingPath] [ProvideToolWindow(typeof(ChatToolWindow))] [ProvideToolWindow(typeof(VulnerabilitiesToolWindow))] [ProvideToolWindow(typeof(RequirementsToolWindow))] diff --git a/src/Conviso.Platform.VisualStudio/Infrastructure/AsyncDelegateCommand.cs b/src/Conviso.Platform.VisualStudio/Infrastructure/AsyncDelegateCommand.cs index dcc2988..9656c2c 100644 --- a/src/Conviso.Platform.VisualStudio/Infrastructure/AsyncDelegateCommand.cs +++ b/src/Conviso.Platform.VisualStudio/Infrastructure/AsyncDelegateCommand.cs @@ -36,6 +36,12 @@ public async void Execute(object? parameter) RaiseCanExecuteChanged(); await execute(); } + catch (Exception error) + { + // ICommand.Execute is async void by contract. Never let an exception + // escape to WPF's dispatcher, because Visual Studio hosts us in-proc. + DiagnosticsLogger.LogError("Command failed: " + error); + } finally { isExecuting = false; diff --git a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs index 414a26a..7a92225 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Broker/BrokerClient.cs @@ -264,18 +264,34 @@ public async Task DisconnectAsync(CancellationToken cancellationToken) receiveLoopCancellation?.Dispose(); receiveLoopCancellation = null; - if (socket == null) + ClientWebSocket? activeSocket = socket; + socket = null; + if (activeSocket == null) { return; } - if (socket.State == WebSocketState.Open) + try { - await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "disconnect", cancellationToken); + if (activeSocket.State == WebSocketState.Open) + { + await activeSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "disconnect", cancellationToken); + } + } + catch (WebSocketException) when (activeSocket.State == WebSocketState.Aborted || + activeSocket.State == WebSocketState.Closed) + { + // An aborted socket is already disconnected; cleanup is enough. + } + catch (InvalidOperationException) when (activeSocket.State == WebSocketState.Aborted || + activeSocket.State == WebSocketState.Closed) + { + // CloseAsync cannot be used after the receive loop aborts the socket. + } + finally + { + activeSocket.Dispose(); } - - socket.Dispose(); - socket = null; } private async Task ReceiveLoopAsync(ClientWebSocket activeSocket, CancellationToken cancellationToken) diff --git a/src/Conviso.Platform.VisualStudio/Services/Platform/GraphQlDocuments.cs b/src/Conviso.Platform.VisualStudio/Services/Platform/GraphQlDocuments.cs index 1b95a4c..1d2b3cd 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Platform/GraphQlDocuments.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Platform/GraphQlDocuments.cs @@ -32,13 +32,11 @@ query Issues( $pagination: PaginationInput! $filters: IssuesFiltersInput $companyId: ID! - $sortOptions: [IssueSortOptionInput!] ) { issues( pagination: $pagination filters: $filters companyId: $companyId - sortOptions: $sortOptions ) { collection { id diff --git a/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformApiClient.cs b/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformApiClient.cs index 90294fc..6a99fd4 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformApiClient.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformApiClient.cs @@ -21,9 +21,8 @@ public PlatformApiClient(ISettingsService settingsService) public async Task QueryAsync(string graphqlDocument, string variablesJson, CancellationToken cancellationToken) { - string apiBaseUrl = settingsService.GetString(ConvisoOptions.ApiBaseUrlKey, ConvisoOptions.DefaultApiBaseUrl); string apiToken = settingsService.GetSecret(ConvisoOptions.ApiTokenKey, string.Empty); - string endpoint = $"{apiBaseUrl.TrimEnd('/')}/graphql"; + string endpoint = ConvisoOptions.DefaultApiBaseUrl + "/graphql"; using var request = new HttpRequestMessage(HttpMethod.Post, endpoint); request.Headers.Add("Accept", "application/json"); diff --git a/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformFacade.cs b/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformFacade.cs index 7281421..75fc3db 100644 --- a/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformFacade.cs +++ b/src/Conviso.Platform.VisualStudio/Services/Platform/PlatformFacade.cs @@ -351,18 +351,10 @@ public async Task> GetVulnerabilitiesAsync(s pagination = new { page = 1, - limit = 20, + perPage = 20, }, filters, companyId = effectiveCompanyId, - sortOptions = new[] - { - new - { - field = "updatedAt", - direction = "DESC", - }, - }, }); string json = await apiClient.QueryAsync(GraphQlDocuments.IssuesList, variables, cancellationToken); diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/ChatToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/ChatToolWindowControl.xaml index d8a26a5..d658543 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/ChatToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/ChatToolWindowControl.xaml @@ -1,8 +1,12 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:vs="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" + Background="{DynamicResource {x:Static vs:VsBrushes.ToolWindowBackgroundKey}}" + Foreground="{DynamicResource {x:Static vs:VsBrushes.ToolWindowTextKey}}"> + + @@ -10,43 +14,49 @@ - - - + + + + + - @@ -57,7 +71,16 @@ - + @@ -106,23 +129,43 @@ - + Margin="0,0,0,12"> + + + + + + + diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml index 1cffb46..cc6574a 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml @@ -19,7 +19,7 @@ - - @@ -92,7 +92,9 @@ - @@ -119,7 +121,9 @@ - diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/SettingsToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/SettingsToolWindowControl.xaml index 473ee44..d9daa43 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/SettingsToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/SettingsToolWindowControl.xaml @@ -14,12 +14,12 @@ - - diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml index 01b5cc0..6ed5a28 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml @@ -21,7 +21,9 @@ - @@ -51,10 +53,14 @@ - - From 6b7615fc85b845db31a1c0114a92988be5c03aef Mon Sep 17 00:00:00 2001 From: firedragon Date: Thu, 23 Jul 2026 21:24:55 -0300 Subject: [PATCH 14/17] feat: add local filtering to data lists - add reactive text filters to vulnerabilities and pipeline breaks - add independent filters to projects, requirements, and activities - filter locally by each list's primary column without API requests - preserve loaded collections and restore all items when filters are cleared - clear hidden selections and stale details safely - add contextual filter placeholders - update manual validation scenarios --- docs/manual-validation.md | 38 +++++--- .../PipelineBreaksToolWindowControl.xaml | 16 ++-- .../RequirementsToolWindowControl.xaml | 48 +++++++++-- .../VulnerabilitiesToolWindowControl.xaml | 16 ++-- .../PipelineBreaksToolWindowViewModel.cs | 33 +++++++ .../RequirementsToolWindowViewModel.cs | 86 +++++++++++++++++++ .../VulnerabilitiesToolWindowViewModel.cs | 33 +++++++ 7 files changed, 237 insertions(+), 33 deletions(-) diff --git a/docs/manual-validation.md b/docs/manual-validation.md index 55a5a72..cb882ed 100644 --- a/docs/manual-validation.md +++ b/docs/manual-validation.md @@ -103,17 +103,21 @@ Expected result: 1. Open `Tools > Conviso Vulnerabilities`. 2. Confirm that data uses the company configured in `Conviso Settings > Scope`. -3. Change the asset filter. -4. Click `Refresh`. -5. Select a vulnerability row. -6. Review the details panel. -7. Click `Generate Fix`. -8. Enter a new status and click `Update Status`. +3. Type part of a vulnerability title in `Filter by title...`. +4. Confirm results change on each keystroke without a loading request. +5. Clear the text filter and confirm all loaded vulnerabilities return. +6. Change the asset filter. +7. Click `Refresh`. +8. Select a vulnerability row. +9. Review the details panel. +10. Click `Generate Fix`. +11. Enter a new status and click `Update Status`. Expected result: - list loads without UI crashes - the title is the first column and horizontal scrolling is available when columns overflow +- title filtering is case-insensitive, supports no-match results, and does not discard loaded items - filters do not break the view - selecting a row loads details - generate-fix returns a meaningful result or a clear backend error @@ -123,18 +127,22 @@ Expected result: 1. Open `Tools > Conviso Requirements`. 2. Click `Refresh`. -3. Select a project. -4. Verify project details load. -5. Select a requirement. -6. Verify requirement details and activities load. -7. Select an activity. -8. Change project status and click `Update Project`. -9. Change activity status and click `Update Activity`. +3. Filter projects by label, then clear the filter. +4. Select a project. +5. Verify project details load. +6. Filter requirements by label, then clear the filter. +7. Select a requirement. +8. Verify requirement details and activities load. +9. Filter activities by title, then clear the filter. +10. Select an activity. +11. Change project status and click `Update Project`. +12. Change activity status and click `Update Activity`. Expected result: - projects load - project, requirement, and activity lists show horizontal scrollbars when their columns overflow +- each text filter updates only its local loaded list and preserves the full list when cleared - selecting a project populates requirements - selecting a requirement populates activities - detail sidebars update correctly @@ -144,12 +152,14 @@ Expected result: 1. Open `Tools > Conviso Pipeline Breaks`. 2. Click `Refresh`. -3. Select an item. +3. Filter the list by part of an asset name, then clear the filter. +4. Select an item. Expected result: - list loads - the list shows a horizontal scrollbar when its columns overflow +- asset filtering updates immediately without an additional API request - selecting a row loads execution details - empty-state or setup-state messages remain readable diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml index cc6574a..f72c801 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/PipelineBreaksToolWindowControl.xaml @@ -20,13 +20,19 @@ - + + + + + + + @@ -36,8 +42,8 @@ - - + + diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/RequirementsToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/RequirementsToolWindowControl.xaml index 2721668..c63bc7c 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/RequirementsToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/RequirementsToolWindowControl.xaml @@ -24,18 +24,28 @@ - + + + + + + + + - + + @@ -46,32 +56,52 @@ - + + + + + + + + - + + - + + + + + + + + - + + diff --git a/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml b/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml index 6ed5a28..37e2792 100644 --- a/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml +++ b/src/Conviso.Platform.VisualStudio/ToolWindows/VulnerabilitiesToolWindowControl.xaml @@ -26,13 +26,19 @@ - + + + + + + + @@ -42,8 +48,8 @@ - - + + diff --git a/src/Conviso.Platform.VisualStudio/ViewModels/PipelineBreaksToolWindowViewModel.cs b/src/Conviso.Platform.VisualStudio/ViewModels/PipelineBreaksToolWindowViewModel.cs index 2c9449f..722a59b 100644 --- a/src/Conviso.Platform.VisualStudio/ViewModels/PipelineBreaksToolWindowViewModel.cs +++ b/src/Conviso.Platform.VisualStudio/ViewModels/PipelineBreaksToolWindowViewModel.cs @@ -1,6 +1,8 @@ using System.Collections.ObjectModel; +using System.ComponentModel; using System.Threading; using System.Threading.Tasks; +using System.Windows.Data; using Conviso.Platform.VisualStudio.Infrastructure; using Conviso.Platform.VisualStudio.Models; using Conviso.Platform.VisualStudio.Services.Platform; @@ -19,22 +21,43 @@ internal sealed class PipelineBreaksToolWindowViewModel : ObservableObject private string detailAssetName = string.Empty; private string detailReasonText = string.Empty; private string detailCommandStatus = "Select a pipeline break"; + private string itemFilter = string.Empty; public PipelineBreaksToolWindowViewModel(IPlatformFacade platformFacade) { this.platformFacade = platformFacade; Items = new ObservableCollection(); + ItemsView = CollectionViewSource.GetDefaultView(Items); + ItemsView.Filter = FilterItem; RefreshCommand = new AsyncDelegateCommand(RefreshAsync); } public ObservableCollection Items { get; } + public ICollectionView ItemsView { get; } + public string Status { get => status; set => SetProperty(ref status, value); } + public string ItemFilter + { + get => itemFilter; + set + { + if (SetProperty(ref itemFilter, value)) + { + ItemsView.Refresh(); + if (SelectedItem != null && !ItemsView.Contains(SelectedItem)) + { + SelectedItem = null; + } + } + } + } + public PipelineBreakSummary? SelectedItem { get => selectedItem; @@ -123,5 +146,15 @@ private void ResetDetails() DetailReasonText = string.Empty; DetailCommandStatus = "Select a pipeline break"; } + + private bool FilterItem(object item) + { + if (item is not PipelineBreakSummary pipelineBreak || string.IsNullOrWhiteSpace(ItemFilter)) + { + return true; + } + + return (pipelineBreak.AssetName ?? string.Empty).IndexOf(ItemFilter.Trim(), System.StringComparison.OrdinalIgnoreCase) >= 0; + } } } diff --git a/src/Conviso.Platform.VisualStudio/ViewModels/RequirementsToolWindowViewModel.cs b/src/Conviso.Platform.VisualStudio/ViewModels/RequirementsToolWindowViewModel.cs index 8393c73..507db93 100644 --- a/src/Conviso.Platform.VisualStudio/ViewModels/RequirementsToolWindowViewModel.cs +++ b/src/Conviso.Platform.VisualStudio/ViewModels/RequirementsToolWindowViewModel.cs @@ -1,6 +1,8 @@ using System.Collections.ObjectModel; +using System.ComponentModel; using System.Threading; using System.Threading.Tasks; +using System.Windows.Data; using Conviso.Platform.VisualStudio.Infrastructure; using Conviso.Platform.VisualStudio.Models; using Conviso.Platform.VisualStudio.Services.Platform; @@ -35,6 +37,9 @@ internal sealed class RequirementsToolWindowViewModel : ObservableObject private string activityUpdatedAt = string.Empty; private string newActivityStatus = string.Empty; private string activityCommandStatus = "Select an activity"; + private string projectFilter = string.Empty; + private string requirementFilter = string.Empty; + private string activityFilter = string.Empty; public RequirementsToolWindowViewModel(IPlatformFacade platformFacade) { @@ -42,6 +47,12 @@ public RequirementsToolWindowViewModel(IPlatformFacade platformFacade) Projects = new ObservableCollection(); Requirements = new ObservableCollection(); Activities = new ObservableCollection(); + ProjectsView = CollectionViewSource.GetDefaultView(Projects); + RequirementsView = CollectionViewSource.GetDefaultView(Requirements); + ActivitiesView = CollectionViewSource.GetDefaultView(Activities); + ProjectsView.Filter = FilterProject; + RequirementsView.Filter = FilterRequirement; + ActivitiesView.Filter = FilterActivity; RefreshCommand = new AsyncDelegateCommand(RefreshAsync); UpdateProjectStatusCommand = new AsyncDelegateCommand(UpdateProjectStatusAsync, () => selectedProject != null && !string.IsNullOrWhiteSpace(NewProjectStatus)); UpdateActivityStatusCommand = new AsyncDelegateCommand(UpdateActivityStatusAsync, () => selectedActivity != null && !string.IsNullOrWhiteSpace(NewActivityStatus)); @@ -53,6 +64,12 @@ public RequirementsToolWindowViewModel(IPlatformFacade platformFacade) public ObservableCollection Activities { get; } + public ICollectionView ProjectsView { get; } + + public ICollectionView RequirementsView { get; } + + public ICollectionView ActivitiesView { get; } + public string Status { get => status; @@ -177,6 +194,54 @@ private async Task LoadProjectSafelyAsync(ProjectSummary? project) } } + public string ProjectFilter + { + get => projectFilter; + set + { + if (SetProperty(ref projectFilter, value)) + { + ProjectsView.Refresh(); + if (SelectedProject != null && !ProjectsView.Contains(SelectedProject)) + { + SelectedProject = null; + } + } + } + } + + public string RequirementFilter + { + get => requirementFilter; + set + { + if (SetProperty(ref requirementFilter, value)) + { + RequirementsView.Refresh(); + if (SelectedRequirement != null && !RequirementsView.Contains(SelectedRequirement)) + { + SelectedRequirement = null; + } + } + } + } + + public string ActivityFilter + { + get => activityFilter; + set + { + if (SetProperty(ref activityFilter, value)) + { + ActivitiesView.Refresh(); + if (SelectedActivity != null && !ActivitiesView.Contains(SelectedActivity)) + { + SelectedActivity = null; + } + } + } + } + private async Task LoadRequirementSafelyAsync(RequirementSummary? requirement) { try @@ -373,5 +438,26 @@ private void ResetActivityDetails() NewActivityStatus = string.Empty; ActivityCommandStatus = "Select an activity"; } + + private bool FilterProject(object item) + { + return item is not ProjectSummary project || + string.IsNullOrWhiteSpace(ProjectFilter) || + (project.Label ?? string.Empty).IndexOf(ProjectFilter.Trim(), System.StringComparison.OrdinalIgnoreCase) >= 0; + } + + private bool FilterRequirement(object item) + { + return item is not RequirementSummary requirement || + string.IsNullOrWhiteSpace(RequirementFilter) || + (requirement.Label ?? string.Empty).IndexOf(RequirementFilter.Trim(), System.StringComparison.OrdinalIgnoreCase) >= 0; + } + + private bool FilterActivity(object item) + { + return item is not ProjectActivitySummary activity || + string.IsNullOrWhiteSpace(ActivityFilter) || + (activity.Title ?? string.Empty).IndexOf(ActivityFilter.Trim(), System.StringComparison.OrdinalIgnoreCase) >= 0; + } } } diff --git a/src/Conviso.Platform.VisualStudio/ViewModels/VulnerabilitiesToolWindowViewModel.cs b/src/Conviso.Platform.VisualStudio/ViewModels/VulnerabilitiesToolWindowViewModel.cs index 690a635..50003ab 100644 --- a/src/Conviso.Platform.VisualStudio/ViewModels/VulnerabilitiesToolWindowViewModel.cs +++ b/src/Conviso.Platform.VisualStudio/ViewModels/VulnerabilitiesToolWindowViewModel.cs @@ -1,7 +1,9 @@ using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows.Data; using Conviso.Platform.VisualStudio.Configuration; using Conviso.Platform.VisualStudio.Infrastructure; using Conviso.Platform.VisualStudio.Models; @@ -19,6 +21,7 @@ internal sealed class VulnerabilitiesToolWindowViewModel : ObservableObject private AssetOption? selectedAsset; private VulnerabilitySummary? selectedItem; private string status = "Ready"; + private string itemFilter = string.Empty; public VulnerabilitiesToolWindowViewModel( IPlatformFacade platformFacade, @@ -29,6 +32,8 @@ public VulnerabilitiesToolWindowViewModel( this.settingsService = settingsService; this.brokerClient = brokerClient; Items = new ObservableCollection(); + ItemsView = CollectionViewSource.GetDefaultView(Items); + ItemsView.Filter = FilterItem; Assets = new ObservableCollection(); Details = new VulnerabilityDetailsViewModel(platformFacade, settingsService, brokerClient); RefreshCommand = new AsyncDelegateCommand(RefreshAsync); @@ -36,6 +41,8 @@ public VulnerabilitiesToolWindowViewModel( public ObservableCollection Items { get; } + public ICollectionView ItemsView { get; } + public ObservableCollection Assets { get; } public VulnerabilityDetailsViewModel Details { get; } @@ -101,6 +108,22 @@ public Task InitializeAsync() return LoadFilterOptionsAsync(); } + public string ItemFilter + { + get => itemFilter; + set + { + if (SetProperty(ref itemFilter, value)) + { + ItemsView.Refresh(); + if (SelectedItem != null && !ItemsView.Contains(SelectedItem)) + { + SelectedItem = null; + } + } + } + } + private async Task LoadDetailsSafelyAsync(VulnerabilitySummary? item) { try @@ -146,5 +169,15 @@ private async Task LoadAssetsForSelectedCompanyAsync() } } + private bool FilterItem(object item) + { + if (item is not VulnerabilitySummary vulnerability || string.IsNullOrWhiteSpace(ItemFilter)) + { + return true; + } + + return (vulnerability.Title ?? string.Empty).IndexOf(ItemFilter.Trim(), System.StringComparison.OrdinalIgnoreCase) >= 0; + } + } } From 81913f48dcc26d1a013187d3a4e714f47d70635c Mon Sep 17 00:00:00 2001 From: firedragon Date: Fri, 24 Jul 2026 09:42:03 -0300 Subject: [PATCH 15/17] chore: increase version --- src/Conviso.Platform.VisualStudio/AssemblyInfo.cs | 4 ++-- .../source.extension.vsixmanifest | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs index 0f26709..77aa5aa 100644 --- a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs +++ b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs @@ -5,6 +5,6 @@ [assembly: AssemblyDescription("Conviso Platform Visual Studio extension")] [assembly: AssemblyCompany("Conviso")] [assembly: AssemblyProduct("Conviso Platform Visual Studio")] -[assembly: AssemblyVersion("0.1.4.0")] -[assembly: AssemblyFileVersion("0.1.4.0")] +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] [assembly: ComVisible(false)] diff --git a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest index ed95fdf..809b8d9 100644 --- a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest +++ b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Conviso Platform Conviso Platform integration for Visual Studio. Resources\pluginIcon.png From d3635e84aec9ffb538d7a01cd99a0befb96d525b Mon Sep 17 00:00:00 2001 From: firedragon Date: Fri, 24 Jul 2026 11:16:22 -0300 Subject: [PATCH 16/17] docs: update README.md --- README.md | 4 ++-- src/Conviso.Platform.VisualStudio/AssemblyInfo.cs | 4 ++-- .../source.extension.vsixmanifest | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 46c837a..184ecca 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ If you do not have an account, sign up or start a free trial: 1. In Visual Studio, open **Tools > Conviso Settings**. 2. On the **Platform API** tab, enter your **API Token**. -3. Select **Save Settings**. -4. Open the **Scope** tab and choose the company to use. Accessible companies load automatically, and the selection is saved immediately. +3. Open the **Scope** tab and choose the company to use. +4. Select **Save Settings**. You can use **Test API** and **Test Chat** to validate the credentials. The API token is stored using Windows user-level data protection. The Platform API and AI service endpoints are configured by the extension and do not require manual URLs or separate broker credentials. diff --git a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs index 77aa5aa..21bed72 100644 --- a/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs +++ b/src/Conviso.Platform.VisualStudio/AssemblyInfo.cs @@ -5,6 +5,6 @@ [assembly: AssemblyDescription("Conviso Platform Visual Studio extension")] [assembly: AssemblyCompany("Conviso")] [assembly: AssemblyProduct("Conviso Platform Visual Studio")] -[assembly: AssemblyVersion("0.2.0.0")] -[assembly: AssemblyFileVersion("0.2.0.0")] +[assembly: AssemblyVersion("0.2.1.0")] +[assembly: AssemblyFileVersion("0.2.1.0")] [assembly: ComVisible(false)] diff --git a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest index 809b8d9..ccee98f 100644 --- a/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest +++ b/src/Conviso.Platform.VisualStudio/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + Conviso Platform Conviso Platform integration for Visual Studio. Resources\pluginIcon.png From a802b2eae92f0ce2587479af20a0c10a46afd845 Mon Sep 17 00:00:00 2001 From: firedragon Date: Fri, 24 Jul 2026 11:18:19 -0300 Subject: [PATCH 17/17] docs: update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 184ecca..982b315 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If you do not have an account, sign up or start a free trial: ### Install the extension -1. Go to **Extensions > Manager Extensions > Online**. +1. Go to **Extensions > Manage Extensions > Online**. 2. Search for **Conviso Platform**. 3. Install. @@ -51,7 +51,7 @@ If you do not have an account, sign up or start a free trial: 3. Open the **Scope** tab and choose the company to use. 4. Select **Save Settings**. -You can use **Test API** and **Test Chat** to validate the credentials. The API token is stored using Windows user-level data protection. The Platform API and AI service endpoints are configured by the extension and do not require manual URLs or separate broker credentials. +You can use **Test API** and **Test Chat** to validate the credentials. The API token is stored using Windows user-level data protection. ---