pkg/document/model/feature.go (175 lines of code) (raw):
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"slices"
"strings"
"github.com/GoogleCloudPlatform/khi/pkg/common/filter"
"github.com/GoogleCloudPlatform/khi/pkg/common/typedmap"
"github.com/GoogleCloudPlatform/khi/pkg/inspection"
inspection_task "github.com/GoogleCloudPlatform/khi/pkg/inspection/task"
"github.com/GoogleCloudPlatform/khi/pkg/inspection/task/label"
"github.com/GoogleCloudPlatform/khi/pkg/model/enum"
"github.com/GoogleCloudPlatform/khi/pkg/task"
)
// FeatureDocumentModel is a model type for generating document docs/en/reference/features.md
type FeatureDocumentModel struct {
// Features are the list of feature tasks defined in KHI.
Features []FeatureDocumentElement
}
// FeatureDocumentElement is a model type for a feature task used in FeatureDocumentModel.
type FeatureDocumentElement struct {
// ID is the unique name of the feature task.
ID string
// Name is the human readable name of the feature task.
Name string
// Description is the string explain the feature task.
Description string
// Forms is the list of information about form inputs that is required from the feature task.
Forms []FeatureDependentFormElement
// IndirectQueryDependency is the list of query tasks that is required from this feature task but not the target query task.
IndirectQueryDependency []FeatureIndirectDependentQueryElement
// TargetQUeryDependency is the main query task used in this feature task.
TargetQueryDependency FeatureDependentTargetQueryElement
// OutputTimelines is the list of timelines(=ParentRelationship type) that can be generated by this feature task.
OutputTimelines []FeatureOutputTimelineElement
// AvailableInspctionType is the list of InspectionType that supports this feature task.
AvailableInspectionTypes []FeatureAvailableInspectionType
}
// FeatureDependentFormElement is a model type for a input form required from a feature task.
type FeatureDependentFormElement struct {
// ID is the unique name of this form element.
ID string
// Label is a human readable short name of this input element.
Label string
// Description is a string explaining this form input.
Description string
}
// FeatureIndirectDependentQueryElement is a model type for query tasks required from a feature task but not the target query task.
type FeatureIndirectDependentQueryElement struct {
// ID is the unique name of this query task.
ID string
// LogTypeLabel is a human readable short name of the log type queried by the query task.
LogTypeLabel string
// LogTypeColorCode is the hex color code without the `#` prefix for the log type.
LogTypeColorCode string
}
// FeatureDependentTargetQueryElement is a model type for a target query task of the feature task.
type FeatureDependentTargetQueryElement struct {
// ID is the unique name of this query task.
ID string
// LogTypeLabel is a human readable short name of the log type queried by the query task.
LogTypeLabel string
// LogTypeColorCode is the hex color code without the `#` prefix for the log type.
LogTypeColorCode string
// SampleQuery is an example query string used in this query task.
SampleQuery string
}
// FeatureOutputTimelineElement is a model type for one of relationship type of timelines that can be related to this feature.
type FeatureOutputTimelineElement struct {
// RelationshipID is the unique name of the relationship type.
RelationshipID string
// RelationshipColorCode is the hex color code without the `#` prefix for the relationship type.
RelationshipColorCode string
// LongName is the human readable name of the relationship
LongName string
// Label is the short name of the timeline. This is also used in the chip on the left side of timelines.
Label string
// Description is the string explains the relationship.
Description string
}
// FeatureAvailableInspectionType is a model type for a InspectionType that supports the feature task.
type FeatureAvailableInspectionType struct {
// ID is the unique name of the InspectionType.
ID string
// Name is the human readable name of the InspectionType.
Name string
}
// GetFeatureDocumentModel returns the document model for feature tasks from the task server.
func GetFeatureDocumentModel(taskServer *inspection.InspectionTaskServer) (*FeatureDocumentModel, error) {
result := FeatureDocumentModel{}
features := task.Subset(taskServer.RootTaskSet, filter.NewEnabledFilter(inspection_task.LabelKeyInspectionFeatureFlag, false))
for _, feature := range features.GetAll() {
indirectQueryDependencyElement := []FeatureIndirectDependentQueryElement{}
targetQueryDependencyElement := FeatureDependentTargetQueryElement{}
targetLogTypeKey := typedmap.GetOrDefault(feature.Labels(), inspection_task.LabelKeyFeatureTaskTargetLogType, enum.LogTypeUnknown)
// Get query related tasks in the dependency of this feature.
queryTasksInDependency, err := getDependentQueryTasks(taskServer, feature)
if err != nil {
return nil, err
}
for _, queryTask := range queryTasksInDependency {
logTypeKey := typedmap.GetOrDefault(queryTask.Labels(), label.TaskLabelKeyQueryTaskTargetLogType, enum.LogTypeUnknown)
if targetLogTypeKey != logTypeKey {
logType := enum.LogTypes[logTypeKey]
indirectQueryDependencyElement = append(indirectQueryDependencyElement, FeatureIndirectDependentQueryElement{
ID: queryTask.UntypedID().String(),
LogTypeLabel: logType.Label,
LogTypeColorCode: strings.TrimLeft(logType.LabelBackgroundColor, "#"),
})
} else {
targetQueryDependencyElement = FeatureDependentTargetQueryElement{
ID: queryTask.UntypedID().String(),
LogTypeLabel: enum.LogTypes[targetLogTypeKey].Label,
LogTypeColorCode: strings.TrimLeft(enum.LogTypes[targetLogTypeKey].LabelBackgroundColor, "#"),
SampleQuery: typedmap.GetOrDefault(queryTask.Labels(), label.TaskLabelKeyQueryTaskSampleQuery, ""),
}
}
}
formElements := []FeatureDependentFormElement{}
formTasks, err := getDependentFormTasks(taskServer, feature)
if err != nil {
return nil, err
}
for _, formTask := range formTasks {
formElements = append(formElements, FeatureDependentFormElement{
ID: formTask.UntypedID().String(),
Label: typedmap.GetOrDefault(formTask.Labels(), label.TaskLabelKeyFormFieldLabel, ""),
Description: typedmap.GetOrDefault(formTask.Labels(), label.TaskLabelKeyFormFieldDescription, ""),
})
}
outputTimelines := []FeatureOutputTimelineElement{}
for i := 0; i < enum.EnumParentRelationshipLength; i++ {
relationshipKey := enum.ParentRelationship(i)
relationship := enum.ParentRelationships[relationshipKey]
isRelated := false
for _, event := range relationship.GeneratableEvents {
if event.SourceLogType == targetLogTypeKey {
isRelated = true
break
}
}
for _, revision := range relationship.GeneratableRevisions {
if revision.SourceLogType == targetLogTypeKey {
isRelated = true
break
}
}
for _, alias := range relationship.GeneratableAliasTimelineInfo {
if alias.SourceLogType == targetLogTypeKey {
isRelated = true
break
}
}
if isRelated {
outputTimelines = append(outputTimelines, FeatureOutputTimelineElement{
RelationshipID: relationship.EnumKeyName,
RelationshipColorCode: strings.TrimLeft(relationship.LabelBackgroundColor, "#"),
LongName: relationship.LongName,
Label: relationship.Label,
Description: relationship.Description,
})
}
}
result.Features = append(result.Features, FeatureDocumentElement{
ID: feature.UntypedID().String(),
Name: typedmap.GetOrDefault(feature.Labels(), inspection_task.LabelKeyFeatureTaskTitle, ""),
Description: typedmap.GetOrDefault(feature.Labels(), inspection_task.LabelKeyFeatureTaskDescription, ""),
IndirectQueryDependency: indirectQueryDependencyElement,
TargetQueryDependency: targetQueryDependencyElement,
Forms: formElements,
OutputTimelines: outputTimelines,
AvailableInspectionTypes: getAvailableInspectionTypes(taskServer, feature),
})
}
return &result, nil
}
// getDependentQueryTasks returns the list of query tasks required by the feature task.
func getDependentQueryTasks(taskServer *inspection.InspectionTaskServer, featureTask task.UntypedTask) ([]task.UntypedTask, error) {
resolveSource, err := task.NewTaskSet([]task.UntypedTask{featureTask})
if err != nil {
return nil, err
}
resolved, err := resolveSource.ResolveTask(taskServer.RootTaskSet)
if err != nil {
return nil, err
}
return task.Subset(resolved, filter.NewEnabledFilter(label.TaskLabelKeyIsQueryTask, false)).GetAll(), nil
}
// getDependentFormTasks returns the list of form tasks required by the feature task.
func getDependentFormTasks(taskServer *inspection.InspectionTaskServer, featureTask task.UntypedTask) ([]task.UntypedTask, error) {
resolveSource, err := task.NewTaskSet([]task.UntypedTask{featureTask})
if err != nil {
return nil, err
}
resolved, err := resolveSource.ResolveTask(taskServer.RootTaskSet)
if err != nil {
return nil, err
}
return task.Subset(resolved, filter.NewEnabledFilter(label.TaskLabelKeyIsFormTask, false)).GetAll(), nil
}
// getAvailableInspectionTypes returns the list of information about inspection type that supports this feature.
func getAvailableInspectionTypes(taskServer *inspection.InspectionTaskServer, featureTask task.UntypedTask) []FeatureAvailableInspectionType {
result := []FeatureAvailableInspectionType{}
inspectionTypes := taskServer.GetAllInspectionTypes()
for _, inspectionType := range inspectionTypes {
labels, found := typedmap.Get(featureTask.Labels(), inspection_task.LabelKeyInspectionTypes)
if found && slices.Contains(labels, inspectionType.Id) {
result = append(result, FeatureAvailableInspectionType{
ID: inspectionType.Id,
Name: inspectionType.Name,
})
}
}
return result
}