From baa93c218b86a2bdba3dc08b1c3c59c37920a792 Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Wed, 12 Jul 2023 02:00:46 +0900 Subject: [PATCH] feat: add analyzer documentation links (#2903) --- api/linters.yaml | 2 + .../model_linter_result_plugin_rule.go | 36 +++++++ server/http/mappings/linter.go | 1 + server/linter/analyzer/analyzer_metadata.go | 48 +++++----- .../analyzer/analyzer_repository_test.go | 94 +++++++++---------- .../linter/plugins/plugins_entities_test.go | 2 +- .../linter/rules/ensure_span_naming_rule.go | 8 +- .../model_linter_result_plugin_rule.go | 2 + .../analyzer/apply_analyzer_test.go | 2 +- .../analyzer/get_analyzer_test.go | 2 +- .../analyzer/resources/new-analyzer.yaml | 14 +-- .../AnalyzerErrorsPopover/Content.tsx | 3 + .../AnalyzerResult/AnalyzerResult.styled.ts | 6 ++ web/src/components/AnalyzerResult/Rule.tsx | 5 +- .../components/AnalyzerResult/RuleLink.tsx | 20 ++++ .../TraceSpanNode/AnalyzerErrorsPopover.tsx | 3 + web/src/constants/Common.constants.ts | 3 +- web/src/models/LinterResult.model.ts | 2 + .../__tests__/useCreateTestMutation.tesx.tsx | 4 +- web/src/services/TestRun.service.ts | 1 + web/src/types/Generated.types.ts | 6 +- web/src/types/Test.types.ts | 3 +- web/src/types/TestRun.types.ts | 1 + 23 files changed, 172 insertions(+), 96 deletions(-) create mode 100644 web/src/components/AnalyzerResult/RuleLink.tsx diff --git a/api/linters.yaml b/api/linters.yaml index 12d987c482..754769876a 100644 --- a/api/linters.yaml +++ b/api/linters.yaml @@ -105,6 +105,8 @@ components: LinterResultPluginRule: type: object properties: + id: + type: string name: type: string description: diff --git a/cli/openapi/model_linter_result_plugin_rule.go b/cli/openapi/model_linter_result_plugin_rule.go index 81498c01ae..a4ca6de080 100644 --- a/cli/openapi/model_linter_result_plugin_rule.go +++ b/cli/openapi/model_linter_result_plugin_rule.go @@ -19,6 +19,7 @@ var _ MappedNullable = &LinterResultPluginRule{} // LinterResultPluginRule struct for LinterResultPluginRule type LinterResultPluginRule struct { + Id *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` Description *string `json:"description,omitempty"` ErrorDescription *string `json:"errorDescription,omitempty"` @@ -46,6 +47,38 @@ func NewLinterResultPluginRuleWithDefaults() *LinterResultPluginRule { return &this } +// GetId returns the Id field value if set, zero value otherwise. +func (o *LinterResultPluginRule) GetId() string { + if o == nil || isNil(o.Id) { + var ret string + return ret + } + return *o.Id +} + +// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *LinterResultPluginRule) GetIdOk() (*string, bool) { + if o == nil || isNil(o.Id) { + return nil, false + } + return o.Id, true +} + +// HasId returns a boolean if a field has been set. +func (o *LinterResultPluginRule) HasId() bool { + if o != nil && !isNil(o.Id) { + return true + } + + return false +} + +// SetId gets a reference to the given string and assigns it to the Id field. +func (o *LinterResultPluginRule) SetId(v string) { + o.Id = &v +} + // GetName returns the Name field value if set, zero value otherwise. func (o *LinterResultPluginRule) GetName() string { if o == nil || isNil(o.Name) { @@ -312,6 +345,9 @@ func (o LinterResultPluginRule) MarshalJSON() ([]byte, error) { func (o LinterResultPluginRule) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} + if !isNil(o.Id) { + toSerialize["id"] = o.Id + } if !isNil(o.Name) { toSerialize["name"] = o.Name } diff --git a/server/http/mappings/linter.go b/server/http/mappings/linter.go index d6e9413aaf..6eb4df3e7f 100644 --- a/server/http/mappings/linter.go +++ b/server/http/mappings/linter.go @@ -41,6 +41,7 @@ func (m OpenAPI) LinterResultPluginRule(in analyzer.RuleResult) openapi.LinterRe } return openapi.LinterResultPluginRule{ + Id: in.ID, Passed: in.Passed, Description: in.Description, ErrorDescription: in.ErrorDescription, diff --git a/server/linter/analyzer/analyzer_metadata.go b/server/linter/analyzer/analyzer_metadata.go index b698b9b1b1..41b2dcbb89 100644 --- a/server/linter/analyzer/analyzer_metadata.go +++ b/server/linter/analyzer/analyzer_metadata.go @@ -7,13 +7,13 @@ var ( SecurityID = "security" // rules - EnsureSpanNamingRuleID string = "span_naming" - RequiredAttributesRuleID string = "required_attributes" - EnsureAttributeNamingRuleID string = "attribute_naming" - NotEmptyAttributesRuleID string = "not_empty_attributes" - EnforceDnsRuleID string = "enforce_dns" - EnforceHttpsProtocolRuleID string = "enforce_https_protocol" - EnsuresNoApiKeyLeakRuleID string = "ensures_no_api_key_leak" + EnsureSpanNamingRuleID string = "span-naming" + RequiredAttributesRuleID string = "required-attributes" + EnsureAttributeNamingRuleID string = "attribute-naming" + NotEmptyAttributesRuleID string = "no-empty-attributes" + EnforceDnsRuleID string = "prefer-dns" + EnforceHttpsProtocolRuleID string = "secure-https-protocol" + EnsuresNoApiKeyLeakRuleID string = "no-api-key-leak" ErrorLevelWarning string = "warning" ErrorLevelError string = "error" @@ -37,7 +37,7 @@ var ( StandardsPlugin = LinterPlugin{ ID: StandardsID, Name: "OTel Semantic Conventions", - Description: "Enforce standards for spans and attributes", + Description: "Enforce trace standards following OTel Semantic Conventions", Enabled: true, Rules: []LinterRule{ EnsureSpanNamingRule, @@ -49,8 +49,8 @@ var ( EnsureSpanNamingRule = LinterRule{ ID: EnsureSpanNamingRuleID, - Name: "Span Name Convention", - Description: "Ensure all spans follow the naming convention", + Name: "Span Naming", + Description: "Enforce span names that identify a class of Spans", ErrorDescription: "", Tips: []string{}, Weight: 25, @@ -59,8 +59,8 @@ var ( RequiredAttributesRule = LinterRule{ ID: RequiredAttributesRuleID, - Name: "Required Attributes By Span Type", - Description: "Ensure all required attributes are present", + Name: "Required Attributes", + Description: "Enforce required attributes by span type", ErrorDescription: "This span is missing the following required attributes:", Tips: []string{"This rule checks if all required attributes are present in spans of given type"}, Weight: 25, @@ -70,7 +70,7 @@ var ( EnsureAttributeNamingRule = LinterRule{ ID: EnsureAttributeNamingRuleID, Name: "Attribute Naming", - Description: "Ensure all attributes follow the naming convention", + Description: "Enforce attribute keys to follow common specifications", ErrorDescription: "The following attributes do not follow the naming convention:", Tips: []string{ "You should always add namespaces to your span names to ensure they will not be overwritten", @@ -82,8 +82,8 @@ var ( NotEmptyAttributesRule = LinterRule{ ID: NotEmptyAttributesRuleID, - Name: "Not Empty Attributes", - Description: "Does not allow empty attribute values in any span", + Name: "No Empty Attributes", + Description: "Disallow empty attribute values", ErrorDescription: "The following attributes are empty:", Tips: []string{"Empty attributes don't provide any information about the operation and should be removed"}, Weight: 25, @@ -93,8 +93,8 @@ var ( // common CommonPlugin = LinterPlugin{ ID: CommonID, - Name: "Common problems", - Description: "Helps you find common problems with your application", + Name: "Common Problems", + Description: "Help you find common mistakes with your application", Enabled: true, Rules: []LinterRule{ EnforceDnsRule, @@ -103,8 +103,8 @@ var ( EnforceDnsRule = LinterRule{ ID: EnforceDnsRuleID, - Name: "Enforce DNS Over IP usage", - Description: "Enforce DNS usage over IP addresses", + Name: "Prefer DNS", + Description: "Enforce usage of DNS instead of IP addresses", ErrorDescription: "The following attributes are using IP addresses instead of DNS:", Tips: []string{}, Weight: 100, @@ -115,7 +115,7 @@ var ( SecurityPlugin = LinterPlugin{ ID: SecurityID, Name: "Security", - Description: "Enforce security for spans and attributes", + Description: "Help you find security problems with your application", Enabled: true, Rules: []LinterRule{ EnforceHttpsProtocolRule, @@ -125,9 +125,9 @@ var ( EnforceHttpsProtocolRule = LinterRule{ ID: EnforceHttpsProtocolRuleID, - Name: "Enforce HTTPS protocol", - Description: "Ensure all request use https", - ErrorDescription: "The following spans are using http protocol:", + Name: "Secure HTTPS Protocol", + Description: "Enforce usage of secure protocol for HTTP server spans", + ErrorDescription: "The following spans are using insecure http protocol:", Tips: []string{}, Weight: 30, ErrorLevel: "error", @@ -136,7 +136,7 @@ var ( EnsuresNoApiKeyLeakRule = LinterRule{ ID: EnsuresNoApiKeyLeakRuleID, Name: "No API Key Leak", - Description: "Ensure no API keys are leaked in http headers", + Description: "Disallow leaked API keys for HTTP spans", ErrorDescription: "The following attributes are exposing API keys:", Tips: []string{}, Weight: 70, diff --git a/server/linter/analyzer/analyzer_repository_test.go b/server/linter/analyzer/analyzer_repository_test.go index c3b582b20b..a3c30fe74d 100644 --- a/server/linter/analyzer/analyzer_repository_test.go +++ b/server/linter/analyzer/analyzer_repository_test.go @@ -40,94 +40,94 @@ func TestLinterResource(t *testing.T) { "enabled": true, "rules": [ { - "id": "span_naming", + "id": "span-naming", "weight": 25, "errorLevel": "error", - "name": "Span Name Convention", + "name": "Span Naming", "errorDescription": "", - "description": "Ensure all spans follow the naming convention", + "description": "Enforce span names that identify a class of Spans", "tips": [] }, { - "id": "required_attributes", + "id": "required-attributes", "weight": 25, "errorLevel": "error", - "name": "Required Attributes By Span Type", + "name": "Required Attributes", "errorDescription": "This span is missing the following required attributes:", - "description": "Ensure all required attributes are present", + "description": "Enforce required attributes by span type", "tips": [ "This rule checks if all required attributes are present in spans of given type" ] }, { - "id": "attribute_naming", + "id": "attribute-naming", "weight": 25, "errorLevel": "error", "name": "Attribute Naming", "errorDescription": "The following attributes do not follow the naming convention:", - "description": "Ensure all attributes follow the naming convention", + "description": "Enforce attribute keys to follow common specifications", "tips": [ "You should always add namespaces to your span names to ensure they will not be overwritten", "Use snake_case to separate multi-words. Ex: http.status_code instead of http.statusCode" ] }, { - "id": "not_empty_attributes", + "id": "no-empty-attributes", "weight": 25, "errorLevel": "error", - "name": "Not Empty Attributes", + "name": "No Empty Attributes", "errorDescription": "The following attributes are empty:", - "description": "Does not allow empty attribute values in any span", + "description": "Disallow empty attribute values", "tips": [ "Empty attributes don't provide any information about the operation and should be removed" ] } ], "name": "OTel Semantic Conventions", - "description": "Enforce standards for spans and attributes" + "description": "Enforce trace standards following OTel Semantic Conventions" }, { "id": "common", "enabled": true, "rules": [ { - "id": "enforce_dns", + "id": "prefer-dns", "weight": 100, "errorLevel": "error", - "name": "Enforce DNS Over IP usage", + "name": "Prefer DNS", "errorDescription": "The following attributes are using IP addresses instead of DNS:", - "description": "Enforce DNS usage over IP addresses", + "description": "Enforce usage of DNS instead of IP addresses", "tips": [] } ], - "name": "Common problems", - "description": "Helps you find common problems with your application" + "name": "Common Problems", + "description": "Help you find common mistakes with your application" }, { "id": "security", "enabled": true, "rules": [ { - "id": "enforce_https_protocol", + "id": "secure-https-protocol", "weight": 30, "errorLevel": "error", - "name": "Enforce HTTPS protocol", - "errorDescription": "The following spans are using http protocol:", - "description": "Ensure all request use https", + "name": "Secure HTTPS Protocol", + "errorDescription": "The following spans are using insecure http protocol:", + "description": "Enforce usage of secure protocol for HTTP server spans", "tips": [] }, { - "id": "ensures_no_api_key_leak", + "id": "no-api-key-leak", "weight": 70, "errorLevel": "error", "name": "No API Key Leak", "errorDescription": "The following attributes are exposing API keys:", - "description": "Ensure no API keys are leaked in http headers", + "description": "Disallow leaked API keys for HTTP spans", "tips": [] } ], "name": "Security", - "description": "Enforce security for spans and attributes" + "description": "Help you find security problems with your application" } ] } @@ -145,94 +145,94 @@ func TestLinterResource(t *testing.T) { "enabled": true, "rules": [ { - "id": "span_naming", + "id": "span-naming", "weight": 25, "errorLevel": "error", - "name": "Span Name Convention", + "name": "Span Naming", "errorDescription": "", - "description": "Ensure all spans follow the naming convention", + "description": "Enforce span names that identify a class of Spans", "tips": [] }, { - "id": "required_attributes", + "id": "required-attributes", "weight": 25, "errorLevel": "error", - "name": "Required Attributes By Span Type", + "name": "Required Attributes", "errorDescription": "This span is missing the following required attributes:", - "description": "Ensure all required attributes are present", + "description": "Enforce required attributes by span type", "tips": [ "This rule checks if all required attributes are present in spans of given type" ] }, { - "id": "attribute_naming", + "id": "attribute-naming", "weight": 25, "errorLevel": "error", "name": "Attribute Naming", "errorDescription": "The following attributes do not follow the naming convention:", - "description": "Ensure all attributes follow the naming convention", + "description": "Enforce attribute keys to follow common specifications", "tips": [ "You should always add namespaces to your span names to ensure they will not be overwritten", "Use snake_case to separate multi-words. Ex: http.status_code instead of http.statusCode" ] }, { - "id": "not_empty_attributes", + "id": "no-empty-attributes", "weight": 25, "errorLevel": "error", - "name": "Not Empty Attributes", + "name": "No Empty Attributes", "errorDescription": "The following attributes are empty:", - "description": "Does not allow empty attribute values in any span", + "description": "Disallow empty attribute values", "tips": [ "Empty attributes don't provide any information about the operation and should be removed" ] } ], "name": "OTel Semantic Conventions", - "description": "Enforce standards for spans and attributes" + "description": "Enforce trace standards following OTel Semantic Conventions" }, { "id": "common", "enabled": true, "rules": [ { - "id": "enforce_dns", + "id": "prefer-dns", "weight": 100, "errorLevel": "error", - "name": "Enforce DNS Over IP usage", + "name": "Prefer DNS", "errorDescription": "The following attributes are using IP addresses instead of DNS:", - "description": "Enforce DNS usage over IP addresses", + "description": "Enforce usage of DNS instead of IP addresses", "tips": [] } ], - "name": "Common problems", - "description": "Helps you find common problems with your application" + "name": "Common Problems", + "description": "Help you find common mistakes with your application" }, { "id": "security", "enabled": true, "rules": [ { - "id": "enforce_https_protocol", + "id": "secure-https-protocol", "weight": 30, "errorLevel": "error", - "name": "Enforce HTTPS protocol", + "name": "Secure HTTPS Protocol", "errorDescription": "The following spans are using http protocol:", - "description": "Ensure all request use https", + "description": "Enforce usage of secure protocol for HTTP server spans", "tips": [] }, { - "id": "ensures_no_api_key_leak", + "id": "no-api-key-leak", "weight": 70, "errorLevel": "error", "name": "No API Key Leak", "errorDescription": "The following attributes are exposing API keys:", - "description": "Ensure no API keys are leaked in http headers", + "description": "Disallow leaked API keys for HTTP spans", "tips": [] } ], "name": "Security", - "description": "Enforce security for spans and attributes" + "description": "Help you find security problems with your application" } ] } diff --git a/server/linter/plugins/plugins_entities_test.go b/server/linter/plugins/plugins_entities_test.go index c0301ccd4b..b9e7ed18fa 100644 --- a/server/linter/plugins/plugins_entities_test.go +++ b/server/linter/plugins/plugins_entities_test.go @@ -60,7 +60,7 @@ func TestAnalyzerEntities(t *testing.T) { _, err := emptyPlugin.Execute(context.TODO(), trace, analyzer.StandardsPlugin) assert.NotNil(t, err) - assert.Contains(t, "rule span_naming not found", err.Error()) + assert.Contains(t, "rule span-naming not found", err.Error()) }) } diff --git a/server/linter/rules/ensure_span_naming_rule.go b/server/linter/rules/ensure_span_naming_rule.go index 56d9d4f943..41f45ab663 100644 --- a/server/linter/rules/ensure_span_naming_rule.go +++ b/server/linter/rules/ensure_span_naming_rule.go @@ -69,7 +69,7 @@ func (r ensureSpanNamingRule) validateHTTPSpanName(ctx context.Context, span *mo Passed: false, Errors: []analyzer.Error{ { - Value: span.Name, + Value: "tracetest.span.name", Expected: expectedName, Description: fmt.Sprintf(`The Span name %s is not matching the naming convention`, span.Name), }, @@ -105,7 +105,7 @@ func (r ensureSpanNamingRule) validateDatabaseSpanName(ctx context.Context, span Passed: false, Errors: []analyzer.Error{ { - Value: span.Name, + Value: "tracetest.span.name", Expected: expectedName, Description: fmt.Sprintf(`The Span name %s is not matching the naming convention`, span.Name), }, @@ -131,7 +131,7 @@ func (r ensureSpanNamingRule) validateRPCSpanName(ctx context.Context, span *mod Passed: false, Errors: []analyzer.Error{ { - Value: span.Name, + Value: "tracetest.span.name", Expected: expectedName, Description: fmt.Sprintf(`The Span name %s is not matching the naming convention`, span.Name), }, @@ -157,7 +157,7 @@ func (r ensureSpanNamingRule) validateMessagingSpanName(ctx context.Context, spa Passed: false, Errors: []analyzer.Error{ { - Value: span.Name, + Value: "tracetest.span.name", Expected: expectedName, Description: fmt.Sprintf(`The Span name %s is not matching the naming convention`, span.Name), }, diff --git a/server/openapi/model_linter_result_plugin_rule.go b/server/openapi/model_linter_result_plugin_rule.go index a611818465..f6c063439f 100644 --- a/server/openapi/model_linter_result_plugin_rule.go +++ b/server/openapi/model_linter_result_plugin_rule.go @@ -10,6 +10,8 @@ package openapi type LinterResultPluginRule struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` diff --git a/testing/cli-e2etest/testscenarios/analyzer/apply_analyzer_test.go b/testing/cli-e2etest/testscenarios/analyzer/apply_analyzer_test.go index 1119a9cb5d..a8055b8a5f 100644 --- a/testing/cli-e2etest/testscenarios/analyzer/apply_analyzer_test.go +++ b/testing/cli-e2etest/testscenarios/analyzer/apply_analyzer_test.go @@ -46,7 +46,7 @@ func TestApplyAnalyzer(t *testing.T) { plugin1 := analyzer.Spec.Plugins[0] require.Len(plugin1.Rules, 4) - require.Equal(plugin1.Rules[0].ID, "span_naming") + require.Equal(plugin1.Rules[0].ID, "span-naming") for _, rule := range plugin1.Rules { require.Equal(rule.Weight, 25) } diff --git a/testing/cli-e2etest/testscenarios/analyzer/get_analyzer_test.go b/testing/cli-e2etest/testscenarios/analyzer/get_analyzer_test.go index 623c189eda..5260e5bddd 100644 --- a/testing/cli-e2etest/testscenarios/analyzer/get_analyzer_test.go +++ b/testing/cli-e2etest/testscenarios/analyzer/get_analyzer_test.go @@ -91,7 +91,7 @@ func TestGetAnalyzer(t *testing.T) { plugin2 := analyzer.Spec.Plugins[1] require.Len(plugin2.Rules, 1) - require.Equal(plugin2.Rules[0].ID, "enforce_dns") + require.Equal(plugin2.Rules[0].ID, "prefer-dns") require.Equal(plugin2.Rules[0].Weight, 100) }) diff --git a/testing/cli-e2etest/testscenarios/analyzer/resources/new-analyzer.yaml b/testing/cli-e2etest/testscenarios/analyzer/resources/new-analyzer.yaml index 6d43213080..4a856e7436 100644 --- a/testing/cli-e2etest/testscenarios/analyzer/resources/new-analyzer.yaml +++ b/testing/cli-e2etest/testscenarios/analyzer/resources/new-analyzer.yaml @@ -8,30 +8,30 @@ spec: - id: standards enabled: true rules: - - id: span_naming + - id: span-naming weight: 25 errorLevel: error - - id: required_attributes + - id: required-attributes weight: 25 errorLevel: error - - id: attribute_naming + - id: attribute-naming weight: 25 errorLevel: error - - id: not_empty_attributes + - id: no-empty-attributes weight: 25 errorLevel: error - id: common enabled: true rules: - - id: enforce_dns + - id: prefer-dns weight: 100 errorLevel: error - id: security enabled: true rules: - - id: enforce_https_protocol + - id: secure-https-protocol weight: 30 errorLevel: error - - id: ensures_no_api_key_leak + - id: no-api-key-leak weight: 70 errorLevel: error diff --git a/web/src/components/AnalyzerErrorsPopover/Content.tsx b/web/src/components/AnalyzerErrorsPopover/Content.tsx index 56d7e7c06e..9c93950676 100644 --- a/web/src/components/AnalyzerErrorsPopover/Content.tsx +++ b/web/src/components/AnalyzerErrorsPopover/Content.tsx @@ -1,3 +1,4 @@ +import RuleLink from 'components/AnalyzerResult/RuleLink'; import {TAnalyzerError} from 'types/TestRun.types'; import * as S from './AnalyzerErrorsPopover.styled'; @@ -31,6 +32,8 @@ const Content = ({errors}: IProps) => ( {analyzerError.errors[0].description} )} + + {analyzerError.ruleId && } ))} diff --git a/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts b/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts index 516241878b..9e7a5ebb3b 100644 --- a/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts +++ b/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts @@ -162,4 +162,10 @@ export const SwitchContainer = styled.div` export const List = styled.ul` padding-inline-start: 20px; + margin-bottom: 4px; +`; + +export const RuleLinkText = styled(Typography.Text)<{$isSmall: boolean}>` + color: ${({theme}) => theme.color.textSecondary}; + font-size: ${({theme, $isSmall}) => ($isSmall ? theme.size.xs : theme.size.sm)}; `; diff --git a/web/src/components/AnalyzerResult/Rule.tsx b/web/src/components/AnalyzerResult/Rule.tsx index e26227e393..3e14de9fde 100644 --- a/web/src/components/AnalyzerResult/Rule.tsx +++ b/web/src/components/AnalyzerResult/Rule.tsx @@ -9,6 +9,7 @@ import {useAppDispatch} from 'redux/hooks'; import {selectSpan} from 'redux/slices/Trace.slice'; import * as S from './AnalyzerResult.styled'; import RuleIcon from './RuleIcon'; +import RuleLink from './RuleLink'; interface IProps { rule: LinterResultPluginRule; @@ -21,7 +22,7 @@ function getSpanName(spans: Span[], spanId: string) { } const Rule = ({ - rule: {tips, passed, description, name, errorDescription, results = [], level, weight = 0}, + rule: {id, tips, passed, description, name, errorDescription, results = [], level, weight = 0}, trace, }: IProps) => { const dispatch = useAppDispatch(); @@ -89,6 +90,8 @@ const Rule = ({ {result.errors[0].description} )} + + {!result.passed && } ))} diff --git a/web/src/components/AnalyzerResult/RuleLink.tsx b/web/src/components/AnalyzerResult/RuleLink.tsx new file mode 100644 index 0000000000..566258d754 --- /dev/null +++ b/web/src/components/AnalyzerResult/RuleLink.tsx @@ -0,0 +1,20 @@ +import {ANALYZER_RULES_DOCUMENTATION_URL} from 'constants/Common.constants'; +import * as S from './AnalyzerResult.styled'; + +interface IProps { + id: string; + isSmall?: boolean; +} + +const RuleLink = ({id, isSmall = false}: IProps) => ( +
+ + For more information, see{' '} + + analyzer({id}) + + +
+); + +export default RuleLink; diff --git a/web/src/components/Visualization/components/DAG/TraceSpanNode/AnalyzerErrorsPopover.tsx b/web/src/components/Visualization/components/DAG/TraceSpanNode/AnalyzerErrorsPopover.tsx index 27a6142290..f88e1796a8 100644 --- a/web/src/components/Visualization/components/DAG/TraceSpanNode/AnalyzerErrorsPopover.tsx +++ b/web/src/components/Visualization/components/DAG/TraceSpanNode/AnalyzerErrorsPopover.tsx @@ -1,6 +1,7 @@ import {Space} from 'antd'; import {useRef} from 'react'; +import RuleLink from 'components/AnalyzerResult/RuleLink'; import useOnClickOutside from 'hooks/useOnClickOutside'; import {TAnalyzerError} from 'types/TestRun.types'; import * as S from './AnalyzerErrors.styled'; @@ -47,6 +48,8 @@ const AnalyzerErrorsPopover = ({errors, onClose}: IProps) => { {analyzerError.errors[0].description} )} + + {analyzerError.ruleId && } ))} diff --git a/web/src/constants/Common.constants.ts b/web/src/constants/Common.constants.ts index fbacabd974..d74c269ff8 100644 --- a/web/src/constants/Common.constants.ts +++ b/web/src/constants/Common.constants.ts @@ -21,7 +21,8 @@ export const TRACE_DOCUMENTATION_URL = export const ADD_TEST_URL = 'https://docs.tracetest.io/web-ui/creating-tests'; export const ADD_TEST_OUTPUTS_DOCUMENTATION_URL = 'https://docs.tracetest.io/web-ui/creating-test-outputs'; -export const ANALYZER_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/tracetest-analyzer-concepts'; +export const ANALYZER_DOCUMENTATION_URL = 'https://docs.tracetest.io/analyzer/concepts'; +export const ANALYZER_RULES_DOCUMENTATION_URL = 'https://docs.tracetest.io/analyzer/rules'; export const EXPRESSIONS_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/expressions'; export const ENVIRONMENTS_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/environments'; diff --git a/web/src/models/LinterResult.model.ts b/web/src/models/LinterResult.model.ts index c15963fc76..3693ef05f6 100644 --- a/web/src/models/LinterResult.model.ts +++ b/web/src/models/LinterResult.model.ts @@ -40,6 +40,7 @@ function LinterResultPluginRuleResult({ } function LinterResultPluginRule({ + id = '', name = '', description = '', errorDescription = '', @@ -50,6 +51,7 @@ function LinterResultPluginRule({ level = 'error', }: TRawLinterResultPluginRule = {}): LinterResultPluginRule { return { + id, name, level: level as LinterRuleErrorLevel, description, diff --git a/web/src/redux/apis/__tests__/useCreateTestMutation.tesx.tsx b/web/src/redux/apis/__tests__/useCreateTestMutation.tesx.tsx index af3426e000..d13560fd41 100644 --- a/web/src/redux/apis/__tests__/useCreateTestMutation.tesx.tsx +++ b/web/src/redux/apis/__tests__/useCreateTestMutation.tesx.tsx @@ -19,8 +19,8 @@ test('useCreateTestMutation', async () => { spec: { name: 'New test', trigger: { - triggerType: 'http', - http: {url: 'https://google.com', method: HTTP_METHOD.GET}, + type: 'http', + httpRequest: {url: 'https://google.com', method: HTTP_METHOD.GET}, }, }, }).unwrap(); diff --git a/web/src/services/TestRun.service.ts b/web/src/services/TestRun.service.ts index 8fd2e659fd..e0ad41303f 100644 --- a/web/src/services/TestRun.service.ts +++ b/web/src/services/TestRun.service.ts @@ -49,6 +49,7 @@ const TestRunService = () => ({ .flatMap(rule => rule.results.map(result => ({ ...result, + ruleId: rule.id, ruleName: rule.name, ruleErrorDescription: rule.errorDescription, pluginName: rule.pluginName, diff --git a/web/src/types/Generated.types.ts b/web/src/types/Generated.types.ts index f5642f9fea..389e7f8c7b 100644 --- a/web/src/types/Generated.types.ts +++ b/web/src/types/Generated.types.ts @@ -1638,6 +1638,7 @@ export interface external { rules?: external["linters.yaml"]["components"]["schemas"]["LinterResultPluginRule"][]; }; LinterResultPluginRule: { + id?: string; name?: string; description?: string; errorDescription?: string; @@ -2042,16 +2043,11 @@ export interface external { Trigger: { /** @enum {string} */ type?: "http" | "grpc" | "traceid"; - /** @enum {string} */ - triggerType?: "http" | "grpc" | "traceid"; - http?: external["http.yaml"]["components"]["schemas"]["HTTPRequest"]; httpRequest?: external["http.yaml"]["components"]["schemas"]["HTTPRequest"]; grpc?: external["grpc.yaml"]["components"]["schemas"]["GRPCRequest"]; traceid?: external["traceid.yaml"]["components"]["schemas"]["TRACEIDRequest"]; }; TriggerResult: { - /** @enum {string} */ - triggerType?: "http" | "grpc" | "traceid"; /** @enum {string} */ type?: "http" | "grpc" | "traceid"; triggerResult?: { diff --git a/web/src/types/Test.types.ts b/web/src/types/Test.types.ts index 0ad2b75026..887a438eb8 100644 --- a/web/src/types/Test.types.ts +++ b/web/src/types/Test.types.ts @@ -6,7 +6,7 @@ import {VariableDefinition, Request} from 'postman-collection'; import {HTTP_METHOD, SupportedPlugins} from 'constants/Common.constants'; import {TracetestApiTags} from 'constants/Test.constants'; -import {Model, TGrpcSchemas, THttpSchemas, TTriggerSchemas} from './Common.types'; +import {Model, TGrpcSchemas, THttpSchemas} from './Common.types'; import {ICreateTestStep, IPlugin} from './Plugins.types'; import GRPCRequest from '../models/GrpcRequest.model'; import HttpRequest from '../models/HttpRequest.model'; @@ -16,7 +16,6 @@ export type TRequestAuth = THttpSchemas['HTTPRequest']['auth']; export type TMethod = THttpSchemas['HTTPRequest']['method']; export type TRawHeader = THttpSchemas['HTTPHeader']; export type TRawGRPCHeader = TGrpcSchemas['GRPCHeader']; -export type TTriggerType = Required; export type THeader = Model; export interface IRpcValues { diff --git a/web/src/types/TestRun.types.ts b/web/src/types/TestRun.types.ts index af3ffdb0ca..08db674917 100644 --- a/web/src/types/TestRun.types.ts +++ b/web/src/types/TestRun.types.ts @@ -12,6 +12,7 @@ export type TTestRunState = NonNullable