From fe956b61b54f945c1a40b1b31cb7d45d68befe46 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:40:01 +0100 Subject: [PATCH 01/11] Removed collaborator, comments, and one click send (#3696) * Removed collaborator, comments, and one click send * Fix tests * Fixed tests --- All.sln | 3 + .../Api/GraphQL/Enums/ProjectVisibility.cs | 6 +- .../ProjectResourceExceptionalTests.cs | 2 +- .../GraphQL/Resources/ProjectResourceTests.cs | 4 +- DesktopUI2/DesktopUI2/Models/ConfigManager.cs | 2 + .../ViewModels/CollaboratorsViewModel.cs | 1 + .../DesktopUI2/ViewModels/HomeViewModel.cs | 1 + .../DesktopUI2/ViewModels/MainViewModel.cs | 10 +-- .../ViewModels/OneClickViewModel.cs | 1 + .../DesktopUI2/ViewModels/StreamViewModel.cs | 9 +- .../Views/Pages/CollaboratorsView.xaml.cs | 3 + .../DesktopUI2/Views/Pages/HomeView.xaml | 38 ++++---- .../Views/Pages/OneClickView.xaml.cs | 2 + .../Views/Pages/StreamEditView.xaml | 90 +++++++++---------- 14 files changed, 94 insertions(+), 78 deletions(-) diff --git a/All.sln b/All.sln index df383c2f22..d7a3d95627 100644 --- a/All.sln +++ b/All.sln @@ -427,6 +427,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConnectorCore", "ConnectorCore", "{DA9DFC36-C53F-4B19-8911-BF7605230BA7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorRhino8", "ConnectorRhino\ConnectorRhino8\ConnectorRhino8.csproj", "{D22A887D-976C-4DBF-AE5B-9039F169E61C}" + ProjectSection(ProjectDependencies) = postProject + {89996067-3233-410A-A6A1-39E2F11F0626} = {89996067-3233-410A-A6A1-39E2F11F0626} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterRhino8", "Objects\Converters\ConverterRhinoGh\ConverterRhino8\ConverterRhino8.csproj", "{89996067-3233-410A-A6A1-39E2F11F0626}" EndProject diff --git a/Core/Core/Api/GraphQL/Enums/ProjectVisibility.cs b/Core/Core/Api/GraphQL/Enums/ProjectVisibility.cs index 9a62fff999..c79c0a2656 100644 --- a/Core/Core/Api/GraphQL/Enums/ProjectVisibility.cs +++ b/Core/Core/Api/GraphQL/Enums/ProjectVisibility.cs @@ -1,8 +1,12 @@ -namespace Speckle.Core.Api.GraphQL.Enums; +using System; + +namespace Speckle.Core.Api.GraphQL.Enums; public enum ProjectVisibility { Private, + + [Obsolete("Use Unlisted instead")] Public, Unlisted } diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs index 47222b761f..4d0517b5c7 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs @@ -60,7 +60,7 @@ public void ProjectGet_NonExistentProject() [Test] public void ProjectUpdate_NonExistentProject() { - Assert.ThrowsAsync( + Assert.ThrowsAsync( async () => _ = await Sut.Update(new("NonExistentProject", "My new name")) ); } diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs index 66dcba0a1e..383dfda7a0 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs @@ -21,7 +21,7 @@ public async Task Setup() } [TestCase("Very private project", "My secret project", ProjectVisibility.Private)] - [TestCase("Very public project", null, ProjectVisibility.Public)] + [TestCase("Very unlisted project", null, ProjectVisibility.Unlisted)] public async Task ProjectCreate(string name, string desc, ProjectVisibility visibility) { ProjectCreateInput input = new(name, desc, visibility); @@ -50,7 +50,7 @@ public async Task ProjectUpdate() { const string NEW_NAME = "MY new name"; const string NEW_DESCRIPTION = "MY new desc"; - const ProjectVisibility NEW_VISIBILITY = ProjectVisibility.Public; + const ProjectVisibility NEW_VISIBILITY = ProjectVisibility.Unlisted; Project newProject = await Sut.Update(new(_testProject.id, NEW_NAME, NEW_DESCRIPTION, null, NEW_VISIBILITY)); diff --git a/DesktopUI2/DesktopUI2/Models/ConfigManager.cs b/DesktopUI2/DesktopUI2/Models/ConfigManager.cs index 115219640f..af8ac5c006 100644 --- a/DesktopUI2/DesktopUI2/Models/ConfigManager.cs +++ b/DesktopUI2/DesktopUI2/Models/ConfigManager.cs @@ -42,6 +42,8 @@ public static Config Load() public class Config { public bool DarkTheme { set; get; } + + [Obsolete("One click is no longer available", true)] public bool OneClickMode { set; get; } = true; public bool ShowImportExportAlert { set; get; } = true; public bool UseFe2 { set; get; } diff --git a/DesktopUI2/DesktopUI2/ViewModels/CollaboratorsViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/CollaboratorsViewModel.cs index 4b8c7b6f1f..cc86a13bb3 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/CollaboratorsViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/CollaboratorsViewModel.cs @@ -18,6 +18,7 @@ namespace DesktopUI2.ViewModels; +[Obsolete("Collaborators view is not available")] public class CollaboratorsViewModel : ReactiveObject, IRoutableViewModel { private StreamViewModel _stream; diff --git a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs index 8273df5309..b630d24224 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs @@ -858,6 +858,7 @@ public void RefreshCommand() Refresh(); } + [Obsolete("One click mode is no longer available")] private void OneClickModeCommand() { var config = ConfigManager.Load(); diff --git a/DesktopUI2/DesktopUI2/ViewModels/MainViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/MainViewModel.cs index a7dab4c346..e14a22d396 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/MainViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/MainViewModel.cs @@ -96,10 +96,10 @@ public void NavigateToDefaultScreen() { Router.Navigate.Execute(new LogInViewModel(this)); } - else if (config.OneClickMode) - { - Router.Navigate.Execute(new OneClickViewModel(this)); - } + // else if (config.OneClickMode) + // { + // Router.Navigate.Execute(new OneClickViewModel(this)); + // } else { Home.Refresh(); @@ -127,7 +127,7 @@ public static void GoHome() } var config = ConfigManager.Load(); - if (!config.OneClickMode) + // if (!config.OneClickMode) { RouterInstance.Navigate.Execute(Home); } diff --git a/DesktopUI2/DesktopUI2/ViewModels/OneClickViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/OneClickViewModel.cs index 11c05f7266..2330aa0b58 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/OneClickViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/OneClickViewModel.cs @@ -14,6 +14,7 @@ namespace DesktopUI2.ViewModels; +[Obsolete("One click is no longer available")] public class OneClickViewModel : ReactiveObject, IRoutableViewModel { public OneClickViewModel(IScreen screen) diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs index 4558fd4dcb..5888cbdf41 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs @@ -63,7 +63,7 @@ public StreamViewModel(StreamState streamState, IScreen hostScreen, ICommand rem HostScreen = hostScreen; RemoveSavedStreamCommand = removeSavedStreamCommand; - Collaborators = new CollaboratorsViewModel(HostScreen, this); + // Collaborators = new CollaboratorsViewModel(HostScreen, this); //use dependency injection to get bindings Bindings = Locator.Current.GetService(); @@ -229,9 +229,7 @@ public async Task GetStream() .ConfigureAwait(true); Stream.pendingCollaborators = streamPendingCollaborators.pendingCollaborators; } - - Collaborators.ReloadUsers(); - ; + // Collaborators.ReloadUsers(); StreamState.CachedStream = Stream; } @@ -588,7 +586,7 @@ public async Task DownloadImage360(string url) private ConnectorBindings Bindings; - private CollaboratorsViewModel Collaborators { get; set; } + // private CollaboratorsViewModel Collaborators { get; set; } public ICommand RemoveSavedStreamCommand { get; } @@ -1329,6 +1327,7 @@ public void ClearSearchCommand() SearchQuery = ""; } + [Obsolete("Collaborators view is no longer avaiable", true)] public void ShareCommand() { MainViewModel.RouterInstance.Navigate.Execute(new CollaboratorsViewModel(HostScreen, this)); diff --git a/DesktopUI2/DesktopUI2/Views/Pages/CollaboratorsView.xaml.cs b/DesktopUI2/DesktopUI2/Views/Pages/CollaboratorsView.xaml.cs index 2f334bd033..e4644d17c1 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/CollaboratorsView.xaml.cs +++ b/DesktopUI2/DesktopUI2/Views/Pages/CollaboratorsView.xaml.cs @@ -1,10 +1,13 @@ +using System; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using DesktopUI2.ViewModels; using ReactiveUI; +using Speckle.Core.Logging; namespace DesktopUI2.Views.Pages; +[Obsolete(message: "Collaborators view is not available ")] public class CollaboratorsView : ReactiveUserControl { public CollaboratorsView() diff --git a/DesktopUI2/DesktopUI2/Views/Pages/HomeView.xaml b/DesktopUI2/DesktopUI2/Views/Pages/HomeView.xaml index a1e86c34e3..fc05c9aa25 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/HomeView.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/HomeView.xaml @@ -453,25 +453,25 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/DesktopUI2/DesktopUI2/Views/Pages/OneClickView.xaml.cs b/DesktopUI2/DesktopUI2/Views/Pages/OneClickView.xaml.cs index ea62fe5e20..d7e5eb7995 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/OneClickView.xaml.cs +++ b/DesktopUI2/DesktopUI2/Views/Pages/OneClickView.xaml.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using DesktopUI2.ViewModels; @@ -5,6 +6,7 @@ namespace DesktopUI2.Views.Pages; +[Obsolete("One click is no longer available")] public class OneClickView : ReactiveUserControl { public OneClickView() diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml index 763637f6ed..56f24c2fd1 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml @@ -181,30 +181,30 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + @@ -226,27 +226,27 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From 5b052241fc44630f4107f1d266244ac4e1159b16 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:25:23 +0100 Subject: [PATCH 02/11] feat(dui2): Add creation of workspace project (#3699) * First pass * Fix build * Top level try catch incase of unexpected errors * default selection * empty ready message * fixed integration tests again --- Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs | 7 + Core/Core/Api/GraphQL/Inputs/UserInputs.cs | 3 + Core/Core/Api/GraphQL/Models/Workspace.cs | 23 +++ .../GraphQL/Resources/ActiveUserResource.cs | 93 +++++++++- .../Api/GraphQL/Resources/ProjectResource.cs | 48 +++++ .../Api/GraphQL/Resources/graphql.config.yml | 2 +- .../ModelResourceExceptionalTests.cs | 2 +- .../DesktopUI2/ViewModels/HomeViewModel.cs | 43 +++-- .../ViewModels/WorkspaceViewModel.cs | 22 +++ .../Windows/Dialogs/NewStreamDialog.xaml | 56 +++++- .../Windows/Dialogs/NewStreamDialog.xaml.cs | 172 ++++++++++++++++-- 11 files changed, 434 insertions(+), 37 deletions(-) create mode 100644 Core/Core/Api/GraphQL/Inputs/UserInputs.cs create mode 100644 Core/Core/Api/GraphQL/Models/Workspace.cs create mode 100644 DesktopUI2/DesktopUI2/ViewModels/WorkspaceViewModel.cs diff --git a/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs b/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs index 8f9ad4433b..1eeb2a7f7b 100644 --- a/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs +++ b/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs @@ -7,6 +7,13 @@ public sealed record ProjectCommentsFilter(bool? includeArchived, bool? loadedVe public sealed record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility); +public sealed record WorkspaceProjectCreateInput( + string? name, + string? description, + ProjectVisibility? visibility, + string workspaceId +); + public sealed record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId); public sealed record ProjectInviteUseInput(bool accept, string projectId, string token); diff --git a/Core/Core/Api/GraphQL/Inputs/UserInputs.cs b/Core/Core/Api/GraphQL/Inputs/UserInputs.cs new file mode 100644 index 0000000000..fbbaa43979 --- /dev/null +++ b/Core/Core/Api/GraphQL/Inputs/UserInputs.cs @@ -0,0 +1,3 @@ +namespace Speckle.Core.Api.GraphQL.Inputs; + +public sealed record UserWorkspacesFilter(string? search); diff --git a/Core/Core/Api/GraphQL/Models/Workspace.cs b/Core/Core/Api/GraphQL/Models/Workspace.cs new file mode 100644 index 0000000000..1d13f325d3 --- /dev/null +++ b/Core/Core/Api/GraphQL/Models/Workspace.cs @@ -0,0 +1,23 @@ +namespace Speckle.Core.Api.GraphQL.Models; + +public sealed class Workspace +{ + public string id { get; init; } + public string name { get; init; } + public string role { get; init; } + public string slug { get; init; } + public string? description { get; init; } + public WorkspacePermissionChecks permissions { get; init; } +} + +public sealed class WorkspacePermissionChecks +{ + public PermissionCheckResult canCreateProject { get; init; } +} + +public sealed class PermissionCheckResult +{ + public bool authorized { get; init; } + public string code { get; init; } + public string message { get; init; } +} diff --git a/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs b/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs index 6da8610a65..bd62029839 100644 --- a/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs +++ b/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs @@ -21,7 +21,6 @@ internal ActiveUserResource(ISpeckleGraphQLClient client) /// Gets the currently active user profile. /// /// - /// /// the requested user, or null if the user does not exist (i.e. was initialised with an unauthenticated account) /// public async Task Get(CancellationToken cancellationToken = default) @@ -51,6 +50,98 @@ query User { return response.activeUser; } + /// + /// + /// + /// Only supported on server versions >=2.23.17 + /// + /// + public async Task CanCreatePersonalProjects(CancellationToken cancellationToken = default) + { + //language=graphql + const string QUERY = """ + query CanCreatePersonalProject { + data:activeUser { + data:permissions { + data:canCreatePersonalProject { + authorized + code + message + } + } + } + } + """; + var request = new GraphQLRequest { Query = QUERY, }; + + var response = await _client + .ExecuteGraphQLRequest>>>( + request, + cancellationToken + ) + .ConfigureAwait(false); + + return response.data.data.data; + } + + /// Ret + /// This feature is only available on Workspace enabled servers (server versions >=2.23.17) e.g. app.speckle.systems + /// + /// + /// + public async Task> GetWorkspaces( + int limit = 25, + string? cursor = null, + UserWorkspacesFilter? filter = null, + CancellationToken cancellationToken = default + ) + { + //language=graphql + const string QUERY = """ + query ActiveUser($limit: Int!, $cursor: String, $filter: UserWorkspacesFilter) { + data:activeUser { + data:workspaces(limit: $limit, cursor: $cursor, filter: $filter) { + cursor + totalCount + items { + id + name + role + slug + description + permissions { + canCreateProject { + authorized + code + message + } + } + } + } + } + } + """; + var request = new GraphQLRequest + { + Query = QUERY, + Variables = new + { + limit, + cursor, + filter + } + }; + + var response = await _client + .ExecuteGraphQLRequest>>>( + request, + cancellationToken + ) + .ConfigureAwait(false); + + return response.data.data; + } + /// Max number of projects to fetch /// Optional cursor for pagination /// Optional filter diff --git a/Core/Core/Api/GraphQL/Resources/ProjectResource.cs b/Core/Core/Api/GraphQL/Resources/ProjectResource.cs index 34c505ec33..16079bdd03 100644 --- a/Core/Core/Api/GraphQL/Resources/ProjectResource.cs +++ b/Core/Core/Api/GraphQL/Resources/ProjectResource.cs @@ -188,6 +188,10 @@ query ProjectGetWithTeam($projectId: String!) { return response.project; } + /// + /// Creates a non-workspace project (aka Personal Project) + /// + /// /// /// /// @@ -220,6 +224,50 @@ mutation ProjectCreate($input: ProjectCreateInput) { return response.projectMutations.create; } + /// + /// Creates a workspace project () + /// + /// + /// This feature is only supported by Workspace Enabled Servers (e.g. app.speckle.systems). + /// A 's list can be checked if the user . + /// + /// + /// + /// + /// + public async Task CreateInWorkspace( + WorkspaceProjectCreateInput input, + CancellationToken cancellationToken = default + ) + { + //language=graphql + const string QUERY = """ + mutation WorkspaceProjectCreate($input: WorkspaceProjectCreateInput!) { + data:workspaceMutations { + data:projects { + data:create(input: $input) { + id + name + description + visibility + allowPublicComments + role + createdAt + updatedAt + sourceApps + } + } + } + } + """; + GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; + + var response = await _client + .ExecuteGraphQLRequest>>>(request, cancellationToken) + .ConfigureAwait(false); + return response.data.data.data; + } + /// /// /// diff --git a/Core/Core/Api/GraphQL/Resources/graphql.config.yml b/Core/Core/Api/GraphQL/Resources/graphql.config.yml index 64c50ab285..57740f5e8f 100644 --- a/Core/Core/Api/GraphQL/Resources/graphql.config.yml +++ b/Core/Core/Api/GraphQL/Resources/graphql.config.yml @@ -1,2 +1,2 @@ -schema: https://app.speckle.systems/graphql +schema: https://latest.speckle.systems/graphql documents: '**/*.graphql' diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs index 8fe43ba10c..9590505a35 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs @@ -64,7 +64,7 @@ public void ModelUpdate_Throws_NonExistentProject() { UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", "non-existent project"); - Assert.ThrowsAsync(async () => await Sut.Update(input)); + Assert.ThrowsAsync(async () => await Sut.Update(input)); } [Test] diff --git a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs index b630d24224..965de90a8d 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/HomeViewModel.cs @@ -22,6 +22,9 @@ using Material.Styles.Themes.Base; using ReactiveUI; using Speckle.Core.Api; +using Speckle.Core.Api.GraphQL.Enums; +using Speckle.Core.Api.GraphQL.Inputs; +using Speckle.Core.Api.GraphQL.Models; using Speckle.Core.Api.SubscriptionModels; using Speckle.Core.Credentials; using Speckle.Core.Helpers; @@ -641,17 +644,35 @@ public async void NewStreamCommand() try { using var client = new Client(dialog.Account); - var streamId = await client - .StreamCreate( - new StreamCreateInput - { - description = dialog.Description, - name = dialog.StreamName, - isPublic = dialog.IsPublic - } - ) - .ConfigureAwait(true); - var stream = await client.StreamGet(streamId).ConfigureAwait(true); + + Project createdProject; + if (dialog.Workspace is null) + { + createdProject = await client + .Project.Create( + new ProjectCreateInput( + dialog.StreamName, + dialog.Description, + dialog.IsPublic ? ProjectVisibility.Unlisted : ProjectVisibility.Private + ) + ) + .ConfigureAwait(true); + } + else + { + createdProject = await client + .Project.CreateInWorkspace( + new WorkspaceProjectCreateInput( + dialog.StreamName, + dialog.Description, + dialog.IsPublic ? ProjectVisibility.Unlisted : ProjectVisibility.Private, + dialog.Workspace.id + ) + ) + .ConfigureAwait(true); + } + + var stream = await client.StreamGet(createdProject.id).ConfigureAwait(true); var streamState = new StreamState(dialog.Account, stream); MainViewModel.RouterInstance.Navigate.Execute( diff --git a/DesktopUI2/DesktopUI2/ViewModels/WorkspaceViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/WorkspaceViewModel.cs new file mode 100644 index 0000000000..6b7f1cb1f6 --- /dev/null +++ b/DesktopUI2/DesktopUI2/ViewModels/WorkspaceViewModel.cs @@ -0,0 +1,22 @@ +#nullable enable +using ReactiveUI; +using Speckle.Core.Api.GraphQL.Models; + +namespace DesktopUI2.ViewModels; + +public sealed class WorkspaceViewModel : ReactiveObject +{ + public Workspace? Workspace { get; } + + public string Name => Workspace is not null ? Workspace.name : "Personal projects"; + public string Description => Workspace?.description ?? ""; + + public WorkspaceViewModel(Workspace workspace) + { + Workspace = workspace; + } + + private WorkspaceViewModel() { } + + public static WorkspaceViewModel PersonalProjects { get; } = new(); +} diff --git a/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml b/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml index 771e44b635..a3eff2f66b 100644 --- a/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml +++ b/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml @@ -2,11 +2,11 @@ x:Class="DesktopUI2.Views.Windows.Dialogs.NewStreamDialog" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:conv="clr-namespace:DesktopUI2.Views.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:icons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> + - + Margin="4,0" + ColumnDefinitions="*,Auto" + RowDefinitions="auto, auto"> + - - + Width="10" + Height="10" + Margin="5,0,0,0" + VerticalAlignment="Center" + IsVisible="{Binding Stream.favoritedDate, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" + Kind="Heart" + ToolTip.Tip="This is a favorite project" /> + - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + diff --git a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml index 56f24c2fd1..035d72456c 100644 --- a/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml +++ b/DesktopUI2/DesktopUI2/Views/Pages/StreamEditView.xaml @@ -25,6 +25,8 @@ + + @@ -152,7 +154,7 @@ - + - + + + + + @@ -249,7 +258,7 @@ - + Date: Tue, 15 Apr 2025 20:09:20 +0100 Subject: [PATCH 05/11] Tests(integration): Fixed failing integration tests due to changes in error responses from server (#3703) Fix integration tests --- .../Api/GraphQL/Resources/ModelResourceExceptionalTests.cs | 2 +- .../Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs index 9590505a35..8fe43ba10c 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs @@ -64,7 +64,7 @@ public void ModelUpdate_Throws_NonExistentProject() { UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", "non-existent project"); - Assert.ThrowsAsync(async () => await Sut.Update(input)); + Assert.ThrowsAsync(async () => await Sut.Update(input)); } [Test] diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs index 4d0517b5c7..47222b761f 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs @@ -60,7 +60,7 @@ public void ProjectGet_NonExistentProject() [Test] public void ProjectUpdate_NonExistentProject() { - Assert.ThrowsAsync( + Assert.ThrowsAsync( async () => _ = await Sut.Update(new("NonExistentProject", "My new name")) ); } From d0dea6027913201c9dae3994df5c2f1f258b9e56 Mon Sep 17 00:00:00 2001 From: Julio Polo <137197868+jcesarpolo@users.noreply.github.com> Date: Wed, 16 Apr 2025 05:19:39 -0500 Subject: [PATCH 06/11] fix(revit) #3615: include gutters and other subcategories in Revit connector sending "everything" (#3695) * fix(revit): include gutters and other subcategories in everything export (#3615) * style: fix formatting issue * Fixed failing integration tests (this one's on us!) --------- Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> --- .../IRevitDocumentAggregateCacheExtensions.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ConnectorRevit/RevitSharedResources/Extensions/SpeckleExtensions/IRevitDocumentAggregateCacheExtensions.cs b/ConnectorRevit/RevitSharedResources/Extensions/SpeckleExtensions/IRevitDocumentAggregateCacheExtensions.cs index 7c3352ae8b..678a532d49 100644 --- a/ConnectorRevit/RevitSharedResources/Extensions/SpeckleExtensions/IRevitDocumentAggregateCacheExtensions.cs +++ b/ConnectorRevit/RevitSharedResources/Extensions/SpeckleExtensions/IRevitDocumentAggregateCacheExtensions.cs @@ -46,7 +46,19 @@ public static void CacheInitializer(IRevitObjectCache cache, Document { var _categories = new Dictionary(); - foreach (Category category in doc.Settings.Categories) + // Document.Settings.Categories only returns the parent categories. + // To avoid iterating over all subcategories of each parent category, we add some extra categories that are not returned by Document.Settings.Categories #3615 + var extraCategories = new Category[] + { + Category.GetCategory(doc, BuiltInCategory.OST_Gutter), + Category.GetCategory(doc, BuiltInCategory.OST_Fascia), + Category.GetCategory(doc, BuiltInCategory.OST_RoofSoffit), + Category.GetCategory(doc, BuiltInCategory.OST_EdgeSlab), // Slab Edges + Category.GetCategory(doc, BuiltInCategory.OST_Cornices), // Wall Sweeps + }; + var allCategories = doc.Settings.Categories.Cast().Concat(extraCategories); + + foreach (Category category in allCategories) { if (!Helpers.Extensions.Extensions.IsCategorySupported(category)) { From 47221ca7044c74da3c56c652f60824637b9f1ce1 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:05:28 +0100 Subject: [PATCH 07/11] Loosened tests for server error response --- .../Api/GraphQL/Resources/ModelResourceExceptionalTests.cs | 4 +++- .../Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs index 8fe43ba10c..f653daed47 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs @@ -64,7 +64,9 @@ public void ModelUpdate_Throws_NonExistentProject() { UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", "non-existent project"); - Assert.ThrowsAsync(async () => await Sut.Update(input)); + var ex = Assert.CatchAsync(async () => await Sut.Update(input)); + //Different server versions respond slightly differently + Assert.That(ex, Is.TypeOf().Or.TypeOf()); } [Test] diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs index 47222b761f..68496fc0cf 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs @@ -60,9 +60,11 @@ public void ProjectGet_NonExistentProject() [Test] public void ProjectUpdate_NonExistentProject() { - Assert.ThrowsAsync( + //Different server versions respond slightly differently + var ex = Assert.CatchAsync( async () => _ = await Sut.Update(new("NonExistentProject", "My new name")) ); + Assert.That(ex, Is.TypeOf().Or.TypeOf()); } [Test] From b3e89ceb72f2181368c0db91ac5993e7f4753a5e Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:06:06 +0100 Subject: [PATCH 08/11] fix(revit): Fix bug where Revit zones with null phase fail to convert (#3704) * Auto stash before checking out "origin/dev" * Revit 2025 build configuration was misconfugured --- All.sln | 3 +++ .../ConverterRevitShared/PartialClasses/ConvertZone.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/All.sln b/All.sln index d7a3d95627..214388fd6e 100644 --- a/All.sln +++ b/All.sln @@ -461,6 +461,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevitSharedResources2025", "ConnectorRevit\RevitSharedResources2025\RevitSharedResources2025.csproj", "{7B02BACC-D9B6-4FFE-A450-7ECB5F71F209}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConnectorRevit2025", "ConnectorRevit\ConnectorRevit2025\ConnectorRevit2025.csproj", "{D607BD0A-9F7F-4C3A-9B9C-FEAD6BA49C7C}" + ProjectSection(ProjectDependencies) = postProject + {C0295BF9-9A40-4FCD-BE39-E943985CA3F8} = {C0295BF9-9A40-4FCD-BE39-E943985CA3F8} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConverterRevit2025", "Objects\Converters\ConverterRevit\ConverterRevit2025\ConverterRevit2025.csproj", "{C0295BF9-9A40-4FCD-BE39-E943985CA3F8}" EndProject diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/PartialClasses/ConvertZone.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/PartialClasses/ConvertZone.cs index a12b1f32d3..d0c5351020 100644 --- a/Objects/Converters/ConverterRevit/ConverterRevitShared/PartialClasses/ConvertZone.cs +++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/PartialClasses/ConvertZone.cs @@ -183,7 +183,7 @@ public RevitZone ZoneToSpeckle(DB.Zone revitZone) // No implicit displayValue // speckleZone.displayValue = GetElementDisplayValue(revitSpace); - speckleZone["phaseName"] = revitZone.Phase.Name; + speckleZone["phaseName"] = revitZone.Phase?.Name; return speckleZone; } From 13b5060a5ee7864553d92679754c915a540006e6 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:55:16 +0100 Subject: [PATCH 09/11] fixed tests mistake --- .../Api/GraphQL/Resources/ModelResourceExceptionalTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs index f653daed47..edf2fb9127 100644 --- a/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs @@ -66,7 +66,7 @@ public void ModelUpdate_Throws_NonExistentProject() var ex = Assert.CatchAsync(async () => await Sut.Update(input)); //Different server versions respond slightly differently - Assert.That(ex, Is.TypeOf().Or.TypeOf()); + Assert.That(ex, Is.TypeOf().Or.TypeOf()); } [Test] From c55c1cc9d4efab8c6d0ba7e5789301902f4ffd21 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:30:55 +0100 Subject: [PATCH 10/11] Fix(dui2): Fixed error handling of workspaces query on non-workspace servers (#3705) Fixed bug with active user query failing with the wrong exception --- Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs | 2 +- .../Client.StreamOperations.cs | 4 ++-- .../Api/GraphQL/Resources/ActiveUserResource.cs | 14 ++++++++++++-- .../Views/Windows/Dialogs/NewStreamDialog.xaml.cs | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs b/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs index 1eeb2a7f7b..128ca6b295 100644 --- a/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs +++ b/Core/Core/Api/GraphQL/Inputs/ProjectInputs.cs @@ -43,4 +43,4 @@ public sealed record ProjectUpdateInput( public sealed record ProjectUpdateRoleInput(string userId, string projectId, string? role); -public sealed record UserProjectsFilter(string search, IReadOnlyList? onlyWithRoles = null); +public sealed record UserProjectsFilter(string? search, IReadOnlyList? onlyWithRoles = null); diff --git a/Core/Core/Api/GraphQL/Legacy/Client.GraphqlCleintOperations/Client.StreamOperations.cs b/Core/Core/Api/GraphQL/Legacy/Client.GraphqlCleintOperations/Client.StreamOperations.cs index 2c08d41b03..dfe0c50453 100644 --- a/Core/Core/Api/GraphQL/Legacy/Client.GraphqlCleintOperations/Client.StreamOperations.cs +++ b/Core/Core/Api/GraphQL/Legacy/Client.GraphqlCleintOperations/Client.StreamOperations.cs @@ -251,7 +251,7 @@ public async Task> StreamSearch( Variables = new { query, limit } }; - var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? + // var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? return (await ExecuteGraphQLRequest(request, cancellationToken).ConfigureAwait(false)).streams.items; } @@ -400,7 +400,7 @@ public async Task StreamGetPendingCollaborators( }", Variables = new { id = streamId } }; - var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? + // var res = await GQLClient.SendMutationAsync(request, cancellationToken).ConfigureAwait(false); //WARN: Why do we do this? return (await ExecuteGraphQLRequest(request, cancellationToken).ConfigureAwait(false)).stream; } diff --git a/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs b/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs index bd62029839..599fcab624 100644 --- a/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs +++ b/Core/Core/Api/GraphQL/Resources/ActiveUserResource.cs @@ -75,12 +75,17 @@ query CanCreatePersonalProject { var request = new GraphQLRequest { Query = QUERY, }; var response = await _client - .ExecuteGraphQLRequest>>>( + .ExecuteGraphQLRequest>>>( request, cancellationToken ) .ConfigureAwait(false); + if (response.data is null) + { + throw new SpeckleGraphQLException("GraphQL response indicated that the ActiveUser could not be found"); + } + return response.data.data.data; } @@ -133,12 +138,17 @@ query ActiveUser($limit: Int!, $cursor: String, $filter: UserWorkspacesFilter) { }; var response = await _client - .ExecuteGraphQLRequest>>>( + .ExecuteGraphQLRequest>>>( request, cancellationToken ) .ConfigureAwait(false); + if (response.data is null) + { + throw new SpeckleGraphQLException("GraphQL response indicated that the ActiveUser could not be found"); + } + return response.data.data; } diff --git a/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml.cs b/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml.cs index 42bfbfdf14..05b44cc8ed 100644 --- a/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml.cs +++ b/DesktopUI2/DesktopUI2/Views/Windows/Dialogs/NewStreamDialog.xaml.cs @@ -87,7 +87,7 @@ _accountsOptions.SelectedItem is not AccountViewModel selectedAccount return; } - const string READY_MESSAGE = "Ready"; + const string READY_MESSAGE = " "; PermissionCheckResult result; if (selectedWorkspace.Workspace is null) From 2f93b47d34941883e32c2915531bc51015af7d59 Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:16:10 +0100 Subject: [PATCH 11/11] Fix(CI): Fixed broken digicert windows cert syncing (#3707) Sync certs using windows certsysnc --- .circleci/scripts/config-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/scripts/config-template.yml b/.circleci/scripts/config-template.yml index e0918763e7..b3c12d8880 100644 --- a/.circleci/scripts/config-template.yml +++ b/.circleci/scripts/config-template.yml @@ -240,7 +240,7 @@ jobs: # Each project will have individual jobs for each specific task it has to - run: name: Sync Certs command: | - & $env:SSM\smksp_cert_sync.exe + & $env:SSM\smctl.exe windows certsync - run: name: Build Installer Signed command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\%SLUG%.iss /Sbyparam=$p /DSIGN_INSTALLER /DCODE_SIGNING_CERT_FINGERPRINT=%SM_CODE_SIGNING_CERT_SHA1_HASH%