aspire icon indicating copy to clipboard operation
aspire copied to clipboard

Resource grouping annotation for dashboard

Open attilah opened this issue 1 year ago • 4 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I'd like to group my resources together and based on the interest expand/collapse them on the dashboard. Groups could be like - but varies project by project - Frontend, Backend, Control Plane, Application Plane, Supporting Services, etc...

Describe the solution you'd like

I'd like to have a GroupAnnotation or something similar that could act as a visual group for resources, the annotation would be interpreted by the dashboard. When I group resources beside naming the group I'd like to specify an order and to expand or collapse by default when the solution starts. Having an icon assigned would be a plus.

Additional context

No response

attilah avatar Oct 06 '24 01:10 attilah

@mitchdenny for annotation

adamint avatar Oct 09 '24 17:10 adamint

@maddymontaquila have you heard similar feedback from other folks?

If we were to do this we'd probably want to figure out whether we see this as an exclusive membership thing (you can only be in one group) and whether it should be single level or hierarchical or whether its more of a tagging mechanism.

We'd probably want to see the visual design for this before we do anything in the app model since this would mostly just be a visual thing.

mitchdenny avatar Oct 10 '24 02:10 mitchdenny

Related #521

DamianEdwards avatar Nov 04 '24 04:11 DamianEdwards

No time in 9.3

JamesNK avatar Apr 29 '25 22:04 JamesNK

I created a little helper for myself that does this:

namespace Aspire.Hosting;

static class AspireHostingExtensions
{
    public static IResourceBuilder<Resource> AddGroup(this IDistributedApplicationBuilder builder, string name) =>
        builder.AddResource(new GroupResource(name))
            .WithInitialState(new()
            {
                State = new(KnownResourceStates.Running, KnownResourceStateStyles.Success),
                ResourceType = "Group",
                Properties = []
            });

    public static IResourceBuilder<T> InGroup<T>(this IResourceBuilder<T> builder, IResourceBuilder<IResource> group)
        where T : IResource
    {
        if (builder.Resource.TryGetAnnotationsOfType<ResourceSnapshotAnnotation>(out var annot))
        {
            foreach (var snapshot in annot)
            {
                snapshot.InitialSnapshot.GetType().GetProperty("Properties")?.SetValue(snapshot.InitialSnapshot,
                    snapshot.InitialSnapshot.Properties.Add(new("resource.parentName", "data")));
            }
        }
        else
        {
            builder.WithInitialState(new()
            {
                ResourceType = builder.Resource.GetType().Name ?? "Unknown",
                Properties =
                [
                    new("resource.parentName", group.Resource.Name),
                ]
            });
        }

        return builder;
    }


    class GroupResource(string name) : Resource(name)
    {
    }
}

This can then be used like this:

var dataGroup = builder.AddGroup("data");

var dbDir = Path.Combine(Directory.GetCurrentDirectory(), "..", "artifacts", "data");
var db = builder.AddSqlite("db", dbDir, "hybridisms.db")
    .InGroup(dataGroup);

var sqliteWeb = db.WithSqliteWeb(b => b.InGroup(dataGroup)); // this one is weird because of the way it was written

And it looks like this:

Image

mattleibow avatar May 16 '25 20:05 mattleibow

See #1575 , I think it is the exact same as this issue

lukedukeus avatar May 16 '25 21:05 lukedukeus

@mattleibow @lukedukeus what would you think of this API? Would this serve your needs? https://github.com/dotnet/aspire/pull/9425

adamint avatar May 20 '25 18:05 adamint

@adamint I like it, it works for me.

Here's a few ways I think it could be improved:

  • I think the API might be more flexible if it was child.WithParent(parent) rather than parent.AddChild(child). If you had to write this code the other way around, it wouldn't flow as well:
var databaseServer = builder.AddPostgres("Postgres")
                .WithDataVolume()
                .WithLifetime(ContainerLifetime.Persistent);

            databaseServer.WithPgAdmin(options =>
            {
                options.WithParent(databaseServer);
                options.WithLifetime(ContainerLifetime.Persistent);
            }, containerName: "PgAdmin");
  • Parent's logs should be a combination of it's childrens' logs, and its state should be a combination of its childrens' states.
  • Having a group node is good, but it should be possible to attach a resource to a parent that is not a group node. If I had a database server, multiple databases, and a db browser, I would arrange it like:
-Postgres
-- Database1
-- Database2
-- PgAdmin

rather than

-Group
-- Postgres
-- Database1
-- Database2
-- PgAdmin

lukedukeus avatar May 20 '25 18:05 lukedukeus

@adamint I like it, it works for me.

Here's a few ways I think it could be improved:

  • I think the API might be more flexible if it was child.WithParent(parent) rather than parent.AddChild(child). If you had to write this code the other way around, it wouldn't flow as well:
var databaseServer = builder.AddPostgres("Postgres")
                .WithDataVolume()
                .WithLifetime(ContainerLifetime.Persistent);

            databaseServer.WithPgAdmin(options =>
            {
                options.WithParent(databaseServer);
                options.WithLifetime(ContainerLifetime.Persistent);
            }, containerName: "PgAdmin");
  • Parent's logs should be a combination of it's childrens' logs, and its state should be a combination of its childrens' states.

The goal is more so to create an explicit "group" object separate from a resource at all, so I don't know if this approach would work.

  • Having a group node is good, but it should be possible to attach a resource to a parent that is not a group node. If I had a database server, multiple databases, and a db browser, I would arrange it like:
-Postgres
-- Database1
-- Database2
-- PgAdmin

rather than

-Group
-- Postgres
-- Database1
-- Database2
-- PgAdmin

To achieve that structure, you would arrange like this

var appBuilder = DistributedApplication.CreateBuilder();

// create new groups
var postgresGroup = appBuilder.CreateGroup("postgres");
postgresGroup.AddPostgres(...)
var childGroup = appBuilder.CreateGroup("postgres-dependents", postgresGroup);
childGroup.AddPgAdmin(...)
....
appBuilder.Build();

adamint avatar May 20 '25 20:05 adamint