Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/component-type-category.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Feature
body: Add `Category` to `ComponentTypeInput` and `ComponentType`, so component types can be filed under a catalog category (for example `infrastructure`) on create, update and read
time: 2026-09-16T07:00:00.000000000-05:00
61 changes: 59 additions & 2 deletions component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestComponentTypeCreate(t *testing.T) {
input := autopilot.Register[ol.ComponentTypeInput]("component_type_create_input",
ol.ComponentTypeInput{
Alias: ol.RefOf("example"),
Category: ol.RefOf("infrastructure"),
Name: ol.RefOf("Example"),
Description: ol.RefOf("Example Description"),
Properties: &[]ol.ComponentTypePropertyDefinitionInput{},
Expand All @@ -31,7 +32,7 @@ func TestComponentTypeCreate(t *testing.T) {

testRequest := autopilot.NewTestRequest(
`mutation ComponentTypeCreate($input:ComponentTypeInput!){componentTypeCreate(input:$input){componentType{{ template "component_type_graphql" }},errors{message,path}}}`,
`{"input": {"alias": "example", "name": "Example", "description": "Example Description", "properties": [], "ownerRelationship": {"managementRules": [{"operator": "EQUALS", "sourceProperty": "tag_key_eq:owner", "sourcePropertyBuiltin": true, "targetProperty": "name", "targetPropertyBuiltin": true, "targetType": "team"}]} }}`,
`{"input": {"alias": "example", "category": "infrastructure", "name": "Example", "description": "Example Description", "properties": [], "ownerRelationship": {"managementRules": [{"operator": "EQUALS", "sourceProperty": "tag_key_eq:owner", "sourcePropertyBuiltin": true, "targetProperty": "name", "targetPropertyBuiltin": true, "targetType": "team"}]} }}`,
`{"data": {"componentTypeCreate": {"componentType": {{ template "component_type_1_response" }} }}}`,
)

Expand All @@ -41,6 +42,32 @@ func TestComponentTypeCreate(t *testing.T) {
// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, result.Id)
autopilot.Equals(t, "infrastructure", result.Category)
}

// A component type input without a category must omit the field entirely, so that
// accounts where the backend does not expose `category` are unaffected.
func TestComponentTypeCreateWithoutCategory(t *testing.T) {
// Arrange
input := autopilot.Register[ol.ComponentTypeInput]("component_type_create_input_no_category",
ol.ComponentTypeInput{
Alias: ol.RefOf("example"),
Name: ol.RefOf("Example"),
Properties: &[]ol.ComponentTypePropertyDefinitionInput{},
})

testRequest := autopilot.NewTestRequest(
`mutation ComponentTypeCreate($input:ComponentTypeInput!){componentTypeCreate(input:$input){componentType{{ template "component_type_graphql" }},errors{message,path}}}`,
`{"input": {"alias": "example", "name": "Example", "properties": [] }}`,
`{"data": {"componentTypeCreate": {"componentType": {{ template "component_type_2_response" }} }}}`,
)

client := BestTestClient(t, "ComponentType/create_without_category", testRequest)
// Act
result, err := client.CreateComponentType(input)
// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id2, result.Id)
}

func TestComponentTypeGet(t *testing.T) {
Expand All @@ -57,6 +84,7 @@ func TestComponentTypeGet(t *testing.T) {
// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, result.Id)
autopilot.Equals(t, "infrastructure", result.Category)
}

func TestComponentTypeList(t *testing.T) {
Expand All @@ -83,13 +111,17 @@ func TestComponentTypeList(t *testing.T) {
autopilot.Equals(t, "Example1", result[0].Name)
autopilot.Equals(t, "Example2", result[1].Name)
autopilot.Equals(t, "Example3", result[2].Name)
autopilot.Equals(t, "infrastructure", result[0].Category)
autopilot.Equals(t, "default", result[1].Category)
autopilot.Equals(t, "default", result[2].Category)
}

func TestComponentTypeUpdate(t *testing.T) {
// Arrange
input := autopilot.Register[ol.ComponentTypeInput]("component_type_update_input",
ol.ComponentTypeInput{
Alias: ol.RefOf("example"),
Category: ol.RefOf("infrastructure"),
Name: ol.RefOf("Example"),
Description: ol.RefOf("Example Description"),
Properties: &[]ol.ComponentTypePropertyDefinitionInput{},
Expand All @@ -109,7 +141,7 @@ func TestComponentTypeUpdate(t *testing.T) {

testRequest := autopilot.NewTestRequest(
`mutation ComponentTypeUpdate($input:ComponentTypeInput!$target:IdentifierInput!){componentTypeUpdate(componentType:$target,input:$input){componentType{{ template "component_type_graphql" }},errors{message,path}}}`,
`{"input": {"alias": "example", "name": "Example", "description": "Example Description", "properties": [], "ownerRelationship": {"managementRules": [{"operator": "EQUALS", "sourceProperty": "tag_key_eq:owner", "sourcePropertyBuiltin": true, "targetProperty": "name", "targetPropertyBuiltin": true, "targetType": "team"}]}}, "target": { {{ template "id1" }} }}`,
`{"input": {"alias": "example", "category": "infrastructure", "name": "Example", "description": "Example Description", "properties": [], "ownerRelationship": {"managementRules": [{"operator": "EQUALS", "sourceProperty": "tag_key_eq:owner", "sourcePropertyBuiltin": true, "targetProperty": "name", "targetPropertyBuiltin": true, "targetType": "team"}]}}, "target": { {{ template "id1" }} }}`,
`{"data": {"componentTypeUpdate": {"componentType": {{ template "component_type_1_response" }} }}}`,
)

Expand All @@ -119,6 +151,31 @@ func TestComponentTypeUpdate(t *testing.T) {
// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, result.Id)
autopilot.Equals(t, "infrastructure", result.Category)
}

// Patching only the category must leave the rest of the component type untouched, which
// is how existing types get adopted into the infrastructure catalog without a recreate.
func TestComponentTypeUpdateCategoryOnly(t *testing.T) {
// Arrange
input := autopilot.Register[ol.ComponentTypeInput]("component_type_update_category_only_input",
ol.ComponentTypeInput{
Category: ol.RefOf("infrastructure"),
})

testRequest := autopilot.NewTestRequest(
`mutation ComponentTypeUpdate($input:ComponentTypeInput!$target:IdentifierInput!){componentTypeUpdate(componentType:$target,input:$input){componentType{{ template "component_type_graphql" }},errors{message,path}}}`,
`{"input": {"category": "infrastructure"}, "target": { {{ template "id1" }} }}`,
`{"data": {"componentTypeUpdate": {"componentType": {{ template "component_type_1_response" }} }}}`,
)

client := BestTestClient(t, "ComponentType/update_category_only", testRequest)
// Act
result, err := client.UpdateComponentType(string(id1), input)
// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, result.Id)
autopilot.Equals(t, "infrastructure", result.Category)
}

func TestComponentTypeDelete(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ type ComponentTypeIconInput struct {
// ComponentTypeInput Specifies the input fields used to create a component type
type ComponentTypeInput struct {
Alias *Nullable[string] `json:"alias,omitempty" yaml:"alias,omitempty" example:"example_value"` // The unique alias of the component type (Optional)
Category *Nullable[string] `json:"category,omitempty" yaml:"category,omitempty" example:"infrastructure"` // The catalog category the component type is filed under, e.g. `default` or `infrastructure` (Optional)
Description *Nullable[string] `json:"description,omitempty" yaml:"description,omitempty" example:"example_value"` // The description of the component type (Optional)
Icon *ComponentTypeIconInput `json:"icon,omitempty" yaml:"icon,omitempty"` // The icon associated with the component type (Optional)
Name *Nullable[string] `json:"name,omitempty" yaml:"name,omitempty" example:"example_value"` // The unique name of the component type (Optional)
Expand Down
1 change: 1 addition & 0 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ type ComponentTypeId struct {
// ComponentType Information about a particular component type
type ComponentType struct {
ComponentTypeId
Category string // The catalog category the component type is filed under, e.g. `default` or `infrastructure` (Optional)
Description string // The description of the component type (Optional)
Href string // The relative path to link to the component type (Required)
Icon ComponentTypeIcon // The icon associated with the component type (Required)
Expand Down
5 changes: 4 additions & 1 deletion testdata/templates/component_type.tpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- define "component_type_graphql" }}
{id,aliases,description,href,icon{color,name},isDefault,name,ownerRelationship{managementRules{operator,sourceProperty,sourcePropertyBuiltin,targetCategory,targetProperty,targetPropertyBuiltin,targetType}},systemRelationship{managementRules{operator,sourceProperty,sourcePropertyBuiltin,targetCategory,targetProperty,targetPropertyBuiltin,targetType}},timestamps{createdAt,updatedAt}}
{id,aliases,category,description,href,icon{color,name},isDefault,name,ownerRelationship{managementRules{operator,sourceProperty,sourcePropertyBuiltin,targetCategory,targetProperty,targetPropertyBuiltin,targetType}},systemRelationship{managementRules{operator,sourceProperty,sourcePropertyBuiltin,targetCategory,targetProperty,targetPropertyBuiltin,targetType}},timestamps{createdAt,updatedAt}}
{{end}}
{{- define "component_type_1_response" }}
{
Expand All @@ -8,6 +8,7 @@
"example1"
],
"name": "Example1",
"category": "infrastructure",
"description": "Description",
"href": "https://app.opslevel-staging.com/catalog/domains/platformdomain",
"icon": {
Expand Down Expand Up @@ -49,6 +50,7 @@
"example2"
],
"name": "Example2",
"category": "default",
"description": "Description",
"href": "https://app.opslevel-staging.com/catalog/domains/platformdomain",
"icon": {
Expand All @@ -64,6 +66,7 @@
"example3"
],
"name": "Example3",
"category": "default",
"description": "Description",
"href": "https://app.opslevel-staging.com/catalog/domains/platformdomain",
"icon": {
Expand Down