From 881d04c407b329f53e8e9f28ef1b241006263356 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 19:24:50 +0300 Subject: [PATCH 01/14] feat: add BsonIgnoreExtraElements --- .../Persistence.Mongodb/Entity/Analysis/MongodbAnalysisInfo.cs | 1 + .../Driven/Persistence.Mongodb/Entity/MongodbProjectInstance.cs | 1 + .../Driven/Persistence.Mongodb/Entity/MongodbProjectModel.cs | 1 + .../Driven/Persistence.Mongodb/Entity/Script/MongodbScript.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Adapters/Driven/Persistence.Mongodb/Entity/Analysis/MongodbAnalysisInfo.cs b/src/Adapters/Driven/Persistence.Mongodb/Entity/Analysis/MongodbAnalysisInfo.cs index e77e1853..bb149242 100644 --- a/src/Adapters/Driven/Persistence.Mongodb/Entity/Analysis/MongodbAnalysisInfo.cs +++ b/src/Adapters/Driven/Persistence.Mongodb/Entity/Analysis/MongodbAnalysisInfo.cs @@ -6,6 +6,7 @@ namespace ScriptBee.Persistence.Mongodb.Entity.Analysis; +[BsonIgnoreExtraElements] public class MongodbAnalysisInfo : IDocument { [BsonId] diff --git a/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectInstance.cs b/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectInstance.cs index 0dc787d5..71171ee1 100644 --- a/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectInstance.cs +++ b/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectInstance.cs @@ -4,6 +4,7 @@ namespace ScriptBee.Persistence.Mongodb.Entity; +[BsonIgnoreExtraElements] public class MongodbProjectInstance : IDocument { [BsonId] diff --git a/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectModel.cs b/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectModel.cs index 322978be..3ad161ca 100644 --- a/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectModel.cs +++ b/src/Adapters/Driven/Persistence.Mongodb/Entity/MongodbProjectModel.cs @@ -4,6 +4,7 @@ namespace ScriptBee.Persistence.Mongodb.Entity; +[BsonIgnoreExtraElements] public class MongodbProjectModel : IDocument { [BsonId] diff --git a/src/Adapters/Driven/Persistence.Mongodb/Entity/Script/MongodbScript.cs b/src/Adapters/Driven/Persistence.Mongodb/Entity/Script/MongodbScript.cs index 032c54cf..eb9fe24b 100644 --- a/src/Adapters/Driven/Persistence.Mongodb/Entity/Script/MongodbScript.cs +++ b/src/Adapters/Driven/Persistence.Mongodb/Entity/Script/MongodbScript.cs @@ -4,6 +4,7 @@ namespace ScriptBee.Persistence.Mongodb.Entity.Script; +[BsonIgnoreExtraElements] public class MongodbScript : IDocument { [BsonId] From f6d3385d58254cc2b512a2fabcd99ba6d5753619 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 19:25:42 +0300 Subject: [PATCH 02/14] feat: add instance not allocated dialog and open it in project details --- ...stance-not-allocated-dialog.component.html | 10 +++++++ ...stance-not-allocated-dialog.component.scss | 0 ...instance-not-allocated-dialog.component.ts | 26 ++++++++++++++++++ .../project-details-page.component.ts | 27 ++++++++++++++++++- ScriptBeeClient/src/app/types/instance.ts | 3 ++- ScriptBeeClient/src/app/utils/api.ts | 7 +++++ 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html create mode 100644 ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.scss create mode 100644 ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts diff --git a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html new file mode 100644 index 00000000..6d26619d --- /dev/null +++ b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html @@ -0,0 +1,10 @@ +

Instance not allocated

+
+ There is no analysis instance allocated for the project. Having an instance is required for running analysis +
+

Do you want to allocate it now ?

+
+
+ + +
diff --git a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.scss b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts new file mode 100644 index 00000000..cf4a7c19 --- /dev/null +++ b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts @@ -0,0 +1,26 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog'; +import { CreateScriptDialogData } from '../../../pages/projects/project-details/analysis/script-tree/create-script-dialog/create-script-dialog.component'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-instance-not-allocated-dialog', + templateUrl: './instance-not-allocated-dialog.component.html', + styleUrls: ['./instance-not-allocated-dialog.component.scss'], + imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatExpansionModule, MatSelectModule, MatButtonModule, FormsModule], +}) +export class InstanceNotAllocatedDialog { + constructor( + @Inject(MAT_DIALOG_DATA) public data: CreateScriptDialogData, + public dialogRef: MatDialogRef + ) {} + + onCloseClick(): void { + this.dialogRef.close(); + } + + onAllocateClick(): void {} +} diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts index 5ad3b008..03dafadb 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts @@ -4,6 +4,11 @@ import { MatTabsModule } from '@angular/material/tabs'; import { filter, first } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatIcon } from '@angular/material/icon'; +import { InstanceService } from '../../../services/instances/instance.service'; +import { HttpErrorResponse } from '@angular/common/http'; +import { isNoInstanceAllocatedForProjectError } from '../../../utils/api'; +import { MatDialog } from '@angular/material/dialog'; +import { InstanceNotAllocatedDialog } from '../../../components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component'; interface TabInfo { link: string; @@ -40,7 +45,9 @@ export class ProjectDetailsPage { constructor( private route: ActivatedRoute, - private router: Router + private router: Router, + private instanceService: InstanceService, + private dialog: MatDialog ) { router.events .pipe( @@ -53,6 +60,24 @@ export class ProjectDetailsPage { const urlPart = urlParts[urlParts.length - 1]; this.activeTab.set(this.tabInfo.find((t) => t.link === urlPart) ?? this.tabInfo[0]); }); + + route.paramMap.pipe(takeUntilDestroyed()).subscribe({ + next: (paramMap) => { + const projectId = paramMap.get('id'); + console.log(projectId); + if (projectId) { + this.instanceService.getCurrentInstance(projectId).subscribe({ + error: (errorResponse: HttpErrorResponse) => { + if (isNoInstanceAllocatedForProjectError(errorResponse)) { + this.dialog.open(InstanceNotAllocatedDialog, { + disableClose: true, + }); + } + }, + }); + } + }, + }); } selectTab(tab: TabInfo) { diff --git a/ScriptBeeClient/src/app/types/instance.ts b/ScriptBeeClient/src/app/types/instance.ts index 7c881bfa..03e9ecfe 100644 --- a/ScriptBeeClient/src/app/types/instance.ts +++ b/ScriptBeeClient/src/app/types/instance.ts @@ -1,7 +1,8 @@ export interface InstanceInfo { id: string; + creationDate: string; + // TODO FIXIT(#29, #70): remove this properties and make a separate api call to get the context loaders: string[]; linkers: string[]; loadedModels: Record; - creationDate: string; } diff --git a/ScriptBeeClient/src/app/utils/api.ts b/ScriptBeeClient/src/app/utils/api.ts index a9c67681..07fff000 100644 --- a/ScriptBeeClient/src/app/utils/api.ts +++ b/ScriptBeeClient/src/app/utils/api.ts @@ -1,7 +1,14 @@ import { ErrorResponse } from '../types/api'; +import { HttpErrorResponse } from '@angular/common/http'; export const DEFAULT_ERROR_RESPONSE: ErrorResponse = { title: 'Unexpected Error Occurred', detail: 'Please try again or contact support.', status: 500, }; + +export function isNoInstanceAllocatedForProjectError(errorResponse: HttpErrorResponse) { + const error: ErrorResponse = errorResponse.error; + + return error.title === 'No Instance Allocated For Project'; +} From 13d3b21c0dd4689e5d5bbf5ef82e3d6abafa36b0 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:02:56 +0300 Subject: [PATCH 03/14] feat: update analysis Dockerfile --- calculation.Dockerfile => analysis.Dockerfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) rename calculation.Dockerfile => analysis.Dockerfile (72%) diff --git a/calculation.Dockerfile b/analysis.Dockerfile similarity index 72% rename from calculation.Dockerfile rename to analysis.Dockerfile index a2e05771..6b71cd6c 100644 --- a/calculation.Dockerfile +++ b/analysis.Dockerfile @@ -11,10 +11,14 @@ COPY src/Common src/Common COPY src/Application/Domain/Model src/Application/Domain/Model COPY src/Application/Domain/Service.Analysis src/Application/Domain/Service.Analysis +COPY src/Application/Domain/Service.Plugin src/Application/Domain/Service.Plugin COPY src/Application/Ports/Driving/UseCases.Analysis src/Application/Ports/Driving/UseCases.Analysis +COPY src/Application/Ports/Driving/UseCases.Plugin src/Application/Ports/Driving/UseCases.Plugin + COPY src/Application/Ports/Driven/Ports.Analysis src/Application/Ports/Driven/Ports.Analysis COPY src/Application/Ports/Driven/Ports.Files src/Application/Ports/Driven/Ports.Files +COPY src/Application/Ports/Driven/Ports.Instance src/Application/Ports/Driven/Ports.Instance COPY src/Application/Ports/Driven/Ports.Plugins src/Application/Ports/Driven/Ports.Plugins COPY src/Application/Ports/Driven/Ports.Project src/Application/Ports/Driven/Ports.Project @@ -23,11 +27,11 @@ COPY src/Adapters/Driven/Persistence.InMemory src/Adapters/Driven/Persistence.In COPY src/Adapters/Driven/Persistence.Mongodb src/Adapters/Driven/Persistence.Mongodb COPY src/Adapters/Driving/Common.Web src/Adapters/Driving/Common.Web -COPY src/Adapters/Driving/Calculation.Web src/Adapters/Driving/Calculation.Web +COPY src/Adapters/Driving/Analysis.Web src/Adapters/Driving/Analysis.Web -RUN dotnet restore src/Adapters/Driving/Calculation.Web +RUN dotnet restore src/Adapters/Driving/Analysis.Web -RUN dotnet publish src/Adapters/Driving/Calculation.Web -c Release -o publish --no-restore +RUN dotnet publish src/Adapters/Driving/Analysis.Web -c Release -o publish --no-restore # Build the final image FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final @@ -39,4 +43,4 @@ WORKDIR /app COPY --from=build_webapp /app/publish . -ENTRYPOINT ["dotnet", "Calculation.Web.dll"] +ENTRYPOINT ["dotnet", "Analysis.Web.dll"] From f14d18c5e89fbe07167f54cde3a979d04a4b3b2c Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:04:46 +0300 Subject: [PATCH 04/14] feat: update CalculationInstanceDockerAdapter to get the first free port --- .../CalculationInstanceDockerAdapter.cs | 12 +++++++--- .../Config/CalculationDockerConfig.cs | 4 +--- .../AnalysisDockerInstanceExtensions.cs | 23 +++++++++++++++++++ .../FreePortProvider.cs | 16 +++++++++++++ .../IFreePortProvider.cs | 6 +++++ .../ScriptBeeCalculationConfigExtensions.cs | 11 ++------- src/Adapters/Driving/Web/appsettings.json | 2 +- .../CalculationInstanceDockerAdapterTest.cs | 18 ++++++++++----- .../FreePortProviderTest.cs | 14 +++++++++++ 9 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/Adapters/Driven/Analysis.Instance.Docker/Extensions/AnalysisDockerInstanceExtensions.cs create mode 100644 src/Adapters/Driven/Analysis.Instance.Docker/FreePortProvider.cs create mode 100644 src/Adapters/Driven/Analysis.Instance.Docker/IFreePortProvider.cs create mode 100644 test/Adapters/Driven/Analysis.Instance.Docker.Tests/FreePortProviderTest.cs diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs b/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs index 2431e66f..ef0b6074 100644 --- a/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs +++ b/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs @@ -12,7 +12,8 @@ namespace ScriptBee.Analysis.Instance.Docker; public class CalculationInstanceDockerAdapter( IOptions config, - ILogger logger + ILogger logger, + IFreePortProvider freePortProvider ) : IAllocateInstance, IDeallocateInstance { public async Task Allocate( @@ -49,7 +50,7 @@ await client.Containers.StartContainerAsync( client, response.ID, calculationDockerConfig.Network, - calculationDockerConfig.Port, + freePortProvider.GetFreeTcpPort(), cancellationToken ); } @@ -127,7 +128,7 @@ await client.Containers.RemoveContainerAsync( private static async Task GetContainerUrl( DockerClient client, string containerId, - string networkName, + string? networkName, int port, CancellationToken cancellationToken ) @@ -137,6 +138,11 @@ CancellationToken cancellationToken cancellationToken ); + if (networkName == null) + { + return $"http://localhost:{port}"; + } + return containerInfo.NetworkSettings.Networks.TryGetValue(networkName, out var network) ? $"http://{network.IPAddress}:{port}" : $"http://localhost:{port}"; diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs b/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs index e8fff2a0..ce26fa53 100644 --- a/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs +++ b/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs @@ -4,7 +4,5 @@ public class CalculationDockerConfig { public required string DockerSocket { get; init; } - public required int Port { get; init; } - - public required string Network { get; init; } + public string? Network { get; init; } } diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/Extensions/AnalysisDockerInstanceExtensions.cs b/src/Adapters/Driven/Analysis.Instance.Docker/Extensions/AnalysisDockerInstanceExtensions.cs new file mode 100644 index 00000000..10664fb8 --- /dev/null +++ b/src/Adapters/Driven/Analysis.Instance.Docker/Extensions/AnalysisDockerInstanceExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using ScriptBee.Analysis.Instance.Docker.Config; +using ScriptBee.Ports.Instance; + +namespace ScriptBee.Analysis.Instance.Docker.Extensions; + +public static class AnalysisDockerInstanceExtensions +{ + public static IServiceCollection AddDockerInstanceAdapter( + this IServiceCollection services, + string dockerConfigSection + ) + { + services + .AddOptions() + .BindConfiguration("ScriptBee:Calculation:Docker"); + + return services + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } +} diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/FreePortProvider.cs b/src/Adapters/Driven/Analysis.Instance.Docker/FreePortProvider.cs new file mode 100644 index 00000000..f9f6abc4 --- /dev/null +++ b/src/Adapters/Driven/Analysis.Instance.Docker/FreePortProvider.cs @@ -0,0 +1,16 @@ +using System.Net; +using System.Net.Sockets; + +namespace ScriptBee.Analysis.Instance.Docker; + +public class FreePortProvider : IFreePortProvider +{ + public int GetFreeTcpPort() + { + var l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } +} diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/IFreePortProvider.cs b/src/Adapters/Driven/Analysis.Instance.Docker/IFreePortProvider.cs new file mode 100644 index 00000000..0787bf0e --- /dev/null +++ b/src/Adapters/Driven/Analysis.Instance.Docker/IFreePortProvider.cs @@ -0,0 +1,6 @@ +namespace ScriptBee.Analysis.Instance.Docker; + +public interface IFreePortProvider +{ + int GetFreeTcpPort(); +} diff --git a/src/Adapters/Driving/Web/Extensions/ScriptBeeCalculationConfigExtensions.cs b/src/Adapters/Driving/Web/Extensions/ScriptBeeCalculationConfigExtensions.cs index f1473100..6e993155 100644 --- a/src/Adapters/Driving/Web/Extensions/ScriptBeeCalculationConfigExtensions.cs +++ b/src/Adapters/Driving/Web/Extensions/ScriptBeeCalculationConfigExtensions.cs @@ -1,5 +1,4 @@ -using ScriptBee.Analysis.Instance.Docker; -using ScriptBee.Analysis.Instance.Docker.Config; +using ScriptBee.Analysis.Instance.Docker.Extensions; using ScriptBee.Domain.Model.Analysis; using ScriptBee.Ports.Instance; using ScriptBee.Web.Config; @@ -30,12 +29,6 @@ ConfigurationManager configurationManager ) ); - services - .AddOptions() - .BindConfiguration("ScriptBee:Calculation:Docker"); - - return services - .AddSingleton() - .AddSingleton(); + return services.AddDockerInstanceAdapter("ScriptBee:Calculation:Docker"); } } diff --git a/src/Adapters/Driving/Web/appsettings.json b/src/Adapters/Driving/Web/appsettings.json index 5749e67d..2b4e0f13 100644 --- a/src/Adapters/Driving/Web/appsettings.json +++ b/src/Adapters/Driving/Web/appsettings.json @@ -31,7 +31,7 @@ }, "ScriptBee": { "Calculation": { - "Image": "scriptbee/calculation:latest", + "Image": "dxworks/scriptbee/calculation:latest", "Driver": "Docker", "Docker": { "DockerSocket": "unix:///var/run/docker.sock" diff --git a/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs index fa41c147..dd866fc1 100644 --- a/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs +++ b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs @@ -1,6 +1,7 @@ using Docker.DotNet.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using NSubstitute; using ScriptBee.Analysis.Instance.Docker.Config; using ScriptBee.Domain.Model.Analysis; using ScriptBee.Domain.Model.Instance; @@ -12,10 +13,11 @@ namespace ScriptBee.Analysis.Instance.Docker.Tests; public class CalculationInstanceDockerAdapterTest : IClassFixture { - private readonly IOptions _config; + private readonly IFreePortProvider _freePortProvider = Substitute.For(); private readonly DockerFixture _dockerFixture; private readonly CalculationInstanceDockerAdapter _calculationInstanceDockerAdapter; + private const int TestPort = 8080; public CalculationInstanceDockerAdapterTest( DockerFixture dockerFixture, @@ -23,21 +25,25 @@ ITestOutputHelper outputHelper ) { _dockerFixture = dockerFixture; - _config = Options.Create( + var config = Options.Create( new CalculationDockerConfig { DockerSocket = dockerFixture.DockerClient.Configuration.EndpointBaseUri.ToString(), Network = DockerFixture.TestNetworkName, - Port = 8080, } ); + _freePortProvider.GetFreeTcpPort().Returns(TestPort); var loggerFactory = LoggerFactory.Create(builder => builder.AddProvider(new XUnitLoggerProvider(outputHelper)) ); var logger = loggerFactory.CreateLogger(); - _calculationInstanceDockerAdapter = new CalculationInstanceDockerAdapter(_config, logger); + _calculationInstanceDockerAdapter = new CalculationInstanceDockerAdapter( + config, + logger, + _freePortProvider + ); } [Fact] @@ -49,7 +55,7 @@ public async Task Allocate_ShouldCreateAndStartContainerAndReturnUrlWithNetworkI var containerUrl = await _calculationInstanceDockerAdapter.Allocate(instanceId, image); containerUrl.ShouldStartWith("http://"); - containerUrl.ShouldContain($":{_config.Value.Port}"); + containerUrl.ShouldContain($":{TestPort}"); var containers = await _dockerFixture.DockerClient.Containers.ListContainersAsync( new ContainersListParameters { All = true } ); @@ -63,7 +69,7 @@ public async Task Allocate_ShouldCreateAndStartContainerAndReturnUrlWithNetworkI .NetworkSettings.Networks[DockerFixture.TestNetworkName] .IPAddress.ShouldNotBeNullOrEmpty(); containerUrl.ShouldBe( - $"http://{ourContainer.NetworkSettings.Networks[DockerFixture.TestNetworkName].IPAddress}:{_config.Value.Port}" + $"http://{ourContainer.NetworkSettings.Networks[DockerFixture.TestNetworkName].IPAddress}:{TestPort}" ); } diff --git a/test/Adapters/Driven/Analysis.Instance.Docker.Tests/FreePortProviderTest.cs b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/FreePortProviderTest.cs new file mode 100644 index 00000000..84935fd6 --- /dev/null +++ b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/FreePortProviderTest.cs @@ -0,0 +1,14 @@ +namespace ScriptBee.Analysis.Instance.Docker.Tests; + +public class FreePortProviderTest +{ + private readonly FreePortProvider _freePortProvider = new(); + + [Fact] + public void PortIsNotZero() + { + var port = _freePortProvider.GetFreeTcpPort(); + + port.ShouldBeGreaterThan(0); + } +} From 1953cae98c7c289094e3072535b7bf9f03b7969d Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:05:48 +0300 Subject: [PATCH 05/14] feat: update instance not allocated dialog to allocate instance --- ...stance-not-allocated-dialog.component.html | 8 ++++ ...instance-not-allocated-dialog.component.ts | 40 ++++++++++++++++--- .../currently-loaded-models.component.html | 3 +- .../currently-loaded-models.component.ts | 3 +- .../project-details-page.component.ts | 2 +- .../services/instances/instance.service.ts | 4 ++ ScriptBeeClient/src/app/types/instance.ts | 4 -- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html index 6d26619d..1259c619 100644 --- a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html +++ b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.html @@ -3,6 +3,14 @@

Instance not allocated

There is no analysis instance allocated for the project. Having an instance is required for running analysis

Do you want to allocate it now ?

+ + @if (allocateInstanceHandler.isLoading()) { + + } + + @if (allocateInstanceHandler.error()) { + + }
diff --git a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts index cf4a7c19..39d68c34 100644 --- a/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts +++ b/ScriptBeeClient/src/app/components/dialogs/instance-not-allocated-dialog/instance-not-allocated-dialog.component.ts @@ -1,26 +1,56 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog'; -import { CreateScriptDialogData } from '../../../pages/projects/project-details/analysis/script-tree/create-script-dialog/create-script-dialog.component'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatButtonModule } from '@angular/material/button'; import { MatSelectModule } from '@angular/material/select'; import { FormsModule } from '@angular/forms'; +import { InstanceService } from '../../../services/instances/instance.service'; +import { ErrorStateComponent } from '../../error-state/error-state.component'; +import { LoadingProgressBarComponent } from '../../loading-progress-bar/loading-progress-bar.component'; +import { apiHandler } from '../../../utils/apiHandler'; + +export interface InstanceNotAllocatedDialogData { + projectId: string; +} @Component({ selector: 'app-instance-not-allocated-dialog', templateUrl: './instance-not-allocated-dialog.component.html', styleUrls: ['./instance-not-allocated-dialog.component.scss'], - imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatExpansionModule, MatSelectModule, MatButtonModule, FormsModule], + imports: [ + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatExpansionModule, + MatSelectModule, + MatButtonModule, + FormsModule, + ErrorStateComponent, + LoadingProgressBarComponent, + ], }) export class InstanceNotAllocatedDialog { + allocateInstanceHandler = apiHandler( + (params: { projectId: string }) => this.instanceService.allocateInstance(params.projectId), + () => { + this.dialogRef.close(); + } + ); + constructor( - @Inject(MAT_DIALOG_DATA) public data: CreateScriptDialogData, - public dialogRef: MatDialogRef + @Inject(MAT_DIALOG_DATA) + public data: InstanceNotAllocatedDialogData, + public dialogRef: MatDialogRef, + private instanceService: InstanceService ) {} onCloseClick(): void { this.dialogRef.close(); } - onAllocateClick(): void {} + onAllocateClick(): void { + this.allocateInstanceHandler.execute({ + projectId: this.data.projectId, + }); + } } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html index 642a76d7..b36d35c3 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.html @@ -2,7 +2,8 @@
Used Linkers: - @for (linker of instanceInfo().linkers; track linker) { + + @for (linker of []; track linker) { {{ linker }} }
diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts index a13dde3e..81ab65f6 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/currently-loaded-models/currently-loaded-models.component.ts @@ -13,7 +13,8 @@ export class CurrentlyLoadedModelsComponent { instanceInfo = input.required(); loadedFiles = computed(() => { - return convertToTreeNodes(this.instanceInfo().loadedModels); + // TODO FIXIT(#70): populate from api + return convertToTreeNodes({}); }); } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts index 03dafadb..54df6d05 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/project-details-page.component.ts @@ -64,13 +64,13 @@ export class ProjectDetailsPage { route.paramMap.pipe(takeUntilDestroyed()).subscribe({ next: (paramMap) => { const projectId = paramMap.get('id'); - console.log(projectId); if (projectId) { this.instanceService.getCurrentInstance(projectId).subscribe({ error: (errorResponse: HttpErrorResponse) => { if (isNoInstanceAllocatedForProjectError(errorResponse)) { this.dialog.open(InstanceNotAllocatedDialog, { disableClose: true, + data: { projectId }, }); } }, diff --git a/ScriptBeeClient/src/app/services/instances/instance.service.ts b/ScriptBeeClient/src/app/services/instances/instance.service.ts index 6bdc347e..6b50978a 100644 --- a/ScriptBeeClient/src/app/services/instances/instance.service.ts +++ b/ScriptBeeClient/src/app/services/instances/instance.service.ts @@ -12,4 +12,8 @@ export class InstanceService { getCurrentInstance(projectId: string): Observable { return this.http.get(`/api/projects/${projectId}/instances/current`); } + + allocateInstance(projectId: string): Observable { + return this.http.post(`/api/projects/${projectId}/instances`, null); + } } diff --git a/ScriptBeeClient/src/app/types/instance.ts b/ScriptBeeClient/src/app/types/instance.ts index 03e9ecfe..382cb47e 100644 --- a/ScriptBeeClient/src/app/types/instance.ts +++ b/ScriptBeeClient/src/app/types/instance.ts @@ -1,8 +1,4 @@ export interface InstanceInfo { id: string; creationDate: string; - // TODO FIXIT(#29, #70): remove this properties and make a separate api call to get the context - loaders: string[]; - linkers: string[]; - loadedModels: Record; } From d20a5dcb0b24ed22162ba3468f9dd09cc36d42a7 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:34:41 +0300 Subject: [PATCH 06/14] feat: update get all linkers endpoint --- .../model/link-models/link-models.component.html | 2 +- .../model/link-models/link-models.component.ts | 7 ++++++- ScriptBeeClient/src/app/services/linkers/linker.service.ts | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html index 6879a61b..3e70aa7c 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html @@ -27,6 +27,6 @@ @if (!selectedLinkerId()) { -
A loader must be selected in order to upload models
+
A linker must be selected
}
diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts index 5ad31ac1..09bcfe0a 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts @@ -17,11 +17,16 @@ import { apiHandler } from '../../../../../utils/apiHandler'; }) export class LinkModelsComponent { projectId = input.required(); + instanceId = input.required(); selectedLinkerId = signal(undefined); getLinkersResource = createRxResourceHandler({ - loader: () => this.linkerService.getAllLinkers(), + request: () => ({ + projectId: this.projectId(), + instanceId: this.instanceId(), + }), + loader: (params) => this.linkerService.getAllLinkers(params.request.projectId, params.request.instanceId), }); linkModelsHandler = apiHandler( diff --git a/ScriptBeeClient/src/app/services/linkers/linker.service.ts b/ScriptBeeClient/src/app/services/linkers/linker.service.ts index 4644fb18..0a7bb867 100644 --- a/ScriptBeeClient/src/app/services/linkers/linker.service.ts +++ b/ScriptBeeClient/src/app/services/linkers/linker.service.ts @@ -17,7 +17,7 @@ export class LinkerService { }); } - getAllLinkers() { - return this.http.get(this.linkersAPIUrl); + getAllLinkers(projectId: string, instanceId: string) { + return this.http.get(`/api/projects/${projectId}/instances/${instanceId}/loaders`); } } From 8999dba6a0c5b1469b97684646b7b90e20ae5853 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:35:00 +0300 Subject: [PATCH 07/14] feat: update get all loaders endpoint --- .../model/upload-models/upload-models.component.ts | 9 +++++++-- .../src/app/services/loaders/loader.service.ts | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/upload-models/upload-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/upload-models/upload-models.component.ts index d5d2f955..99585f4a 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/upload-models/upload-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/upload-models/upload-models.component.ts @@ -29,15 +29,20 @@ import { UploadService } from '../../../../../services/upload/upload.service'; }) export class UploadModelsComponent { projectId = input.required(); + instanceId = input.required(); selectedLoaderId = signal(undefined); getLoadersResource = createRxResourceHandler({ - loader: () => this.loaderService.getAllLoaders(), + request: () => ({ + projectId: this.projectId(), + instanceId: this.instanceId(), + }), + loader: (params) => this.loaderService.getAllLoaders(params.request.projectId, params.request.instanceId), }); uploadModelsHandler = apiHandler( - (params: { loaderId: string; projectId: string; files: File[] }) => this.uploadService.uploadModels(params.loaderId, params.projectId, params.files), + (params: { loaderId: string; projectId: string; files: File[] }) => this.uploadService.uploadModels(params.projectId, params.loaderId, params.files), (data) => { console.log(data); } diff --git a/ScriptBeeClient/src/app/services/loaders/loader.service.ts b/ScriptBeeClient/src/app/services/loaders/loader.service.ts index 0cb5628d..fd370c72 100644 --- a/ScriptBeeClient/src/app/services/loaders/loader.service.ts +++ b/ScriptBeeClient/src/app/services/loaders/loader.service.ts @@ -13,8 +13,8 @@ export class LoaderService { constructor(private http: HttpClient) {} - getAllLoaders() { - return this.http.get(this.loadersAPIUrl); + getAllLoaders(projectId: string, instanceId: string) { + return this.http.get(`/api/projects/${projectId}/instances/${instanceId}/loaders`); } loadModels(projectId: string, checkedFiles: TreeNode[]) { From a781ac84d508c01483dfdefd7932a0baad6d9f2e Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:35:26 +0300 Subject: [PATCH 08/14] feat: update upload files for loader --- .../model/project-model-page.component.html | 68 ++++++++++--------- .../src/app/services/upload/upload.service.ts | 8 +-- .../src/app/types/upload-models-result.ts | 4 +- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html index 441911c3..8e4b26c1 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html @@ -6,36 +6,42 @@
} @else {
- - - - - - - - - - - - - - @if (currentInstanceInfoResource.isLoading()) { - - } @else if (currentInstanceInfoResource.error()) { - - } @else { - - } - - - - @if (currentInstanceInfoResource.isLoading()) { - - } @else if (currentInstanceInfoResource.error()) { - - } @else { - - } - + @if (currentInstanceInfoResource.isLoading()) { + + } @else if (currentInstanceInfoResource.error()) { + + } @else { + + + + + + + + + + + + + + @if (currentInstanceInfoResource.isLoading()) { + + } @else if (currentInstanceInfoResource.error()) { + + } @else { + + } + + + + @if (currentInstanceInfoResource.isLoading()) { + + } @else if (currentInstanceInfoResource.error()) { + + } @else { + + } + + }
} diff --git a/ScriptBeeClient/src/app/services/upload/upload.service.ts b/ScriptBeeClient/src/app/services/upload/upload.service.ts index a869bddb..e8c285bb 100644 --- a/ScriptBeeClient/src/app/services/upload/upload.service.ts +++ b/ScriptBeeClient/src/app/services/upload/upload.service.ts @@ -6,16 +6,12 @@ import { UploadModelsResult } from '../../types/upload-models-result'; providedIn: 'root', }) export class UploadService { - private uploadFilesUrl = '/api/uploadmodel/fromfile'; - constructor(private http: HttpClient) {} - uploadModels(loaderId: string, projectId: string, files: File[]) { + uploadModels(projectId: string, loaderId: string, files: File[]) { const formData = new FormData(); - formData.append('loaderId', loaderId); files.forEach((file) => formData.append('files', file)); - formData.append('projectId', projectId); - return this.http.post(this.uploadFilesUrl, formData); + return this.http.put(`/api/projects/${projectId}/loaders/${loaderId}/files`, formData); } } diff --git a/ScriptBeeClient/src/app/types/upload-models-result.ts b/ScriptBeeClient/src/app/types/upload-models-result.ts index ec483367..b27b5be6 100644 --- a/ScriptBeeClient/src/app/types/upload-models-result.ts +++ b/ScriptBeeClient/src/app/types/upload-models-result.ts @@ -1,4 +1,4 @@ export interface UploadModelsResult { - loaderName: string; - files: string[]; + loaderId: string; + fileNames: string[]; } From 61f3e4ddef23339b38069f24f191ca0ac9182278 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:42:23 +0300 Subject: [PATCH 09/14] feat: update link models endpoint --- .../model/link-models/link-models.component.html | 2 +- .../model/link-models/link-models.component.ts | 9 +++------ .../src/app/services/linkers/linker.service.ts | 15 ++++++--------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html index 3e70aa7c..6a93491f 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.html @@ -8,7 +8,7 @@ Linker - @for (loader of getLinkersResource.value()!; track loader.id) { + @for (loader of [{ id: 'id', name: 'linker' }]!; track loader.id) { {{ loader.name }} diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts index 09bcfe0a..f13dcac1 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/link-models/link-models.component.ts @@ -29,11 +29,8 @@ export class LinkModelsComponent { loader: (params) => this.linkerService.getAllLinkers(params.request.projectId, params.request.instanceId), }); - linkModelsHandler = apiHandler( - (params: { projectId: string; linkerId: string }) => this.linkerService.linkModels(params.projectId, params.linkerId), - (data) => { - console.log(data); - } + linkModelsHandler = apiHandler((params: { projectId: string; instanceId: string; linkerId: string }) => + this.linkerService.linkModels(params.projectId, params.instanceId, params.linkerId) ); constructor(private linkerService: LinkerService) {} @@ -44,6 +41,6 @@ export class LinkModelsComponent { return; } - this.linkModelsHandler.execute({ projectId: this.projectId(), linkerId: linkerId }); + this.linkModelsHandler.execute({ projectId: this.projectId(), instanceId: this.instanceId(), linkerId: linkerId }); } } diff --git a/ScriptBeeClient/src/app/services/linkers/linker.service.ts b/ScriptBeeClient/src/app/services/linkers/linker.service.ts index 0a7bb867..574953de 100644 --- a/ScriptBeeClient/src/app/services/linkers/linker.service.ts +++ b/ScriptBeeClient/src/app/services/linkers/linker.service.ts @@ -6,18 +6,15 @@ import { Linker } from '../../types/link-model'; providedIn: 'root', }) export class LinkerService { - private linkersAPIUrl = '/api/linkers'; - constructor(private http: HttpClient) {} - linkModels(projectId: string, linkerId: string) { - return this.http.post(this.linkersAPIUrl, { - projectId: projectId, - linkerId: linkerId, - }); - } - getAllLinkers(projectId: string, instanceId: string) { return this.http.get(`/api/projects/${projectId}/instances/${instanceId}/loaders`); } + + linkModels(projectId: string, instanceId: string, linkerId: string) { + return this.http.post(`/api/projects/${projectId}/instances/${instanceId}/context/link`, { + linkerIds: [linkerId], + }); + } } From 616db47293d5ffac6058a4d1d03ff11079acc245 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:52:53 +0300 Subject: [PATCH 10/14] feat: update load models endpoint --- .../load-models/load-models.component.ts | 12 +++---- .../model/project-model-page.component.html | 2 +- .../app/services/loaders/loader.service.ts | 31 +++++-------------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts index 633a652f..55457a8a 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts @@ -15,15 +15,14 @@ import { LoaderService } from '../../../../../services/loaders/loader.service'; }) export class LoadModelsComponent { projectId = input.required(); + instanceId = input.required(); - savedFiles = signal([]); + savedFiles = signal([{ name: 'loader', children: [{ name: 'file' }] }]); checkedFiles = signal([]); - loadModelsHandler = apiHandler( - (params: { projectId: string; checkedFiles: TreeNode[] }) => this.loaderService.loadModels(params.projectId, params.checkedFiles), - (data) => { - console.log(data); - } + loadModelsHandler = apiHandler((params: { projectId: string; instanceId: string; checkedFiles: TreeNode[] }) => + // TODO FIXIT(#14): update with the list of loaders + this.loaderService.loadModels(params.projectId, params.instanceId, params.checkedFiles) ); constructor(private loaderService: LoaderService) {} @@ -35,6 +34,7 @@ export class LoadModelsComponent { onLoadFilesClick() { this.loadModelsHandler.execute({ projectId: this.projectId(), + instanceId: this.instanceId(), checkedFiles: this.checkedFiles(), }); } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html index 8e4b26c1..9148cc89 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html @@ -16,7 +16,7 @@ - + diff --git a/ScriptBeeClient/src/app/services/loaders/loader.service.ts b/ScriptBeeClient/src/app/services/loaders/loader.service.ts index fd370c72..3e16bbed 100644 --- a/ScriptBeeClient/src/app/services/loaders/loader.service.ts +++ b/ScriptBeeClient/src/app/services/loaders/loader.service.ts @@ -1,41 +1,24 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { TreeNode } from '../../types/tree-node'; -import { Loader, LoadModel } from '../../types/load-model'; +import { TreeNodeWithParent } from '../../types/tree-node'; +import { Loader } from '../../types/load-model'; import { ReturnedContextSlice } from '../../types/returned-context-slice'; @Injectable({ providedIn: 'root', }) export class LoaderService { - private loadersAPIUrl = '/api/loaders'; - private loadersClearContextAPIUrl = '/api/loaders/clear'; - constructor(private http: HttpClient) {} getAllLoaders(projectId: string, instanceId: string) { return this.http.get(`/api/projects/${projectId}/instances/${instanceId}/loaders`); } - loadModels(projectId: string, checkedFiles: TreeNode[]) { - const loadModels: LoadModel = { - projectId: projectId, - nodes: checkedFiles - .filter((treeNode) => treeNode.children && treeNode.children.length > 0) - .map((treeNode) => ({ - loaderName: treeNode.name, - models: (treeNode.children ?? []).map((child: any) => child.name), - })), - }; - - return this.http.post(this.loadersAPIUrl, loadModels); - } - - reloadProjectContext(projectId: string) { - return this.http.post(`${this.loadersAPIUrl}/${projectId}`, null); - } + loadModels(projectId: string, instanceId: string, checkedFiles: TreeNodeWithParent[]) { + const loaderIds = checkedFiles.map((node) => node.parent?.name); - clearProjectContext(projectId: string) { - return this.http.post(`${this.loadersClearContextAPIUrl}/${projectId}`, null); + return this.http.post(`/api/projects/${projectId}/instances/${instanceId}/context/load`, { + loaderIds, + }); } } From 183b24fb8467ca92c4a9d979d4a9abbae887172c Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 20:57:52 +0300 Subject: [PATCH 11/14] feat: display basic instance information --- .../instance-info.component.html | 6 ++++ .../instance-info.component.scss | 3 ++ .../instance-info/instance-info.component.ts | 13 +++++++++ .../model/project-model-page.component.html | 28 ++++++++++--------- .../model/project-model-page.component.ts | 2 ++ 5 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.html create mode 100644 ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.scss create mode 100644 ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.ts diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.html new file mode 100644 index 00000000..c083cc97 --- /dev/null +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.html @@ -0,0 +1,6 @@ +
Instance Information
+ +
+
Instance Id: {{ instanceInfo().id }}
+
Creation Date: {{ instanceInfo().creationDate | date }}
+
diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.scss b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.scss new file mode 100644 index 00000000..6fdd66d7 --- /dev/null +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.scss @@ -0,0 +1,3 @@ +.instance-info-div { + margin-left: 8px; +} diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.ts new file mode 100644 index 00000000..a9749ba6 --- /dev/null +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/instance-info/instance-info.component.ts @@ -0,0 +1,13 @@ +import { Component, input } from '@angular/core'; +import { InstanceInfo } from '../../../../../types/instance'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'app-instance-info', + templateUrl: './instance-info.component.html', + styleUrls: ['./instance-info.component.scss'], + imports: [DatePipe], +}) +export class InstanceInfoComponent { + instanceInfo = input.required(); +} diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html index 9148cc89..28937b69 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.html @@ -12,34 +12,36 @@ } @else { - + - + - + - + - + - + + + @if (currentInstanceInfoResource.isLoading()) { - + } @else if (currentInstanceInfoResource.error()) { - + } @else { - + } - + @if (currentInstanceInfoResource.isLoading()) { - + } @else if (currentInstanceInfoResource.error()) { - + } @else { - + } } diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.ts index aa377097..797215a9 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/project-model-page.component.ts @@ -14,6 +14,7 @@ import { ProjectContextComponent } from './project-context/project-context.compo import { LoadingProgressBarComponent } from '../../../../components/loading-progress-bar/loading-progress-bar.component'; import { InstanceService } from '../../../../services/instances/instance.service'; import { CenteredSpinnerComponent } from '../../../../components/centered-spinner/centered-spinner.component'; +import { InstanceInfoComponent } from './instance-info/instance-info.component'; @Component({ selector: 'app-project-model-page', @@ -30,6 +31,7 @@ import { CenteredSpinnerComponent } from '../../../../components/centered-spinne ProjectContextComponent, LoadingProgressBarComponent, CenteredSpinnerComponent, + InstanceInfoComponent, ], }) export class ProjectModelPage { From 703dd5c93fa759abdd285893e86b29ba2fac9b15 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 21:03:00 +0300 Subject: [PATCH 12/14] feat: add message for no results displayed since no analysis was run --- .../analysis/analysis.component.html | 6 +++++- .../analysis/analysis.component.ts | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.html b/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.html index 3edabbba..8c913c92 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.html +++ b/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.html @@ -13,7 +13,11 @@ - + @if (analysisId()) { + + } @else { +
There are no results since no analysis was run yet
+ }
diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.ts index 91e79118..a4b6c795 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/analysis/analysis.component.ts @@ -6,6 +6,7 @@ import { AnalysisOutputComponent } from './output/analysis-output.component'; import { ActivatedRoute, Router } from '@angular/router'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { TreeNode } from '../../../../types/tree-node'; +import { InstanceService } from '../../../../services/instances/instance.service'; @Component({ selector: 'app-analysis', @@ -16,6 +17,7 @@ import { TreeNode } from '../../../../types/tree-node'; export class AnalysisComponent { projectId = signal(undefined); analysisId = signal(undefined); + instanceId = signal(undefined); selectedFileId = signal(null); @@ -24,7 +26,8 @@ export class AnalysisComponent { constructor( private route: ActivatedRoute, - private router: Router + private router: Router, + private instanceService: InstanceService ) { route.queryParamMap.subscribe((params) => { this.selectedFileId.set(params.get('fileId')); @@ -32,7 +35,16 @@ export class AnalysisComponent { route.parent?.paramMap.pipe(takeUntilDestroyed()).subscribe({ next: (paramMap) => { - this.projectId.set(paramMap.get('id') ?? undefined); + const id = paramMap.get('id'); + this.projectId.set(id ?? undefined); + + if (id) { + this.instanceService.getCurrentInstance(id).subscribe({ + next: (instanceInfo) => { + this.instanceId.set(instanceInfo.id); + }, + }); + } }, }); } From 544e73f4a529c825363a5e44d661b4148c00c568 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 21:10:11 +0300 Subject: [PATCH 13/14] feat: removed test values for load models --- .../project-details/model/load-models/load-models.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts b/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts index 55457a8a..4d9c9987 100644 --- a/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts +++ b/ScriptBeeClient/src/app/pages/projects/project-details/model/load-models/load-models.component.ts @@ -17,7 +17,7 @@ export class LoadModelsComponent { projectId = input.required(); instanceId = input.required(); - savedFiles = signal([{ name: 'loader', children: [{ name: 'file' }] }]); + savedFiles = signal([]); checkedFiles = signal([]); loadModelsHandler = apiHandler((params: { projectId: string; instanceId: string; checkedFiles: TreeNode[] }) => From 090eeeb0bd895335e90cc27317b652c7ab9c1a0c Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 1 Apr 2025 21:11:14 +0300 Subject: [PATCH 14/14] feat: update CalculationInstanceDockerAdapter to expose port --- .../CalculationInstanceDockerAdapter.cs | 22 +++++++++++++++++-- .../Config/CalculationDockerConfig.cs | 2 ++ .../CalculationInstanceDockerAdapterTest.cs | 9 ++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs b/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs index ef0b6074..3616ed55 100644 --- a/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs +++ b/src/Adapters/Driven/Analysis.Instance.Docker/CalculationInstanceDockerAdapter.cs @@ -27,12 +27,30 @@ public async Task Allocate( await PullImageIfNeeded(client, image.ImageName, cancellationToken); + var hostPort = freePortProvider.GetFreeTcpPort(); + + var portBindings = new Dictionary> + { + { + $"{calculationDockerConfig.Port}/tcp", + new List { new() { HostPort = hostPort.ToString() } } + }, + }; + var response = await client.Containers.CreateContainerAsync( new CreateContainerParameters { Name = $"scriptbee-calculation-{instanceId}", Image = image.ImageName, - HostConfig = new HostConfig { NetworkMode = calculationDockerConfig.Network }, + HostConfig = new HostConfig + { + NetworkMode = calculationDockerConfig.Network, + PortBindings = portBindings, + }, + ExposedPorts = new Dictionary + { + { $"{calculationDockerConfig.Port}/tcp", new EmptyStruct() }, + }, }, cancellationToken ); @@ -50,7 +68,7 @@ await client.Containers.StartContainerAsync( client, response.ID, calculationDockerConfig.Network, - freePortProvider.GetFreeTcpPort(), + hostPort, cancellationToken ); } diff --git a/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs b/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs index ce26fa53..850d9234 100644 --- a/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs +++ b/src/Adapters/Driven/Analysis.Instance.Docker/Config/CalculationDockerConfig.cs @@ -4,5 +4,7 @@ public class CalculationDockerConfig { public required string DockerSocket { get; init; } + public int Port { get; init; } = 8080; + public string? Network { get; init; } } diff --git a/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs index dd866fc1..93608a4c 100644 --- a/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs +++ b/test/Adapters/Driven/Analysis.Instance.Docker.Tests/CalculationInstanceDockerAdapterTest.cs @@ -17,7 +17,7 @@ public class CalculationInstanceDockerAdapterTest : IClassFixture private readonly DockerFixture _dockerFixture; private readonly CalculationInstanceDockerAdapter _calculationInstanceDockerAdapter; - private const int TestPort = 8080; + private readonly int _testPort; public CalculationInstanceDockerAdapterTest( DockerFixture dockerFixture, @@ -32,7 +32,8 @@ ITestOutputHelper outputHelper Network = DockerFixture.TestNetworkName, } ); - _freePortProvider.GetFreeTcpPort().Returns(TestPort); + _testPort = new FreePortProvider().GetFreeTcpPort(); + _freePortProvider.GetFreeTcpPort().Returns(_testPort); var loggerFactory = LoggerFactory.Create(builder => builder.AddProvider(new XUnitLoggerProvider(outputHelper)) @@ -55,7 +56,7 @@ public async Task Allocate_ShouldCreateAndStartContainerAndReturnUrlWithNetworkI var containerUrl = await _calculationInstanceDockerAdapter.Allocate(instanceId, image); containerUrl.ShouldStartWith("http://"); - containerUrl.ShouldContain($":{TestPort}"); + containerUrl.ShouldContain($":{_testPort}"); var containers = await _dockerFixture.DockerClient.Containers.ListContainersAsync( new ContainersListParameters { All = true } ); @@ -69,7 +70,7 @@ public async Task Allocate_ShouldCreateAndStartContainerAndReturnUrlWithNetworkI .NetworkSettings.Networks[DockerFixture.TestNetworkName] .IPAddress.ShouldNotBeNullOrEmpty(); containerUrl.ShouldBe( - $"http://{ourContainer.NetworkSettings.Networks[DockerFixture.TestNetworkName].IPAddress}:{TestPort}" + $"http://{ourContainer.NetworkSettings.Networks[DockerFixture.TestNetworkName].IPAddress}:{_testPort}" ); }