From 8796737991d152cd79d4225709006da906f52ca1 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:50:47 +0530 Subject: [PATCH] feat: revamp application list with grouping support (#905) --- container_manager/service.go | 128 +--------- container_manager/types.go | 9 +- .../core/application.operations.go | 9 +- swiftwave_service/core/models.go | 6 +- ...617_add_extra_fields_in_app_group.down.sql | 2 + ...75617_add_extra_fields_in_app_group.up.sql | 2 + swiftwave_service/db/migrations/atlas.sum | 4 +- swiftwave_service/gqlgen.yml | 4 + .../graphql/application.resolvers.go | 49 +++- .../graphql/application_group.resolvers.go | 9 +- swiftwave_service/graphql/generated.go | 218 +++++++++++++++++- .../graphql/graphql_object_mapper.go | 2 + swiftwave_service/graphql/model/models_gen.go | 45 ++++ .../graphql/schema/application.graphqls | 9 +- .../graphql/schema/application_group.graphqls | 1 + swiftwave_service/graphql/stack.resolvers.go | 4 +- 16 files changed, 343 insertions(+), 158 deletions(-) create mode 100644 swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.down.sql create mode 100644 swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.up.sql diff --git a/container_manager/service.go b/container_manager/service.go index 67fb4cc0d0..33b3815e24 100644 --- a/container_manager/service.go +++ b/container_manager/service.go @@ -255,140 +255,24 @@ func (m Manager) SetServiceReplicaCount(serviceName string, replicas int) error } } -func (m Manager) RealtimeInfoRunningServices() (map[string]ServiceRealtimeInfo, error) { - // fetch all nodes and store in map > nodeID:nodeDetails - nodes, err := m.client.NodeList(m.ctx, types.NodeListOptions{}) - if err != nil { - return nil, errors.New("error getting node list") - } - nodeMap := make(map[string]swarm.Node) - for _, node := range nodes { - nodeMap[node.ID] = node - } - // fetch all services and store in map > serviceName:serviceDetails - services, err := m.client.ServiceList(m.ctx, types.ServiceListOptions{}) - if err != nil { - return nil, errors.New("error getting service list") - } - // create map of service name to service realtime info - serviceRealtimeInfoMap := make(map[string]ServiceRealtimeInfo) - // analyze each service - for _, service := range services { - runningCount := 0 - - // inspect service to get desired count - serviceData, _, err := m.client.ServiceInspectWithRaw(m.ctx, service.ID, types.ServiceInspectOptions{}) - if err != nil { - continue - } - // create service realtime info - serviceRealtimeInfo := ServiceRealtimeInfo{} - serviceRealtimeInfo.Name = serviceData.Spec.Name - serviceRealtimeInfo.PlacementInfos = []ServiceTaskPlacementInfo{} - // set desired count - if serviceData.Spec.Mode.Replicated != nil { - serviceRealtimeInfo.DesiredReplicas = int(*serviceData.Spec.Mode.Replicated.Replicas) - serviceRealtimeInfo.ReplicatedService = true - } else { - serviceRealtimeInfo.DesiredReplicas = -1 - serviceRealtimeInfo.ReplicatedService = false - } - - // query task list - tasks, err := m.client.TaskList(m.ctx, types.TaskListOptions{ - Filters: filters.NewArgs( - filters.Arg("service", serviceData.Spec.Name), - ), - }) - if err != nil { - continue - } - servicePlacementCountMap := make(map[string]int) // nodeID:count - // set placement infos > how many replicas are running in each node - for _, task := range tasks { - if task.Status.State == swarm.TaskStateRunning { - servicePlacementCountMap[task.NodeID]++ - } - } - for nodeID, count := range servicePlacementCountMap { - node := nodeMap[nodeID] - serviceRealtimeInfo.PlacementInfos = append(serviceRealtimeInfo.PlacementInfos, ServiceTaskPlacementInfo{ - NodeID: nodeID, - NodeName: node.Description.Hostname, - IsManagerNode: node.Spec.Role != swarm.NodeRoleManager, - RunningReplicas: count, - }) - runningCount += count - } - // set service realtime info in map - serviceRealtimeInfo.RunningReplicas = runningCount - serviceRealtimeInfoMap[serviceRealtimeInfo.Name] = serviceRealtimeInfo - } - return serviceRealtimeInfoMap, nil -} - -func (m Manager) RealtimeInfoService(serviceName string, ignoreNodeDetails bool) (ServiceRealtimeInfo, error) { - runningCount := 0 - serviceRealtimeInfo := ServiceRealtimeInfo{} - // fetch all nodes and store in map > nodeID:nodeDetails - nodeMap := make(map[string]swarm.Node) - if !ignoreNodeDetails { - nodes, err := m.client.NodeList(m.ctx, types.NodeListOptions{}) - if err != nil { - return serviceRealtimeInfo, errors.New("error getting node list") - } - for _, node := range nodes { - nodeMap[node.ID] = node - } - } - // inspect service to get desired count - serviceData, _, err := m.client.ServiceInspectWithRaw(m.ctx, serviceName, types.ServiceInspectOptions{}) - if err != nil { - return serviceRealtimeInfo, errors.New("error getting service") - } - // create service realtime info - serviceRealtimeInfo.Name = serviceData.Spec.Name - serviceRealtimeInfo.PlacementInfos = []ServiceTaskPlacementInfo{} - // set desired count - if serviceData.Spec.Mode.Replicated != nil { - serviceRealtimeInfo.DesiredReplicas = int(*serviceData.Spec.Mode.Replicated.Replicas) - serviceRealtimeInfo.ReplicatedService = true - } else { - serviceRealtimeInfo.DesiredReplicas = -1 - serviceRealtimeInfo.ReplicatedService = false - } - +func (m Manager) NoOfRunningTasks(serviceName string) (int, error) { // query task list tasks, err := m.client.TaskList(m.ctx, types.TaskListOptions{ Filters: filters.NewArgs( - filters.Arg("service", serviceData.Spec.Name), + filters.Arg("service", serviceName), ), }) if err != nil { - return serviceRealtimeInfo, err + return 0, err } - servicePlacementCountMap := make(map[string]int) // nodeID:count + runningCount := 0 // set placement infos > how many replicas are running in each node for _, task := range tasks { if task.Status.State == swarm.TaskStateRunning { - servicePlacementCountMap[task.NodeID]++ - } - } - for nodeID, count := range servicePlacementCountMap { - if !ignoreNodeDetails { - node := nodeMap[nodeID] - serviceRealtimeInfo.PlacementInfos = append(serviceRealtimeInfo.PlacementInfos, ServiceTaskPlacementInfo{ - NodeID: nodeID, - NodeName: node.Description.Hostname, - IsManagerNode: node.Spec.Role != swarm.NodeRoleManager, - RunningReplicas: count, - }) + runningCount++ } - runningCount += count } - // set service realtime info in map - serviceRealtimeInfo.RunningReplicas = runningCount - return serviceRealtimeInfo, nil + return runningCount, nil } // ServiceRunningServers Fetch the servers where a service is running diff --git a/container_manager/types.go b/container_manager/types.go index cd1fc26eaa..53d7676667 100644 --- a/container_manager/types.go +++ b/container_manager/types.go @@ -48,11 +48,10 @@ type CustomHealthCheck struct { } type ServiceRealtimeInfo struct { - Name string `json:"name"` - PlacementInfos []ServiceTaskPlacementInfo `json:"placementinfos"` - DesiredReplicas int `json:"desiredreplicas"` - RunningReplicas int `json:"runningreplicas"` - ReplicatedService bool `json:"replicatedservice"` + Name string `json:"name"` + DesiredReplicas int `json:"desiredreplicas"` + RunningReplicas int `json:"runningreplicas"` + ReplicatedService bool `json:"replicatedservice"` } type ServiceTaskPlacementInfo struct { diff --git a/swiftwave_service/core/application.operations.go b/swiftwave_service/core/application.operations.go index 6e787be60f..c54504775a 100644 --- a/swiftwave_service/core/application.operations.go +++ b/swiftwave_service/core/application.operations.go @@ -42,9 +42,14 @@ func IsExistApplicationName(_ context.Context, db gorm.DB, dockerManager contain return false, nil } -func FindAllApplications(_ context.Context, db gorm.DB) ([]*Application, error) { +func FindAllApplications(_ context.Context, db gorm.DB, includeGroupedApplications bool) ([]*Application, error) { var applications []*Application - tx := db.Find(&applications) + var tx *gorm.DB + if includeGroupedApplications { + tx = db.Find(&applications) + } else { + tx = db.Where("application_group_id IS NULL").Find(&applications) + } return applications, tx.Error } diff --git a/swiftwave_service/core/models.go b/swiftwave_service/core/models.go index c2f19680b8..bdcbe94821 100644 --- a/swiftwave_service/core/models.go +++ b/swiftwave_service/core/models.go @@ -211,10 +211,12 @@ type ConfigMount struct { FileMode uint `json:"file_mode" gorm:"default:444"` } -// ApplicationGroup hold information about application group +// ApplicationGroup hold information about application-group type ApplicationGroup struct { ID string `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"unique"` + Name string `json:"name"` + Logo string `json:"logo"` + StackContent string `json:"stack_content"` Applications []Application `json:"applications" gorm:"foreignKey:ApplicationGroupID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` } diff --git a/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.down.sql b/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.down.sql new file mode 100644 index 0000000000..579d2adb09 --- /dev/null +++ b/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.down.sql @@ -0,0 +1,2 @@ +-- reverse: modify "application_groups" table +ALTER TABLE "public"."application_groups" DROP COLUMN "stack_content", DROP COLUMN "logo", ADD CONSTRAINT "uni_application_groups_name" UNIQUE ("name"); diff --git a/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.up.sql b/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.up.sql new file mode 100644 index 0000000000..6661166772 --- /dev/null +++ b/swiftwave_service/db/migrations/20240628175617_add_extra_fields_in_app_group.up.sql @@ -0,0 +1,2 @@ +-- modify "application_groups" table +ALTER TABLE "public"."application_groups" DROP CONSTRAINT "uni_application_groups_name", ADD COLUMN "logo" text NULL, ADD COLUMN "stack_content" text NULL; diff --git a/swiftwave_service/db/migrations/atlas.sum b/swiftwave_service/db/migrations/atlas.sum index 9c75cbf3fc..a1a0bbcc32 100644 --- a/swiftwave_service/db/migrations/atlas.sum +++ b/swiftwave_service/db/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:hvxXPYu5/HahR/1ICKIZo/MYqsNXMabxSt0SdHKNf8Q= +h1:k1qJKQt8Sk2nl3wA2bU97eDfUN6gf5sarKNVQ0Ez5Lw= 20240413191732_init.down.sql h1:HoitObGwuKF/akF4qg3dol2FfNTLCEuf6wHYDuCez8I= 20240413191732_init.up.sql h1:USKdQx/yTz1KJ0+mDwYGhKm3WzX7k+I9+6B6SxImwaE= 20240414051823_server_custom_ssh_port_added.down.sql h1:IC1DFQBQceTPTRdZOo5/WqytH+ZbgcKrQuMCkhArF/0= @@ -43,3 +43,5 @@ h1:hvxXPYu5/HahR/1ICKIZo/MYqsNXMabxSt0SdHKNf8Q= 20240625161343_rename_app_status_live_to_deployed.up.sql h1:J5B2Il4UoFg9fZtj1tkCXIJoDHhCDYjj1XUA2088JRc= 20240626072036_move_application_group_to_relation.down.sql h1:oMF4rB5As6BFCMPAxbm7lXvWyyCb9uqmFOC/AcTsDY8= 20240626072036_move_application_group_to_relation.up.sql h1:SNdKqmYmIZeE0JcgKiLYFajW/awiz4rXRSAKVdabPZg= +20240628175617_add_extra_fields_in_app_group.down.sql h1:T/QBEilqWYysok6l2wxZAlYK1Z6ceSQLUmers5fZNig= +20240628175617_add_extra_fields_in_app_group.up.sql h1:+qBOQc/2bhG1igFdUbWSsZEy01aORuPyBPvxKVXJJoA= diff --git a/swiftwave_service/gqlgen.yml b/swiftwave_service/gqlgen.yml index 895282eaef..8dda0c5826 100644 --- a/swiftwave_service/gqlgen.yml +++ b/swiftwave_service/gqlgen.yml @@ -60,6 +60,10 @@ models: resolver: true applicationGroup: resolver: true + RealtimeInfo: + fields: + HealthStatus: + resolver: true ApplicationGroup: fields: applications: diff --git a/swiftwave_service/graphql/application.resolvers.go b/swiftwave_service/graphql/application.resolvers.go index c5004bdf23..d0c7a5b7d0 100644 --- a/swiftwave_service/graphql/application.resolvers.go +++ b/swiftwave_service/graphql/application.resolvers.go @@ -67,28 +67,27 @@ func (r *applicationResolver) RealtimeInfo(ctx context.Context, obj *model.Appli InfoFound: false, DesiredReplicas: 0, RunningReplicas: 0, + DeploymentMode: obj.DeploymentMode, }, nil } - info, err := dockerManager.RealtimeInfoService(obj.Name, true) + runningCount, err := dockerManager.NoOfRunningTasks(obj.Name) if err != nil { return &model.RealtimeInfo{ InfoFound: false, DesiredReplicas: 0, RunningReplicas: 0, - DeploymentMode: model.DeploymentModeGlobal, + DeploymentMode: obj.DeploymentMode, }, nil } - var deploymentMode model.DeploymentMode - if info.ReplicatedService { - deploymentMode = model.DeploymentModeReplicated - } else { - deploymentMode = model.DeploymentModeGlobal + var desiredReplicas = -1 + if obj.DeploymentMode == model.DeploymentModeReplicated { + desiredReplicas = int(obj.Replicas) } return &model.RealtimeInfo{ InfoFound: true, - DesiredReplicas: info.DesiredReplicas, - RunningReplicas: info.RunningReplicas, - DeploymentMode: deploymentMode, + DesiredReplicas: desiredReplicas, + RunningReplicas: runningCount, + DeploymentMode: obj.DeploymentMode, }, nil } @@ -423,9 +422,9 @@ func (r *queryResolver) Application(ctx context.Context, id string) (*model.Appl } // Applications is the resolver for the applications field. -func (r *queryResolver) Applications(ctx context.Context) ([]*model.Application, error) { +func (r *queryResolver) Applications(ctx context.Context, includeGroupedApplications bool) ([]*model.Application, error) { var records []*core.Application - records, err := core.FindAllApplications(ctx, r.ServiceManager.DbClient) + records, err := core.FindAllApplications(ctx, r.ServiceManager.DbClient, includeGroupedApplications) if err != nil { return nil, err } @@ -479,7 +478,33 @@ func (r *queryResolver) ApplicationResourceAnalytics(ctx context.Context, id str return result, nil } +// HealthStatus is the resolver for the HealthStatus field. +func (r *realtimeInfoResolver) HealthStatus(ctx context.Context, obj *model.RealtimeInfo) (model.HealthStatus, error) { + if obj == nil || !obj.InfoFound { + return model.HealthStatusUnknown, nil + } + if obj.DeploymentMode == model.DeploymentModeReplicated { + if obj.DesiredReplicas == obj.RunningReplicas { + return model.HealthStatusHealthy, nil + } else { + return model.HealthStatusUnhealthy, nil + } + } + if obj.DeploymentMode == model.DeploymentModeGlobal { + if obj.RunningReplicas > 0 { + return model.HealthStatusHealthy, nil + } else { + return model.HealthStatusUnhealthy, nil + } + } + return model.HealthStatusUnknown, nil +} + // Application returns ApplicationResolver implementation. func (r *Resolver) Application() ApplicationResolver { return &applicationResolver{r} } +// RealtimeInfo returns RealtimeInfoResolver implementation. +func (r *Resolver) RealtimeInfo() RealtimeInfoResolver { return &realtimeInfoResolver{r} } + type applicationResolver struct{ *Resolver } +type realtimeInfoResolver struct{ *Resolver } diff --git a/swiftwave_service/graphql/application_group.resolvers.go b/swiftwave_service/graphql/application_group.resolvers.go index 6153d40e44..9bd55830e6 100644 --- a/swiftwave_service/graphql/application_group.resolvers.go +++ b/swiftwave_service/graphql/application_group.resolvers.go @@ -6,8 +6,6 @@ package graphql import ( "context" - "fmt" - "github.com/swiftwave-org/swiftwave/swiftwave_service/core" "github.com/swiftwave-org/swiftwave/swiftwave_service/graphql/model" ) @@ -67,7 +65,12 @@ func (r *queryResolver) ApplicationGroups(ctx context.Context) ([]*model.Applica // ApplicationGroup is the resolver for the applicationGroup field. func (r *queryResolver) ApplicationGroup(ctx context.Context, id string) (*model.ApplicationGroup, error) { - panic(fmt.Errorf("not implemented: ApplicationGroup - applicationGroup")) + var record = &core.ApplicationGroup{} + err := record.FindById(ctx, r.ServiceManager.DbClient, id) + if err != nil { + return nil, err + } + return applicationGroupToGraphqlObject(record), nil } // ApplicationGroup returns ApplicationGroupResolver implementation. diff --git a/swiftwave_service/graphql/generated.go b/swiftwave_service/graphql/generated.go index 5198fd0238..0d9c418a09 100644 --- a/swiftwave_service/graphql/generated.go +++ b/swiftwave_service/graphql/generated.go @@ -53,6 +53,7 @@ type ResolverRoot interface { PersistentVolume() PersistentVolumeResolver PersistentVolumeBinding() PersistentVolumeBindingResolver Query() QueryResolver + RealtimeInfo() RealtimeInfoResolver RedirectRule() RedirectRuleResolver Server() ServerResolver Subscription() SubscriptionResolver @@ -121,6 +122,7 @@ type ComplexityRoot struct { ApplicationGroup struct { Applications func(childComplexity int) int ID func(childComplexity int) int + Logo func(childComplexity int) int Name func(childComplexity int) int } @@ -430,7 +432,7 @@ type ComplexityRoot struct { ApplicationGroup func(childComplexity int, id string) int ApplicationGroups func(childComplexity int) int ApplicationResourceAnalytics func(childComplexity int, id string, timeframe model.ApplicationResourceAnalyticsTimeframe) int - Applications func(childComplexity int) int + Applications func(childComplexity int, includeGroupedApplications bool) int AvailableDockerConfigs func(childComplexity int) int CheckGitCredentialRepositoryAccess func(childComplexity int, input model.GitCredentialRepositoryAccessInput) int CurrentUser func(childComplexity int) int @@ -474,6 +476,7 @@ type ComplexityRoot struct { RealtimeInfo struct { DeploymentMode func(childComplexity int) int DesiredReplicas func(childComplexity int) int + HealthStatus func(childComplexity int) int InfoFound func(childComplexity int) int RunningReplicas func(childComplexity int) int } @@ -710,7 +713,7 @@ type PersistentVolumeBindingResolver interface { type QueryResolver interface { AppBasicAuthAccessControlLists(ctx context.Context) ([]*model.AppBasicAuthAccessControlList, error) Application(ctx context.Context, id string) (*model.Application, error) - Applications(ctx context.Context) ([]*model.Application, error) + Applications(ctx context.Context, includeGroupedApplications bool) ([]*model.Application, error) IsExistApplicationName(ctx context.Context, name string) (bool, error) ApplicationResourceAnalytics(ctx context.Context, id string, timeframe model.ApplicationResourceAnalyticsTimeframe) ([]*model.ApplicationResourceAnalytics, error) ApplicationGroups(ctx context.Context) ([]*model.ApplicationGroup, error) @@ -753,6 +756,9 @@ type QueryResolver interface { User(ctx context.Context, id uint) (*model.User, error) CurrentUser(ctx context.Context) (*model.User, error) } +type RealtimeInfoResolver interface { + HealthStatus(ctx context.Context, obj *model.RealtimeInfo) (model.HealthStatus, error) +} type RedirectRuleResolver interface { Domain(ctx context.Context, obj *model.RedirectRule) (*model.Domain, error) } @@ -1086,6 +1092,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ApplicationGroup.ID(childComplexity), true + case "ApplicationGroup.logo": + if e.complexity.ApplicationGroup.Logo == nil { + break + } + + return e.complexity.ApplicationGroup.Logo(childComplexity), true + case "ApplicationGroup.name": if e.complexity.ApplicationGroup.Name == nil { break @@ -3084,7 +3097,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in break } - return e.complexity.Query.Applications(childComplexity), true + args, err := ec.field_Query_applications_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Applications(childComplexity, args["includeGroupedApplications"].(bool)), true case "Query.availableDockerConfigs": if e.complexity.Query.AvailableDockerConfigs == nil { @@ -3486,6 +3504,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RealtimeInfo.DesiredReplicas(childComplexity), true + case "RealtimeInfo.HealthStatus": + if e.complexity.RealtimeInfo.HealthStatus == nil { + break + } + + return e.complexity.RealtimeInfo.HealthStatus(childComplexity), true + case "RealtimeInfo.InfoFound": if e.complexity.RealtimeInfo.InfoFound == nil { break @@ -5338,6 +5363,21 @@ func (ec *executionContext) field_Query_application_args(ctx context.Context, ra return args, nil } +func (ec *executionContext) field_Query_applications_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 bool + if tmp, ok := rawArgs["includeGroupedApplications"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeGroupedApplications")) + arg0, err = ec.unmarshalNBoolean2bool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includeGroupedApplications"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_checkGitCredentialRepositoryAccess_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -6535,6 +6575,8 @@ func (ec *executionContext) fieldContext_Application_realtimeInfo(_ context.Cont return ec.fieldContext_RealtimeInfo_RunningReplicas(ctx, field) case "DeploymentMode": return ec.fieldContext_RealtimeInfo_DeploymentMode(ctx, field) + case "HealthStatus": + return ec.fieldContext_RealtimeInfo_HealthStatus(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type RealtimeInfo", field.Name) }, @@ -7159,6 +7201,8 @@ func (ec *executionContext) fieldContext_Application_applicationGroup(_ context. return ec.fieldContext_ApplicationGroup_id(ctx, field) case "name": return ec.fieldContext_ApplicationGroup_name(ctx, field) + case "logo": + return ec.fieldContext_ApplicationGroup_logo(ctx, field) case "applications": return ec.fieldContext_ApplicationGroup_applications(ctx, field) } @@ -7943,6 +7987,50 @@ func (ec *executionContext) fieldContext_ApplicationGroup_name(_ context.Context return fc, nil } +func (ec *executionContext) _ApplicationGroup_logo(ctx context.Context, field graphql.CollectedField, obj *model.ApplicationGroup) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ApplicationGroup_logo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Logo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ApplicationGroup_logo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ApplicationGroup", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _ApplicationGroup_applications(ctx context.Context, field graphql.CollectedField, obj *model.ApplicationGroup) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ApplicationGroup_applications(ctx, field) if err != nil { @@ -14916,6 +15004,8 @@ func (ec *executionContext) fieldContext_Mutation_createApplicationGroup(ctx con return ec.fieldContext_ApplicationGroup_id(ctx, field) case "name": return ec.fieldContext_ApplicationGroup_name(ctx, field) + case "logo": + return ec.fieldContext_ApplicationGroup_logo(ctx, field) case "applications": return ec.fieldContext_ApplicationGroup_applications(ctx, field) } @@ -19884,7 +19974,7 @@ func (ec *executionContext) _Query_applications(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Applications(rctx) + return ec.resolvers.Query().Applications(rctx, fc.Args["includeGroupedApplications"].(bool)) }) if err != nil { ec.Error(ctx, err) @@ -19901,7 +19991,7 @@ func (ec *executionContext) _Query_applications(ctx context.Context, field graph return ec.marshalNApplication2ᚕᚖgithubᚗcomᚋswiftwaveᚑorgᚋswiftwaveᚋswiftwave_serviceᚋgraphqlᚋmodelᚐApplicationᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_applications(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_applications(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -19963,6 +20053,17 @@ func (ec *executionContext) fieldContext_Query_applications(_ context.Context, f return nil, fmt.Errorf("no field named %q was found under type Application", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_applications_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -20141,6 +20242,8 @@ func (ec *executionContext) fieldContext_Query_applicationGroups(_ context.Conte return ec.fieldContext_ApplicationGroup_id(ctx, field) case "name": return ec.fieldContext_ApplicationGroup_name(ctx, field) + case "logo": + return ec.fieldContext_ApplicationGroup_logo(ctx, field) case "applications": return ec.fieldContext_ApplicationGroup_applications(ctx, field) } @@ -20193,6 +20296,8 @@ func (ec *executionContext) fieldContext_Query_applicationGroup(ctx context.Cont return ec.fieldContext_ApplicationGroup_id(ctx, field) case "name": return ec.fieldContext_ApplicationGroup_name(ctx, field) + case "logo": + return ec.fieldContext_ApplicationGroup_logo(ctx, field) case "applications": return ec.fieldContext_ApplicationGroup_applications(ctx, field) } @@ -22834,6 +22939,50 @@ func (ec *executionContext) fieldContext_RealtimeInfo_DeploymentMode(_ context.C return fc, nil } +func (ec *executionContext) _RealtimeInfo_HealthStatus(ctx context.Context, field graphql.CollectedField, obj *model.RealtimeInfo) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_RealtimeInfo_HealthStatus(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.RealtimeInfo().HealthStatus(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(model.HealthStatus) + fc.Result = res + return ec.marshalNHealthStatus2githubᚗcomᚋswiftwaveᚑorgᚋswiftwaveᚋswiftwave_serviceᚋgraphqlᚋmodelᚐHealthStatus(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_RealtimeInfo_HealthStatus(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "RealtimeInfo", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type HealthStatus does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _RedirectRule_id(ctx context.Context, field graphql.CollectedField, obj *model.RedirectRule) (ret graphql.Marshaler) { fc, err := ec.fieldContext_RedirectRule_id(ctx, field) if err != nil { @@ -29803,6 +29952,11 @@ func (ec *executionContext) _ApplicationGroup(ctx context.Context, sel ast.Selec if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "logo": + out.Values[i] = ec._ApplicationGroup_logo(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "applications": field := field @@ -33445,23 +33599,59 @@ func (ec *executionContext) _RealtimeInfo(ctx context.Context, sel ast.Selection case "InfoFound": out.Values[i] = ec._RealtimeInfo_InfoFound(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "DesiredReplicas": out.Values[i] = ec._RealtimeInfo_DesiredReplicas(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "RunningReplicas": out.Values[i] = ec._RealtimeInfo_RunningReplicas(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "DeploymentMode": out.Values[i] = ec._RealtimeInfo_DeploymentMode(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "HealthStatus": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._RealtimeInfo_HealthStatus(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -35648,6 +35838,16 @@ func (ec *executionContext) marshalNGitType2githubᚗcomᚋswiftwaveᚑorgᚋswi return v } +func (ec *executionContext) unmarshalNHealthStatus2githubᚗcomᚋswiftwaveᚑorgᚋswiftwaveᚋswiftwave_serviceᚋgraphqlᚋmodelᚐHealthStatus(ctx context.Context, v interface{}) (model.HealthStatus, error) { + var res model.HealthStatus + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNHealthStatus2githubᚗcomᚋswiftwaveᚑorgᚋswiftwaveᚋswiftwave_serviceᚋgraphqlᚋmodelᚐHealthStatus(ctx context.Context, sel ast.SelectionSet, v model.HealthStatus) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNImageRegistryCredential2githubᚗcomᚋswiftwaveᚑorgᚋswiftwaveᚋswiftwave_serviceᚋgraphqlᚋmodelᚐImageRegistryCredential(ctx context.Context, sel ast.SelectionSet, v model.ImageRegistryCredential) graphql.Marshaler { return ec._ImageRegistryCredential(ctx, sel, &v) } diff --git a/swiftwave_service/graphql/graphql_object_mapper.go b/swiftwave_service/graphql/graphql_object_mapper.go index 30b15cd3ec..599e4010f5 100644 --- a/swiftwave_service/graphql/graphql_object_mapper.go +++ b/swiftwave_service/graphql/graphql_object_mapper.go @@ -340,6 +340,7 @@ func applicationGroupInputToDatabaseObject(record *model.ApplicationGroupInput) return &core.ApplicationGroup{ ID: uuid.UUIDv4(), Name: record.Name, + Logo: "", } } @@ -348,6 +349,7 @@ func applicationGroupToGraphqlObject(record *core.ApplicationGroup) *model.Appli return &model.ApplicationGroup{ ID: record.ID, Name: record.Name, + Logo: record.Logo, } } diff --git a/swiftwave_service/graphql/model/models_gen.go b/swiftwave_service/graphql/model/models_gen.go index b1976cf667..ab0c3ba3cd 100644 --- a/swiftwave_service/graphql/model/models_gen.go +++ b/swiftwave_service/graphql/model/models_gen.go @@ -88,6 +88,7 @@ type ApplicationDeployResult struct { type ApplicationGroup struct { ID string `json:"id"` Name string `json:"name"` + Logo string `json:"logo"` Applications []*Application `json:"applications"` } @@ -518,6 +519,7 @@ type RealtimeInfo struct { DesiredReplicas int `json:"DesiredReplicas"` RunningReplicas int `json:"RunningReplicas"` DeploymentMode DeploymentMode `json:"DeploymentMode"` + HealthStatus HealthStatus `json:"HealthStatus"` } type RedirectRule struct { @@ -970,6 +972,49 @@ func (e GitType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } +type HealthStatus string + +const ( + HealthStatusHealthy HealthStatus = "healthy" + HealthStatusUnhealthy HealthStatus = "unhealthy" + HealthStatusUnknown HealthStatus = "unknown" +) + +var AllHealthStatus = []HealthStatus{ + HealthStatusHealthy, + HealthStatusUnhealthy, + HealthStatusUnknown, +} + +func (e HealthStatus) IsValid() bool { + switch e { + case HealthStatusHealthy, HealthStatusUnhealthy, HealthStatusUnknown: + return true + } + return false +} + +func (e HealthStatus) String() string { + return string(e) +} + +func (e *HealthStatus) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = HealthStatus(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid HealthStatus", str) + } + return nil +} + +func (e HealthStatus) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + type IngressRuleAuthenticationType string const ( diff --git a/swiftwave_service/graphql/schema/application.graphqls b/swiftwave_service/graphql/schema/application.graphqls index afb4c9682b..12a4a40269 100644 --- a/swiftwave_service/graphql/schema/application.graphqls +++ b/swiftwave_service/graphql/schema/application.graphqls @@ -13,11 +13,18 @@ enum ApplicationResourceAnalyticsTimeframe { last_30_days } +enum HealthStatus { + healthy + unhealthy + unknown +} + type RealtimeInfo { InfoFound: Boolean! DesiredReplicas: Int! RunningReplicas: Int! DeploymentMode: DeploymentMode! + HealthStatus: HealthStatus! } type ResourceLimit { @@ -110,7 +117,7 @@ input ApplicationInput { extend type Query { application(id: String!): Application! - applications: [Application!]! + applications(includeGroupedApplications: Boolean!): [Application!]! isExistApplicationName(name: String!): Boolean! applicationResourceAnalytics(id: String!, timeframe: ApplicationResourceAnalyticsTimeframe!): [ApplicationResourceAnalytics!]! } diff --git a/swiftwave_service/graphql/schema/application_group.graphqls b/swiftwave_service/graphql/schema/application_group.graphqls index 73feecc73e..e630cba21f 100644 --- a/swiftwave_service/graphql/schema/application_group.graphqls +++ b/swiftwave_service/graphql/schema/application_group.graphqls @@ -1,6 +1,7 @@ type ApplicationGroup { id: String! name: String! + logo: String! applications: [Application!]! } diff --git a/swiftwave_service/graphql/stack.resolvers.go b/swiftwave_service/graphql/stack.resolvers.go index 33344ef060..d37e83fbda 100644 --- a/swiftwave_service/graphql/stack.resolvers.go +++ b/swiftwave_service/graphql/stack.resolvers.go @@ -190,7 +190,9 @@ func (r *mutationResolver) DeployStack(ctx context.Context, input model.StackInp var applicationGroupID *string if len(stackFilled.ServiceNames()) > 1 { applicationGroup := &core.ApplicationGroup{ - Name: stackName, + Name: stackName, + Logo: stackFilled.Docs.LogoURL, + StackContent: input.Content, } err = applicationGroup.Create(ctx, r.ServiceManager.DbClient) if err != nil {