diff --git a/models/registration/register.go b/models/registration/register.go
index fce9df44..55cddd55 100644
--- a/models/registration/register.go
+++ b/models/registration/register.go
@@ -139,6 +139,30 @@ func (rh *RegistrationHelper) register(pkg PackagingUnit) {
for _, rel := range pkg.Relationships {
rel.Model = model.ToReference()
rel.ModelId = model.Id
+
+ if rel.Metadata != nil && rel.Metadata.Styles != nil {
+ svgComplete := ""
+ if rel.Metadata.Styles.SvgComplete != nil {
+ svgComplete = *rel.Metadata.Styles.SvgComplete
+ }
+
+ var svgCompletePath string
+
+ // Write SVG for relationships
+ rel.Metadata.Styles.SvgColor, rel.Metadata.Styles.SvgWhite, svgCompletePath = WriteAndReplaceSVGWithFileSystemPath(
+ rel.Metadata.Styles.SvgColor,
+ rel.Metadata.Styles.SvgWhite,
+ svgComplete,
+ rh.svgBaseDir,
+ model.Name,
+ string(rel.Kind),
+ false,
+ )
+ if svgCompletePath != "" {
+ rel.Metadata.Styles.SvgComplete = &svgCompletePath
+ }
+ }
+
_, _, err := rh.regManager.RegisterEntity(model.Registrant, &rel)
if err != nil {
err = ErrRegisterEntity(err, string(rel.Type()), string(rel.Kind))
diff --git a/registry/component.go b/registry/component.go
index fa9245ea..c7ac475e 100644
--- a/registry/component.go
+++ b/registry/component.go
@@ -1,12 +1,12 @@
package registry
import (
+ "encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
- "encoding/json"
"github.com/meshery/meshkit/encoding"
"github.com/meshery/meshkit/files"
@@ -14,10 +14,10 @@ import (
"github.com/meshery/meshkit/utils"
"github.com/meshery/meshkit/utils/csv"
"github.com/meshery/meshkit/utils/manifests"
+ "github.com/meshery/schemas"
"github.com/meshery/schemas/models/v1alpha1/capability"
schmeaVersion "github.com/meshery/schemas/models/v1beta1"
"github.com/meshery/schemas/models/v1beta1/component"
- "github.com/meshery/schemas"
)
const (
@@ -209,8 +209,6 @@ func (c *ComponentCSV) UpdateCompDefinition(compDef *component.ComponentDefiniti
return nil
}
-
-
type ComponentCSVHelper struct {
SpreadsheetID int64
SpreadsheetURL string
@@ -377,6 +375,84 @@ func CreateRelationshipsMetadata(model ModelCSV, relationships []RelationshipCSV
}
+// CreateRelationshipsMetadataAndCreateSVGsForMDStyle creates relationship metadata and writes SVGs for MD style docs
+func CreateRelationshipsMetadataAndCreateSVGsForMDStyle(model ModelCSV, relationships []RelationshipCSV, path, svgDir string) (string, error) {
+ err := os.MkdirAll(filepath.Join(path), 0777)
+ if err != nil {
+ return "", err
+ }
+ relationshipMetadata := ""
+ for _, relnship := range relationships {
+ relationshipTemplate := `
+- type: %s
+ kind: %s
+ colorIcon: %s
+ whiteIcon: %s
+ description: %s`
+
+ relnshipName := utils.FormatName(manifests.FormatToReadableString(fmt.Sprintf("%s-%s", relnship.KIND, relnship.SubType)))
+ colorIconDir := filepath.Join(svgDir, relnshipName, "icons", "color")
+ whiteIconDir := filepath.Join(svgDir, relnshipName, "icons", "white")
+
+ relationshipMetadata += fmt.Sprintf(relationshipTemplate, relnship.Type, relnship.KIND, fmt.Sprintf("%s/%s-color.svg", colorIconDir, relnshipName), fmt.Sprintf("%s/%s-white.svg", whiteIconDir, relnshipName), relnship.Description)
+
+ // Get SVGs for relationship
+ colorSVG, whiteSVG := getSVGForRelationship(model, relnship)
+
+ // Only create directories and write SVGs if they exist
+ if colorSVG != "" {
+ // create color svg dir
+ err = os.MkdirAll(filepath.Join(path, relnshipName, "icons", "color"), 0777)
+ if err != nil {
+ return "", err
+ }
+ err = utils.WriteToFile(filepath.Join(path, relnshipName, "icons", "color", relnshipName+"-color.svg"), colorSVG)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ if whiteSVG != "" {
+ // create white svg dir
+ err = os.MkdirAll(filepath.Join(path, relnshipName, "icons", "white"), 0777)
+ if err != nil {
+ return "", err
+ }
+ err = utils.WriteToFile(filepath.Join(path, relnshipName, "icons", "white", relnshipName+"-white.svg"), whiteSVG)
+ if err != nil {
+ return "", err
+ }
+ }
+ }
+
+ return relationshipMetadata, nil
+}
+
+// getSVGForRelationship extracts SVG data from a relationship's styles, falling back to model SVGs if not present
+func getSVGForRelationship(model ModelCSV, relationship RelationshipCSV) (colorSVG string, whiteSVG string) {
+ // Try to extract SVGs from the relationship's Styles JSON
+ if relationship.Styles != "" {
+ var styles struct {
+ SvgColor string `json:"svgColor"`
+ SvgWhite string `json:"svgWhite"`
+ }
+ if err := encoding.Unmarshal([]byte(relationship.Styles), &styles); err == nil {
+ colorSVG = styles.SvgColor
+ whiteSVG = styles.SvgWhite
+ }
+ }
+
+ // Fall back to model SVGs if relationship doesn't have its own
+ if colorSVG == "" {
+ colorSVG = model.SVGColor
+ }
+
+ if whiteSVG == "" {
+ whiteSVG = model.SVGWhite
+ }
+ return
+}
+
func CreateComponentsMetadataAndCreateSVGsForMDStyle(model ModelCSV, components []ComponentCSV, path, svgDir string) (string, error) {
err := os.MkdirAll(filepath.Join(path), 0777)
if err != nil {
@@ -464,43 +540,42 @@ func getSVGForComponent(model ModelCSV, component ComponentCSV) (colorSVG string
return
}
-
func getMinimalUICapabilitiesFromSchema() ([]capability.Capability, error) {
- schema, err := schemas.Schemas.ReadFile("schemas/constructs/v1beta1/component/component.json")
- if err != nil {
- return nil, fmt.Errorf("failed to read component schema: %v", err)
- }
-
- capabilitiesJSON, err := extractCapabilitiesJSONFromSchema(schema)
- if err != nil {
- return nil, fmt.Errorf("failed to extract capabilities from schema: %v", err)
- }
-
- var allCapabilities []capability.Capability
- if err := json.Unmarshal(capabilitiesJSON, &allCapabilities); err != nil {
- return nil, fmt.Errorf("failed to unmarshal capabilities: %v", err)
- }
-
- if len(allCapabilities) >= 3 {
- return allCapabilities[len(allCapabilities)-3:], nil
- }
-
- return nil, fmt.Errorf("insufficient default capabilities in schema, found %d", len(allCapabilities))
+ schema, err := schemas.Schemas.ReadFile("schemas/constructs/v1beta1/component/component.json")
+ if err != nil {
+ return nil, fmt.Errorf("failed to read component schema: %v", err)
+ }
+
+ capabilitiesJSON, err := extractCapabilitiesJSONFromSchema(schema)
+ if err != nil {
+ return nil, fmt.Errorf("failed to extract capabilities from schema: %v", err)
+ }
+
+ var allCapabilities []capability.Capability
+ if err := json.Unmarshal(capabilitiesJSON, &allCapabilities); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal capabilities: %v", err)
+ }
+
+ if len(allCapabilities) >= 3 {
+ return allCapabilities[len(allCapabilities)-3:], nil
+ }
+
+ return nil, fmt.Errorf("insufficient default capabilities in schema, found %d", len(allCapabilities))
}
func extractCapabilitiesJSONFromSchema(schema []byte) ([]byte, error) {
- var schemaMap map[string]interface{}
- if err := json.Unmarshal(schema, &schemaMap); err != nil {
- return nil, err
- }
-
- if properties, ok := schemaMap["properties"].(map[string]interface{}); ok {
- if capabilitiesSchema, ok := properties["capabilities"].(map[string]interface{}); ok {
- if defaultValue, ok := capabilitiesSchema["default"]; ok {
- return json.Marshal(defaultValue)
- }
- }
- }
-
- return nil, fmt.Errorf("default capabilities not found in schema")
-}
\ No newline at end of file
+ var schemaMap map[string]interface{}
+ if err := json.Unmarshal(schema, &schemaMap); err != nil {
+ return nil, err
+ }
+
+ if properties, ok := schemaMap["properties"].(map[string]interface{}); ok {
+ if capabilitiesSchema, ok := properties["capabilities"].(map[string]interface{}); ok {
+ if defaultValue, ok := capabilitiesSchema["default"]; ok {
+ return json.Marshal(defaultValue)
+ }
+ }
+ }
+
+ return nil, fmt.Errorf("default capabilities not found in schema")
+}
diff --git a/registry/component_test.go b/registry/component_test.go
index 09fb16a4..5ee97ca5 100644
--- a/registry/component_test.go
+++ b/registry/component_test.go
@@ -1,85 +1,214 @@
package registry
import (
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/meshery/schemas/models/v1beta1/component"
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/meshery/meshkit/utils"
+ "github.com/meshery/meshkit/utils/manifests"
+ "github.com/meshery/schemas/models/v1beta1/component"
+ "github.com/stretchr/testify/assert"
)
func TestUpdateCompDefinitionWithDefaultCapabilities(t *testing.T) {
- tests := []struct {
- name string
- csvCapabilities string
- expectedCapabilitiesLen int
- shouldHaveDefaultCaps bool
- }{
- {
- name: "Empty capabilities should get defaults",
- csvCapabilities: "",
- expectedCapabilitiesLen: 3,
- shouldHaveDefaultCaps: true,
- },
- {
- name: "Null capabilities should get defaults",
- csvCapabilities: "null",
- expectedCapabilitiesLen: 3,
- shouldHaveDefaultCaps: true,
- },
- {
- name: "Existing capabilities should be preserved",
- csvCapabilities: `[{"displayName":"Custom Cap","kind":"test"}]`,
- expectedCapabilitiesLen: 1,
- shouldHaveDefaultCaps: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- comp := ComponentCSV{
- Component: "TestComponent",
- Capabilities: tt.csvCapabilities,
- Registrant: "meshery",
- Model: "test-model",
- }
-
- compDef := &component.ComponentDefinition{}
-
- err := comp.UpdateCompDefinition(compDef)
-
- assert.NoError(t, err)
- assert.NotNil(t, compDef.Capabilities)
- assert.Len(t, *compDef.Capabilities, tt.expectedCapabilitiesLen)
-
- if tt.shouldHaveDefaultCaps {
- capabilities := *compDef.Capabilities
-
- expectedNames := []string{"Styling", "Change Shape", "Compound Drag And Drop"}
- actualNames := make([]string, len(capabilities))
- for i, cap := range capabilities {
- actualNames[i] = cap.DisplayName
- }
-
- assert.ElementsMatch(t, expectedNames, actualNames)
-
- assert.Equal(t, "Styling", capabilities[0].DisplayName)
- assert.Equal(t, "mutate", capabilities[0].Kind)
- assert.Equal(t, "style", capabilities[0].Type)
- }
- })
- }
+ tests := []struct {
+ name string
+ csvCapabilities string
+ expectedCapabilitiesLen int
+ shouldHaveDefaultCaps bool
+ }{
+ {
+ name: "Empty capabilities should get defaults",
+ csvCapabilities: "",
+ expectedCapabilitiesLen: 3,
+ shouldHaveDefaultCaps: true,
+ },
+ {
+ name: "Null capabilities should get defaults",
+ csvCapabilities: "null",
+ expectedCapabilitiesLen: 3,
+ shouldHaveDefaultCaps: true,
+ },
+ {
+ name: "Existing capabilities should be preserved",
+ csvCapabilities: `[{"displayName":"Custom Cap","kind":"test"}]`,
+ expectedCapabilitiesLen: 1,
+ shouldHaveDefaultCaps: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ comp := ComponentCSV{
+ Component: "TestComponent",
+ Capabilities: tt.csvCapabilities,
+ Registrant: "meshery",
+ Model: "test-model",
+ }
+
+ compDef := &component.ComponentDefinition{}
+
+ err := comp.UpdateCompDefinition(compDef)
+
+ assert.NoError(t, err)
+ assert.NotNil(t, compDef.Capabilities)
+ assert.Len(t, *compDef.Capabilities, tt.expectedCapabilitiesLen)
+
+ if tt.shouldHaveDefaultCaps {
+ capabilities := *compDef.Capabilities
+
+ expectedNames := []string{"Styling", "Change Shape", "Compound Drag And Drop"}
+ actualNames := make([]string, len(capabilities))
+ for i, cap := range capabilities {
+ actualNames[i] = cap.DisplayName
+ }
+
+ assert.ElementsMatch(t, expectedNames, actualNames)
+
+ assert.Equal(t, "Styling", capabilities[0].DisplayName)
+ assert.Equal(t, "mutate", capabilities[0].Kind)
+ assert.Equal(t, "style", capabilities[0].Type)
+ }
+ })
+ }
}
func TestGetMinimalUICapabilitiesFromSchema(t *testing.T) {
- capabilities, err := getMinimalUICapabilitiesFromSchema()
-
- assert.NoError(t, err)
- assert.Len(t, capabilities, 3)
-
- expectedNames := []string{"Styling", "Change Shape", "Compound Drag And Drop"}
- actualNames := make([]string, len(capabilities))
- for i, cap := range capabilities {
- actualNames[i] = cap.DisplayName
- }
-
- assert.ElementsMatch(t, expectedNames, actualNames)
-}
\ No newline at end of file
+ capabilities, err := getMinimalUICapabilitiesFromSchema()
+
+ assert.NoError(t, err)
+ assert.Len(t, capabilities, 3)
+
+ expectedNames := []string{"Styling", "Change Shape", "Compound Drag And Drop"}
+ actualNames := make([]string, len(capabilities))
+ for i, cap := range capabilities {
+ actualNames[i] = cap.DisplayName
+ }
+
+ assert.ElementsMatch(t, expectedNames, actualNames)
+}
+
+func TestGetSVGForRelationship(t *testing.T) {
+ tests := []struct {
+ name string
+ model ModelCSV
+ relationship RelationshipCSV
+ expectedColorSVG string
+ expectedWhiteSVG string
+ }{
+ {
+ name: "Relationship with its own SVGs",
+ model: ModelCSV{
+ SVGColor: "",
+ SVGWhite: "",
+ },
+ relationship: RelationshipCSV{
+ KIND: "edge",
+ SubType: "binding",
+ Styles: `{"svgColor": "", "svgWhite": ""}`,
+ },
+ expectedColorSVG: "",
+ expectedWhiteSVG: "",
+ },
+ {
+ name: "Relationship falls back to model SVGs",
+ model: ModelCSV{
+ SVGColor: "",
+ SVGWhite: "",
+ },
+ relationship: RelationshipCSV{
+ KIND: "edge",
+ SubType: "binding",
+ Styles: `{}`,
+ },
+ expectedColorSVG: "",
+ expectedWhiteSVG: "",
+ },
+ {
+ name: "Relationship with no styles uses model SVGs",
+ model: ModelCSV{
+ SVGColor: "",
+ SVGWhite: "",
+ },
+ relationship: RelationshipCSV{
+ KIND: "edge",
+ SubType: "binding",
+ Styles: "",
+ },
+ expectedColorSVG: "",
+ expectedWhiteSVG: "",
+ },
+ {
+ name: "Relationship with partial SVGs",
+ model: ModelCSV{
+ SVGColor: "",
+ SVGWhite: "",
+ },
+ relationship: RelationshipCSV{
+ KIND: "edge",
+ SubType: "binding",
+ Styles: `{"svgColor": ""}`,
+ },
+ expectedColorSVG: "",
+ expectedWhiteSVG: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ colorSVG, whiteSVG := getSVGForRelationship(tt.model, tt.relationship)
+ assert.Equal(t, tt.expectedColorSVG, colorSVG)
+ assert.Equal(t, tt.expectedWhiteSVG, whiteSVG)
+ })
+ }
+}
+
+func TestCreateRelationshipsMetadataAndCreateSVGsForMDStyle(t *testing.T) {
+ // Create a temporary directory for the test
+ tmpDir, err := os.MkdirTemp("", "relationship-svg-test-md")
+ assert.NoError(t, err)
+ defer os.RemoveAll(tmpDir)
+
+ model := ModelCSV{
+ SVGColor: "",
+ SVGWhite: "",
+ }
+
+ relationships := []RelationshipCSV{
+ {
+ KIND: "edge",
+ SubType: "binding",
+ Type: "hierarchical",
+ Description: "Test relationship",
+ Styles: `{"svgColor": "", "svgWhite": ""}`,
+ },
+ }
+
+ svgDir := "icons"
+ metadata, err := CreateRelationshipsMetadataAndCreateSVGsForMDStyle(model, relationships, tmpDir, svgDir)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, metadata)
+
+ // Verify metadata structure
+ assert.Contains(t, metadata, "edge")
+ assert.Contains(t, metadata, "hierarchical")
+ assert.Contains(t, metadata, "Test relationship")
+
+ // Verify SVG files were created - derive name the same way as implementation
+ rel := relationships[0]
+ relnshipName := utils.FormatName(manifests.FormatToReadableString(fmt.Sprintf("%s-%s", rel.KIND, rel.SubType)))
+ colorSVGPath := filepath.Join(tmpDir, relnshipName, "icons", "color", relnshipName+"-color.svg")
+ whiteSVGPath := filepath.Join(tmpDir, relnshipName, "icons", "white", relnshipName+"-white.svg")
+
+ // Check color SVG exists and has correct content
+ colorContent, err := os.ReadFile(colorSVGPath)
+ assert.NoError(t, err)
+ assert.Equal(t, "", string(colorContent))
+
+ // Check white SVG exists and has correct content
+ whiteContent, err := os.ReadFile(whiteSVGPath)
+ assert.NoError(t, err)
+ assert.Equal(t, "", string(whiteContent))
+}