From 1454674474cf0118616bfd3290ce505fd12b47ac Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Wed, 14 Jun 2023 11:46:34 -0500 Subject: [PATCH 1/5] feat(frontend): add errors filter for analyzer results and ux/ui improvements --- .../AnalyzerResult/AnalyzerResult.styled.ts | 17 +- .../AnalyzerResult/AnalyzerResult.tsx | 19 +-- .../AnalyzerResult/CollapseIcon.tsx | 2 +- .../AnalyzerResult/GlobalResult.tsx | 19 ++- web/src/components/AnalyzerResult/Plugins.tsx | 158 ++++++++++-------- .../AnalyzerScore.styled.ts} | 4 +- .../AnalyzerScore/AnalyzerScore.tsx | 25 +++ .../components/AnalyzerScore/Percentage.tsx | 25 +++ .../{LintScore => AnalyzerScore}/index.ts | 2 +- web/src/components/LintScore/LintScore.tsx | 27 --- .../components/LintScore/PercentageScore.tsx | 18 -- .../TestSpecForm/AssertionCheck.tsx | 13 +- .../TestSpecForm/AssertionCheckList.tsx | 3 +- .../TooltipQuestion/TooltipQuestion.tsx | 2 +- web/src/constants/Common.constants.ts | 1 + 15 files changed, 184 insertions(+), 151 deletions(-) rename web/src/components/{LintScore/LintScore.styled.ts => AnalyzerScore/AnalyzerScore.styled.ts} (91%) create mode 100644 web/src/components/AnalyzerScore/AnalyzerScore.tsx create mode 100644 web/src/components/AnalyzerScore/Percentage.tsx rename web/src/components/{LintScore => AnalyzerScore}/index.ts (54%) delete mode 100644 web/src/components/LintScore/LintScore.tsx delete mode 100644 web/src/components/LintScore/PercentageScore.tsx diff --git a/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts b/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts index 59f095103e..0ecc122764 100644 --- a/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts +++ b/web/src/components/AnalyzerResult/AnalyzerResult.styled.ts @@ -3,6 +3,11 @@ import {Button, Collapse, Progress, Typography} from 'antd'; import styled from 'styled-components'; import noResultsIcon from 'assets/SpanAssertionsEmptyState.svg'; +export const StyledCollapse = styled(Collapse)` + background-color: ${({theme}) => theme.color.white}; + border: 0; +`; + export const Container = styled.div` padding: 24px; `; @@ -95,6 +100,8 @@ export const ScoreProgress = styled(Progress)` export const PluginPanel = styled(Collapse.Panel)` background-color: ${({theme}) => theme.color.white}; + border: ${({theme}) => `1px solid ${theme.color.border}`}; + margin-bottom: 12px; .ant-collapse-content { background-color: ${({theme}) => theme.color.background}; @@ -121,7 +128,7 @@ export const CollapseIconContainer = styled.div` position: absolute; top: 25%; right: 16px; - border-left: 1px solid ${({theme}) => theme.color.borderLight}}; + border-left: 1px solid ${({theme}) => theme.color.borderLight}; padding-left: 14px; height: 24px; align-items: center; @@ -163,3 +170,11 @@ export const EmptyTitle = styled(Typography.Title).attrs({level: 3})``; export const ConfigureButtonContainer = styled.div` margin-top: 6px; `; + +export const SwitchContainer = styled.div` + align-items: center; + display: flex; + gap: 8px; + justify-content: flex-end; + margin-bottom: 16px; +`; diff --git a/web/src/components/AnalyzerResult/AnalyzerResult.tsx b/web/src/components/AnalyzerResult/AnalyzerResult.tsx index 0673d9eff6..f8ee30d58d 100644 --- a/web/src/components/AnalyzerResult/AnalyzerResult.tsx +++ b/web/src/components/AnalyzerResult/AnalyzerResult.tsx @@ -1,13 +1,13 @@ import {Link} from 'react-router-dom'; +import BetaBadge from 'components/BetaBadge/BetaBadge'; +import {DISCORD_URL, OCTOLIINT_ISSUE_URL} from 'constants/Common.constants'; import LinterResult from 'models/LinterResult.model'; import Trace from 'models/Trace.model'; -import {DISCORD_URL, OCTOLIINT_ISSUE_URL} from 'constants/Common.constants'; import {useSettingsValues} from 'providers/SettingsValues/SettingsValues.provider'; import * as S from './AnalyzerResult.styled'; -import BetaBadge from '../BetaBadge/BetaBadge'; import Empty from './Empty'; -import Plugins from './Plugins'; import GlobalResult from './GlobalResult'; +import Plugins from './Plugins'; interface IProps { result: LinterResult; @@ -22,21 +22,18 @@ const AnalyzerResult = ({result: {score, minimumScore, plugins = []}, trace}: IP Analyzer Results + The Tracetest Analyzer is a plugin based framework used to analyze OpenTelemetry traces to help teams improve their instrumentation data, find potential problems and provide tips to fix the problems.{' '} - {linter.enabled ? ( + {linter.enabled && ( <> - If you want to disable the analyzer for all tests, go to the{' '} - settings page. + It can be globally disabled for all tests in the settings page.{' '} - ) : ( - '' )} - We have released this initial version to get feedback from the community. Have thoughts about how to improve the - Tracetest Analyzer? Add to this Issue or Discord! + We value your feedback on this beta release. Share your thoughts on Discord or add + them to this Issue. - {plugins.length ? ( <> diff --git a/web/src/components/AnalyzerResult/CollapseIcon.tsx b/web/src/components/AnalyzerResult/CollapseIcon.tsx index 7e22481eca..480269d05b 100644 --- a/web/src/components/AnalyzerResult/CollapseIcon.tsx +++ b/web/src/components/AnalyzerResult/CollapseIcon.tsx @@ -6,7 +6,7 @@ interface IProps { const CollapseIcon = ({isCollapsed}: IProps) => { return ( - {isCollapsed ? : } + {isCollapsed ? : } ); }; diff --git a/web/src/components/AnalyzerResult/GlobalResult.tsx b/web/src/components/AnalyzerResult/GlobalResult.tsx index 1da2dc71e7..d023e22ac5 100644 --- a/web/src/components/AnalyzerResult/GlobalResult.tsx +++ b/web/src/components/AnalyzerResult/GlobalResult.tsx @@ -1,6 +1,7 @@ +import Percentage from 'components/AnalyzerScore/Percentage'; +import {TooltipQuestion} from 'components/TooltipQuestion/TooltipQuestion'; +import {ANALYZER_DOCUMENTATION_URL} from 'constants/Common.constants'; import * as S from './AnalyzerResult.styled'; -import PercentageScore from '../LintScore/PercentageScore'; -import {TooltipQuestion} from '../TooltipQuestion/TooltipQuestion'; interface IProps { score: number; @@ -15,10 +16,20 @@ const GlobalResult = ({score, minimumScore}: IProps) => { Overall Trace Analyzer Score - + + Tracetest core system supports analyzer evaluation as part of the testing capabilities.{' '} + + Learn more + {' '} + about the Analyzer. + + } + /> - + {!!minimumScore && ( diff --git a/web/src/components/AnalyzerResult/Plugins.tsx b/web/src/components/AnalyzerResult/Plugins.tsx index c1eb23725e..1494b7ebcc 100644 --- a/web/src/components/AnalyzerResult/Plugins.tsx +++ b/web/src/components/AnalyzerResult/Plugins.tsx @@ -1,14 +1,16 @@ -import {useCallback} from 'react'; import {CaretUpFilled} from '@ant-design/icons'; -import {Collapse, Space, Tooltip, Typography} from 'antd'; -import {useAppDispatch} from 'redux/hooks'; -import {selectSpan} from 'redux/slices/Trace.slice'; +import {Space, Switch, Tooltip, Typography} from 'antd'; +import {useCallback, useState} from 'react'; +import AnalyzerScore from 'components/AnalyzerScore/AnalyzerScore'; import LinterResult from 'models/LinterResult.model'; import Trace from 'models/Trace.model'; import Span from 'models/Span.model'; -import CollapseIcon from './CollapseIcon'; -import LintScore from '../LintScore/LintScore'; +import {useAppDispatch} from 'redux/hooks'; +import {selectSpan} from 'redux/slices/Trace.slice'; import * as S from './AnalyzerResult.styled'; +import CollapseIcon from './CollapseIcon'; + +const MAX_SCORE = 100; interface IProps { plugins: LinterResult['plugins']; @@ -22,6 +24,7 @@ function getSpanName(spans: Span[], spanId: string) { const Plugins = ({plugins, trace}: IProps) => { const dispatch = useAppDispatch(); + const [onlyErrors, setOnlyErrors] = useState(false); const onSpanResultClick = useCallback( (spanId: string) => { @@ -31,74 +34,87 @@ const Plugins = ({plugins, trace}: IProps) => { ); return ( - }> - {plugins.map(plugin => ( - - - {plugin.name} - {plugin.description} - - } - key={plugin.name} - > - {plugin.rules.map(rule => ( - - - - - {rule.passed ? : } - - {rule.name} - - - - - {rule.description} - - + <> + + setOnlyErrors(prev => !prev)} /> + + + + }> + {plugins + .filter(plugin => !onlyErrors || plugin.score < MAX_SCORE) + .map(plugin => ( + + + {plugin.name} + {plugin.description} + + } + key={plugin.name} + > + {plugin.rules + .filter(rule => !onlyErrors || !rule.passed) + .map(rule => ( + + + + + {rule.passed ? : } + + {rule.name} + + + + + {rule.description} + + - - {rule?.results?.map((result, resultIndex) => ( - // eslint-disable-next-line react/no-array-index-key -
- {result.passed ? ( - } - onClick={() => onSpanResultClick(result.spanId)} - type="link" - > - {getSpanName(trace.spans, result.spanId)} - - ) : ( - <> - } - onClick={() => onSpanResultClick(result.spanId)} - type="link" - $error - > - {getSpanName(trace.spans, result.spanId)} - -
- {result.errors.map((error, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {error} -
- ))} -
- - )} -
+ + {rule?.results + ?.filter(result => !onlyErrors || !result.passed) + ?.map((result, resultIndex) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {result.passed ? ( + } + onClick={() => onSpanResultClick(result.spanId)} + type="link" + > + {getSpanName(trace.spans, result.spanId)} + + ) : ( + <> + } + onClick={() => onSpanResultClick(result.spanId)} + type="link" + $error + > + {getSpanName(trace.spans, result.spanId)} + +
+ {result.errors.map((error, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {error} +
+ ))} +
+ + )} +
+ ))} +
+
))} - -
+
))} - - ))} -
+ + ); }; diff --git a/web/src/components/LintScore/LintScore.styled.ts b/web/src/components/AnalyzerScore/AnalyzerScore.styled.ts similarity index 91% rename from web/src/components/LintScore/LintScore.styled.ts rename to web/src/components/AnalyzerScore/AnalyzerScore.styled.ts index d72a1eb7e9..050fc2fce0 100644 --- a/web/src/components/LintScore/LintScore.styled.ts +++ b/web/src/components/AnalyzerScore/AnalyzerScore.styled.ts @@ -15,9 +15,9 @@ export const ScoreTexContainer = styled.div` height: 100%; `; -export const Score = styled(Typography.Title)` +export const Score = styled(Typography.Title)<{$isLarge?: boolean}>` && { - font-size: 12px; + font-size: ${({$isLarge}) => ($isLarge ? '24px' : '12px')}; margin-bottom: 0; } `; diff --git a/web/src/components/AnalyzerScore/AnalyzerScore.tsx b/web/src/components/AnalyzerScore/AnalyzerScore.tsx new file mode 100644 index 0000000000..e119dc6b13 --- /dev/null +++ b/web/src/components/AnalyzerScore/AnalyzerScore.tsx @@ -0,0 +1,25 @@ +import * as S from './AnalyzerScore.styled'; + +interface IProps { + score: number; + height?: string; + width?: string; +} + +const AnalyzerScore = ({score, height, width}: IProps) => ( + + + {score} + + ''} + percent={score || 100} + $score={score} + type="circle" + /> + +); + +export default AnalyzerScore; diff --git a/web/src/components/AnalyzerScore/Percentage.tsx b/web/src/components/AnalyzerScore/Percentage.tsx new file mode 100644 index 0000000000..f2720fb6a0 --- /dev/null +++ b/web/src/components/AnalyzerScore/Percentage.tsx @@ -0,0 +1,25 @@ +import * as S from './AnalyzerScore.styled'; + +interface IProps { + score: number; + height?: string; + width?: string; +} + +const Percentage = ({score, height, width}: IProps) => ( + + + {score}% + + ''} + percent={score || 100} + type="circle" + /> + +); + +export default Percentage; diff --git a/web/src/components/LintScore/index.ts b/web/src/components/AnalyzerScore/index.ts similarity index 54% rename from web/src/components/LintScore/index.ts rename to web/src/components/AnalyzerScore/index.ts index 4b8dc04e4e..dba4263954 100644 --- a/web/src/components/LintScore/index.ts +++ b/web/src/components/AnalyzerScore/index.ts @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export {default} from './LintScore'; +export {default} from './AnalyzerScore'; diff --git a/web/src/components/LintScore/LintScore.tsx b/web/src/components/LintScore/LintScore.tsx deleted file mode 100644 index 402039651f..0000000000 --- a/web/src/components/LintScore/LintScore.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as S from './LintScore.styled'; - -interface IProps { - score: number; - height?: string; - width?: string; -} - -const LintScore = ({score, height, width}: IProps) => { - return ( - - - {score} - - ''} - percent={score || 100} - $score={score} - type="circle" - /> - - ); -}; - -export default LintScore; diff --git a/web/src/components/LintScore/PercentageScore.tsx b/web/src/components/LintScore/PercentageScore.tsx deleted file mode 100644 index 5f4e674f47..0000000000 --- a/web/src/components/LintScore/PercentageScore.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as S from './LintScore.styled'; - -interface IProps { - score: number; - height?: string; - width?: string; -} - -const Percentage = ({score, height, width}: IProps) => { - return ( - - {score}% - ''} percent={score || 100} type="circle" /> - - ); -}; - -export default Percentage; diff --git a/web/src/components/TestSpecForm/AssertionCheck.tsx b/web/src/components/TestSpecForm/AssertionCheck.tsx index 210315017a..c28cd284a2 100644 --- a/web/src/components/TestSpecForm/AssertionCheck.tsx +++ b/web/src/components/TestSpecForm/AssertionCheck.tsx @@ -24,7 +24,6 @@ interface IProps { field: Pick; name: number; attributeList: TSpanFlatAttribute[]; - index: number; runId: string; testId: string; spanIdList: string[]; @@ -35,17 +34,7 @@ const operatorList = Object.values(CompareOperator).map(value => ({ label: OperatorService.getOperatorSymbol(value), })); -export const AssertionCheck = ({ - field, - index, - name, - remove, - attributeList, - runId, - testId, - spanIdList, - form, -}: IProps) => { +export const AssertionCheck = ({field, name, remove, attributeList, runId, testId, spanIdList, form}: IProps) => { const [selectedAttributeKey, setSelectedAttributeKey] = useState(''); const onAttributeFocus = useCallback((view: EditorView) => { if (!view?.state.doc.length) delay(() => startCompletion(view!), 0); diff --git a/web/src/components/TestSpecForm/AssertionCheckList.tsx b/web/src/components/TestSpecForm/AssertionCheckList.tsx index 00ec5601f9..985b9b1b54 100644 --- a/web/src/components/TestSpecForm/AssertionCheckList.tsx +++ b/web/src/components/TestSpecForm/AssertionCheckList.tsx @@ -24,7 +24,7 @@ const AssertionCheckList = ({form, fields, add, remove, attributeList, runId, te return ( - {fields.map(({key, name, ...field}, index) => { + {fields.map(({key, name, ...field}) => { return ( { diff --git a/web/src/constants/Common.constants.ts b/web/src/constants/Common.constants.ts index 4f710cae9d..855e2950ff 100644 --- a/web/src/constants/Common.constants.ts +++ b/web/src/constants/Common.constants.ts @@ -20,6 +20,7 @@ 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 EXPRESSIONS_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/expressions'; export const ENVIRONMENTS_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/environments'; From aeb839ea175d57ffa1ab126db2ebfc08a1b55897 Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Wed, 14 Jun 2023 15:26:15 -0500 Subject: [PATCH 2/5] add analyzer service --- web/src/components/AnalyzerResult/Plugins.tsx | 140 +++++++++--------- web/src/services/Analyzer.service.ts | 18 +++ 2 files changed, 85 insertions(+), 73 deletions(-) create mode 100644 web/src/services/Analyzer.service.ts diff --git a/web/src/components/AnalyzerResult/Plugins.tsx b/web/src/components/AnalyzerResult/Plugins.tsx index 1494b7ebcc..57eb5a59a0 100644 --- a/web/src/components/AnalyzerResult/Plugins.tsx +++ b/web/src/components/AnalyzerResult/Plugins.tsx @@ -7,11 +7,10 @@ import Trace from 'models/Trace.model'; import Span from 'models/Span.model'; import {useAppDispatch} from 'redux/hooks'; import {selectSpan} from 'redux/slices/Trace.slice'; +import AnalyzerService from 'services/Analyzer.service'; import * as S from './AnalyzerResult.styled'; import CollapseIcon from './CollapseIcon'; -const MAX_SCORE = 100; - interface IProps { plugins: LinterResult['plugins']; trace: Trace; @@ -22,9 +21,10 @@ function getSpanName(spans: Span[], spanId: string) { return span?.name ?? ''; } -const Plugins = ({plugins, trace}: IProps) => { +const Plugins = ({plugins: rawPlugins, trace}: IProps) => { const dispatch = useAppDispatch(); const [onlyErrors, setOnlyErrors] = useState(false); + const plugins = AnalyzerService.getPlugins(rawPlugins, onlyErrors); const onSpanResultClick = useCallback( (spanId: string) => { @@ -41,78 +41,72 @@ const Plugins = ({plugins, trace}: IProps) => { }> - {plugins - .filter(plugin => !onlyErrors || plugin.score < MAX_SCORE) - .map(plugin => ( - - - {plugin.name} - {plugin.description} - - } - key={plugin.name} - > - {plugin.rules - .filter(rule => !onlyErrors || !rule.passed) - .map(rule => ( - - - - - {rule.passed ? : } - - {rule.name} - - - - - {rule.description} - - + {plugins.map(plugin => ( + + + {plugin.name} + {plugin.description} + + } + key={plugin.name} + > + {plugin.rules.map(rule => ( + + + + + {rule.passed ? : } + + {rule.name} + + + + + {rule.description} + + - - {rule?.results - ?.filter(result => !onlyErrors || !result.passed) - ?.map((result, resultIndex) => ( - // eslint-disable-next-line react/no-array-index-key -
- {result.passed ? ( - } - onClick={() => onSpanResultClick(result.spanId)} - type="link" - > - {getSpanName(trace.spans, result.spanId)} - - ) : ( - <> - } - onClick={() => onSpanResultClick(result.spanId)} - type="link" - $error - > - {getSpanName(trace.spans, result.spanId)} - -
- {result.errors.map((error, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {error} -
- ))} -
- - )} + + {rule?.results?.map((result, resultIndex) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {result.passed ? ( + } + onClick={() => onSpanResultClick(result.spanId)} + type="link" + > + {getSpanName(trace.spans, result.spanId)} + + ) : ( + <> + } + onClick={() => onSpanResultClick(result.spanId)} + type="link" + $error + > + {getSpanName(trace.spans, result.spanId)} + +
+ {result.errors.map((error, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {error} +
+ ))}
- ))} - - - ))} - - ))} + + )} +
+ ))} +
+ + ))} + + ))} ); diff --git a/web/src/services/Analyzer.service.ts b/web/src/services/Analyzer.service.ts new file mode 100644 index 0000000000..12600cc319 --- /dev/null +++ b/web/src/services/Analyzer.service.ts @@ -0,0 +1,18 @@ +import LinterResult from 'models/LinterResult.model'; + +const MAX_PLUGIN_SCORE = 100; + +const AnalyzerService = () => ({ + getPlugins(plugins: LinterResult['plugins'], showOnlyErrors: boolean): LinterResult['plugins'] { + return plugins + .filter(plugin => !showOnlyErrors || plugin.score < MAX_PLUGIN_SCORE) + .map(plugin => ({ + ...plugin, + rules: plugin.rules + .filter(rule => !showOnlyErrors || !rule.passed) + .map(rule => ({...rule, results: rule?.results?.filter(result => !showOnlyErrors || !result.passed)})), + })); + }, +}); + +export default AnalyzerService(); From 8142e93083b0b964efa4f851237848d22e83bfd5 Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Wed, 14 Jun 2023 16:58:32 -0500 Subject: [PATCH 3/5] feat(frontend): add analyzer score summary --- api/tests.yaml | 3 ++ cli/openapi/model_test_summary_last_run.go | 40 +++++++++++++++++-- server/http/mappings/tests.go | 7 ++-- server/model/tests.go | 7 ++-- server/openapi/model_test_summary_last_run.go | 2 + server/testdb/tests.go | 13 +++++- .../ResourceCard/ResourceCard.styled.ts | 2 +- .../ResourceCard/ResourceCardSummary.tsx | 14 ++++++- web/src/components/RunCard/RunCard.styled.ts | 3 +- web/src/components/RunCard/TestRunCard.tsx | 25 +++++++++++- web/src/models/Summary.model.ts | 2 + web/src/types/Generated.types.ts | 1 + 12 files changed, 102 insertions(+), 17 deletions(-) diff --git a/api/tests.yaml b/api/tests.yaml index 094eedb876..d419399976 100644 --- a/api/tests.yaml +++ b/api/tests.yaml @@ -73,6 +73,9 @@ components: fails: type: integer readOnly: true + analyzerScore: + type: integer + readOnly: true TestSpecs: type: object diff --git a/cli/openapi/model_test_summary_last_run.go b/cli/openapi/model_test_summary_last_run.go index 7aabfd1f30..2d42718a63 100644 --- a/cli/openapi/model_test_summary_last_run.go +++ b/cli/openapi/model_test_summary_last_run.go @@ -20,9 +20,10 @@ var _ MappedNullable = &TestSummaryLastRun{} // TestSummaryLastRun struct for TestSummaryLastRun type TestSummaryLastRun struct { - Time NullableTime `json:"time,omitempty"` - Passes *int32 `json:"passes,omitempty"` - Fails *int32 `json:"fails,omitempty"` + Time NullableTime `json:"time,omitempty"` + Passes *int32 `json:"passes,omitempty"` + Fails *int32 `json:"fails,omitempty"` + AnalyzerScore *int32 `json:"analyzerScore,omitempty"` } // NewTestSummaryLastRun instantiates a new TestSummaryLastRun object @@ -149,6 +150,38 @@ func (o *TestSummaryLastRun) SetFails(v int32) { o.Fails = &v } +// GetAnalyzerScore returns the AnalyzerScore field value if set, zero value otherwise. +func (o *TestSummaryLastRun) GetAnalyzerScore() int32 { + if o == nil || isNil(o.AnalyzerScore) { + var ret int32 + return ret + } + return *o.AnalyzerScore +} + +// GetAnalyzerScoreOk returns a tuple with the AnalyzerScore field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *TestSummaryLastRun) GetAnalyzerScoreOk() (*int32, bool) { + if o == nil || isNil(o.AnalyzerScore) { + return nil, false + } + return o.AnalyzerScore, true +} + +// HasAnalyzerScore returns a boolean if a field has been set. +func (o *TestSummaryLastRun) HasAnalyzerScore() bool { + if o != nil && !isNil(o.AnalyzerScore) { + return true + } + + return false +} + +// SetAnalyzerScore gets a reference to the given int32 and assigns it to the AnalyzerScore field. +func (o *TestSummaryLastRun) SetAnalyzerScore(v int32) { + o.AnalyzerScore = &v +} + func (o TestSummaryLastRun) MarshalJSON() ([]byte, error) { toSerialize, err := o.ToMap() if err != nil { @@ -164,6 +197,7 @@ func (o TestSummaryLastRun) ToMap() (map[string]interface{}, error) { } // skip: passes is readOnly // skip: fails is readOnly + // skip: analyzerScore is readOnly return toSerialize, nil } diff --git a/server/http/mappings/tests.go b/server/http/mappings/tests.go index bc091788c0..d359d2d9fb 100644 --- a/server/http/mappings/tests.go +++ b/server/http/mappings/tests.go @@ -65,9 +65,10 @@ func (m OpenAPI) Test(in model.Test) openapi.Test { Summary: openapi.TestSummary{ Runs: int32(in.Summary.Runs), LastRun: openapi.TestSummaryLastRun{ - Time: optionalTime(in.Summary.LastRun.Time), - Passes: int32(in.Summary.LastRun.Passes), - Fails: int32(in.Summary.LastRun.Fails), + Time: optionalTime(in.Summary.LastRun.Time), + Passes: int32(in.Summary.LastRun.Passes), + Fails: int32(in.Summary.LastRun.Fails), + AnalyzerScore: int32(in.Summary.LastRun.AnalyzerScore), }, }, } diff --git a/server/model/tests.go b/server/model/tests.go index dc2c4ac2e2..fade12cb4e 100644 --- a/server/model/tests.go +++ b/server/model/tests.go @@ -40,9 +40,10 @@ type ( } LastRun struct { - Time time.Time `json:"time"` - Passes int `json:"passes"` - Fails int `json:"fails"` + Time time.Time `json:"time"` + Passes int `json:"passes"` + Fails int `json:"fails"` + AnalyzerScore int `json:"analyzerScore"` } TriggerType string diff --git a/server/openapi/model_test_summary_last_run.go b/server/openapi/model_test_summary_last_run.go index 15c6259ffe..8404b7f442 100644 --- a/server/openapi/model_test_summary_last_run.go +++ b/server/openapi/model_test_summary_last_run.go @@ -19,6 +19,8 @@ type TestSummaryLastRun struct { Passes int32 `json:"passes,omitempty"` Fails int32 `json:"fails,omitempty"` + + AnalyzerScore int32 `json:"analyzerScore,omitempty"` } // AssertTestSummaryLastRunRequired checks if the required fields are not zero-ed diff --git a/server/testdb/tests.go b/server/testdb/tests.go index 5950c898b5..c906e3f065 100644 --- a/server/testdb/tests.go +++ b/server/testdb/tests.go @@ -163,7 +163,8 @@ const ( (SELECT COUNT(*) FROM test_runs tr WHERE tr.test_id = t.id) as total_runs, last_test_run.created_at as last_test_run_time, last_test_run.pass as last_test_run_pass, - last_test_run.fail as last_test_run_fail + last_test_run.fail as last_test_run_fail, + last_test_run.linter as last_test_run_linter FROM tests t LEFT OUTER JOIN ( SELECT MAX(id) as id, test_id FROM test_runs GROUP BY test_id @@ -316,7 +317,8 @@ func (td *postgresDB) readTestRow(ctx context.Context, row scanner) (model.Test, var ( jsonServiceUnderTest, jsonSpecs, - jsonOutputs []byte + jsonOutputs, + jsonLinter []byte lastRunTime *time.Time @@ -335,6 +337,7 @@ func (td *postgresDB) readTestRow(ctx context.Context, row scanner) (model.Test, &lastRunTime, &pass, &fail, + &jsonLinter, ) switch err { @@ -364,6 +367,12 @@ func (td *postgresDB) readTestRow(ctx context.Context, row scanner) (model.Test, test.Summary.LastRun.Fails = *fail } + var linter model.LinterResult + err = json.Unmarshal(jsonLinter, &linter) + if err == nil { + test.Summary.LastRun.AnalyzerScore = linter.Score + } + return test, nil case sql.ErrNoRows: return model.Test{}, ErrNotFound diff --git a/web/src/components/ResourceCard/ResourceCard.styled.ts b/web/src/components/ResourceCard/ResourceCard.styled.ts index a5143000fa..54e5a057f8 100644 --- a/web/src/components/ResourceCard/ResourceCard.styled.ts +++ b/web/src/components/ResourceCard/ResourceCard.styled.ts @@ -130,7 +130,7 @@ export const RunsListContainer = styled.div` export const TestContainer = styled.div` cursor: pointer; display: grid; - grid-template-columns: auto auto 1fr 100px auto auto; + grid-template-columns: auto auto 1fr 28px 100px auto auto; align-items: center; gap: 18px; padding: 15px 24px; diff --git a/web/src/components/ResourceCard/ResourceCardSummary.tsx b/web/src/components/ResourceCard/ResourceCardSummary.tsx index 0b41e66868..03d57c1800 100644 --- a/web/src/components/ResourceCard/ResourceCardSummary.tsx +++ b/web/src/components/ResourceCard/ResourceCardSummary.tsx @@ -1,6 +1,7 @@ import {Tooltip} from 'antd'; -import Date from 'utils/Date'; +import AnalyzerScore from 'components/AnalyzerScore'; import Summary from 'models/Summary.model'; +import Date from 'utils/Date'; import * as S from './ResourceCard.styled'; interface IProps { @@ -9,10 +10,19 @@ interface IProps { const ResourceCardSummary = ({ summary: { - lastRun: {time, passes, fails}, + lastRun: {time, passes, fails, analyzerScore}, }, }: IProps) => ( <> +
+ {!!analyzerScore && ( + +
+ +
+
+ )} +
Last run result: diff --git a/web/src/components/RunCard/RunCard.styled.ts b/web/src/components/RunCard/RunCard.styled.ts index 7d54689320..4bcbb50ed9 100644 --- a/web/src/components/RunCard/RunCard.styled.ts +++ b/web/src/components/RunCard/RunCard.styled.ts @@ -71,6 +71,7 @@ export const Title = styled(Typography.Title).attrs({level: 3})` } `; -export const Row = styled.div` +export const Row = styled.div<{$minWidth?: number}>` display: flex; + min-width: ${({$minWidth}) => $minWidth && `${$minWidth}px`}; `; diff --git a/web/src/components/RunCard/TestRunCard.tsx b/web/src/components/RunCard/TestRunCard.tsx index 7c5fb3b3e4..cf02fc2dc8 100644 --- a/web/src/components/RunCard/TestRunCard.tsx +++ b/web/src/components/RunCard/TestRunCard.tsx @@ -1,11 +1,15 @@ import {Tooltip} from 'antd'; -import {Link} from 'react-router-dom'; +import {Link, useNavigate} from 'react-router-dom'; +import AnalyzerScore from 'components/AnalyzerScore'; import RunActionsMenu from 'components/RunActionsMenu'; import TestState from 'components/TestState'; import TestRun, {isRunStateFailed, isRunStateFinished, isRunStateStopped} from 'models/TestRun.model'; import Date from 'utils/Date'; import * as S from './RunCard.styled'; +const TEST_RUN_TRACE_TAB = 'trace'; +const TEST_RUN_TEST_TAB = 'test'; + interface IProps { run: TestRun; testId: string; @@ -42,11 +46,20 @@ const TestRunCard = ({ testId, linkTo, }: IProps) => { + const navigate = useNavigate(); const metadataName = metadata?.name; const metadataBuildNumber = metadata?.buildNumber; const metadataBranch = metadata?.branch; const metadataUrl = metadata?.url; + const handleResultClick = ( + event: React.MouseEvent, + type: typeof TEST_RUN_TRACE_TAB | typeof TEST_RUN_TEST_TAB + ) => { + event.preventDefault(); + navigate(`${linkTo}/${type}`); + }; + return ( @@ -80,8 +93,16 @@ const TestRunCard = ({
)} + {isRunStateFinished(state) && !!linter.plugins.length && ( + +
handleResultClick(event, TEST_RUN_TRACE_TAB)}> + +
+
+ )} + {isRunStateFinished(state) && ( - + handleResultClick(event, TEST_RUN_TEST_TAB)}> diff --git a/web/src/models/Summary.model.ts b/web/src/models/Summary.model.ts index b86bf7bcfb..e8971b5b60 100644 --- a/web/src/models/Summary.model.ts +++ b/web/src/models/Summary.model.ts @@ -8,6 +8,7 @@ type Summary = { time: string; passes: number; fails: number; + analyzerScore: number; }; }; @@ -18,6 +19,7 @@ const Summary = (summary: TRawTestSummary = {}): Summary => ({ time: summary.lastRun?.time ?? '', passes: summary.lastRun?.passes ?? 0, fails: summary.lastRun?.fails ?? 0, + analyzerScore: summary.lastRun?.analyzerScore ?? 0, }, }); diff --git a/web/src/types/Generated.types.ts b/web/src/types/Generated.types.ts index 71fb23991c..9cf1903642 100644 --- a/web/src/types/Generated.types.ts +++ b/web/src/types/Generated.types.ts @@ -1814,6 +1814,7 @@ export interface external { time?: string | null; passes?: number; fails?: number; + analyzerScore?: number; }; }; /** @example [object Object] */ From 6ee89f78a383c47e93a73160210143ba5593775b Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Thu, 15 Jun 2023 10:22:25 -0500 Subject: [PATCH 4/5] fix tests --- server/tests/transactions_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/tests/transactions_test.go b/server/tests/transactions_test.go index 822a07fa42..0d8f585f5e 100644 --- a/server/tests/transactions_test.go +++ b/server/tests/transactions_test.go @@ -303,7 +303,8 @@ func TestTransactions(t *testing.T) { "lastRun": { "time": "REMOVEME", "passes": 2, - "fails": 1 + "fails": 1, + "analyzerScore": 0 } } }, @@ -340,7 +341,8 @@ func TestTransactions(t *testing.T) { "lastRun": { "fails": 0, "passes": 0, - "time": "REMOVEME" + "time": "REMOVEME", + "analyzerScore": 0 } } } @@ -350,7 +352,8 @@ func TestTransactions(t *testing.T) { "lastRun": { "fails": 1, "passes": 2, - "time": "REMOVEME" + "time": "REMOVEME", + "analyzerScore": 0 } } } From 3b8f53e3eded6d31879b76f4267432e5cd108ebd Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Thu, 15 Jun 2023 10:41:54 -0500 Subject: [PATCH 5/5] fix css styles --- web/src/components/ResourceCard/ResourceCard.styled.ts | 2 +- web/src/components/ResourceCard/TestCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/ResourceCard/ResourceCard.styled.ts b/web/src/components/ResourceCard/ResourceCard.styled.ts index 54e5a057f8..4b89270db9 100644 --- a/web/src/components/ResourceCard/ResourceCard.styled.ts +++ b/web/src/components/ResourceCard/ResourceCard.styled.ts @@ -130,7 +130,7 @@ export const RunsListContainer = styled.div` export const TestContainer = styled.div` cursor: pointer; display: grid; - grid-template-columns: auto auto 1fr 28px 100px auto auto; + grid-template-columns: auto auto 1fr 28px auto auto auto; align-items: center; gap: 18px; padding: 15px 24px; diff --git a/web/src/components/ResourceCard/TestCard.tsx b/web/src/components/ResourceCard/TestCard.tsx index 381094c459..d04d748fa4 100644 --- a/web/src/components/ResourceCard/TestCard.tsx +++ b/web/src/components/ResourceCard/TestCard.tsx @@ -62,7 +62,7 @@ const TestCard = ({onEdit, onDelete, onRun, onViewAll, test}: IProps) => { shouldEdit={shouldEdit} onDelete={() => onDelete(test.id, test.name, ResourceType.Test)} onEdit={() => onEdit(test.id, lastRunId, ResourceType.Test)} - /> + />