Feature Implement ml data collection progress bar and test case ml indicators
MODIFY
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/JsonReport.java
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/JsonReport.java
@@ -25,15 +25,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

MODIFY
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/beans/TestCase.java
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/beans/TestCase.java
@@ -24,7 +24,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+import com.exactpro.sf.scriptrunner.StatusType;
import com.exactpro.sf.scriptrunner.impl.jsonreport.IJsonReportNode;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -90,6 +92,38 @@ public class TestCase implements IJsonReportNode {
return this.bugToCategoryMap;
}
+ @JsonIgnore
+ public List<Action> getRootActions() {
+ return this.actions.stream().filter(n -> n instanceof Action).map(n -> (Action)n).collect(Collectors.toList());
+ }
+
+ @JsonIgnore
+ public Long getFirstActionId() {
+ List<Action> rootActions = getRootActions();
+ if (rootActions.size() == 0) {
+ return null;
+ }
+ else {
+ return rootActions.get(0).getId();
+ }
+ }
+
+ @JsonIgnore
+ public Long getLastActionId() {
+ List<Action> rootActions = getRootActions();
+ if (rootActions.size() == 0) {
+ return null;
+ }
+ else {
+ return rootActions.get(rootActions.size() - 1).getId();
+ }
+ }
+
+ @JsonIgnore
+ public int getFailedActionsCount() {
+ return (int)getRootActions().stream().filter(a -> a.getStatus().getStatus() == StatusType.FAILED).count();
+ }
+
public List<IJsonReportNode> getActions() {
return actions;
}

MODIFY
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/beans/TestCaseMetadata.java
BackEnd/Core/sailfish-core/src/main/java/com/exactpro/sf/scriptrunner/impl/jsonreport/beans/TestCaseMetadata.java
@@ -32,6 +32,9 @@ public class TestCaseMetadata {
private String id;
private int hash;
private String description;
+ private Long firstActionId;
+ private Long lastActionId;
+ private int failedActionCount;
private Collection<IJsonReportNode> bugs;
@@ -55,6 +58,9 @@ public class TestCaseMetadata {
this.jsonFileName = jsonFileName;
this.jsonpFileName = jsonpFileName;
this.tags = testCase.getTags();
+ this.firstActionId = testCase.getFirstActionId();
+ this.lastActionId = testCase.getLastActionId();
+ this.failedActionCount = testCase.getFailedActionsCount();
}
@@ -153,4 +159,28 @@ public class TestCaseMetadata {
public void setJsonpFileName(String jsonpFileName) {
this.jsonpFileName = jsonpFileName;
}
+
+ public Long getFirstActionId() {
+ return firstActionId;
+ }
+
+ public void setFirstActionId(Long firstActionId) {
+ this.firstActionId = firstActionId;
+ }
+
+ public Long getLastActionId() {
+ return lastActionId;
+ }
+
+ public void setLastActionId(Long lastActionId) {
+ this.lastActionId = lastActionId;
+ }
+
+ public int getFailedActionCount() {
+ return failedActionCount;
+ }
+
+ public void setFailedActionCount(int failedActionCount) {
+ this.failedActionCount = failedActionCount;
+ }
}

MODIFY
FrontEnd/Sailfish-JS-report/src/components/ReportLayout.tsx
FrontEnd/Sailfish-JS-report/src/components/ReportLayout.tsx
@@ -32,12 +32,16 @@ import { ToggleButton } from './ToggleButton';
import RunInformation from './RunInformation';
import ReportSummary from './ReportSummary';
import Tag from './Tag';
+import { SubmittedData } from '../models/MlServiceResponse';
+import TestCase from '../models/TestCase';
const OLD_REPORT_PATH = 'report.html';
interface StateProps {
report: ReportState;
selectedTestCaseId: string;
+ submittedData: SubmittedData[];
+ mlEnabled: boolean;
}
interface Props extends StateProps { }
@@ -51,6 +55,7 @@ interface State {
export class ReportLayoutBase extends React.Component<Props, State> {
+
constructor(props) {
super(props);
@@ -79,6 +84,24 @@ export class ReportLayoutBase extends React.Component<Props, State> {
})
}
+ isMLSubmitted(metadata: TestCaseMetadata) {
+ const { submittedData } = this.props;
+ const submittedCount = [...new Set(submittedData.map(m => m.actionId))]
+ .filter(actionId => actionId >= metadata.firstActionId &&
+ actionId <= metadata.lastActionId).length;
+
+ return submittedCount === metadata.failedActionCount && submittedCount != 0;
+ }
+
+ calculateMLStats() {
+ const { submittedData, report } = this.props;
+
+ let uniqueActionId = new Set(submittedData.map(m => m.actionId)).size,
+ failedActions = report.metadata.map(m => m.failedActionCount).reduce((a,b) => a + b);
+
+ return uniqueActionId / failedActions
+ }
+
render() {
const changePanel= (panel: Panel) => {
return () => {
@@ -87,14 +110,18 @@ export class ReportLayoutBase extends React.Component<Props, State> {
}
const { report, selectedTestCaseId } = this.props,
- { showKnownBugs } = this.state;
+ { showKnownBugs } = this.state,
+ mlStats = (this.calculateMLStats() * 100).toPrecision(2) + "%";
const filteredMetadata = report.metadata.filter(isTestCaseMetadata),
knownBugsPresent = filteredMetadata.some(item => item.bugs != null && item.bugs.length > 0),
knownBugsClass = showKnownBugs ? "active" : "enabled",
failedTestCasesEnabled = filteredMetadata.some(({status}) => status.status === StatusType.FAILED),
failedTcTitleClass = createBemElement('report', 'title', failedTestCasesEnabled ? 'failed': 'disabled'),
- isLive = report.finishTime == null;
+ isLive = report.finishTime == null,
+ alerts = report.alerts || [],
+ tags = report.tags || [],
+ mlStatsClass = createStyleSelector('ml-stats', this.props.mlEnabled? null: "hidden");
const knownBugsButton = (
knownBugsPresent ?
@@ -135,6 +162,13 @@ export class ReportLayoutBase extends React.Component<Props, State> {
</div>
<FailedTestCaseCarousel/>
{knownBugsButton}
+ <div className={mlStatsClass}>
+ <span className="ml-stats__title">ML Collected</span>
+ <div className="ml-stats__bar">
+ <div className="ml-stats__progress-bar" style={{width: mlStats}}/>
+ </div>
+ <span className="ml-stats__percents">{mlStats}</span>
+ </div>
</div>
<div className="report__summary">
<div className="report-summary">
@@ -179,7 +213,8 @@ export class ReportLayoutBase extends React.Component<Props, State> {
isSelected={metadata.id === selectedTestCaseId}
key={index}
metadata={metadata}
- index={index + 1}/>
+ index={index + 1}
+ isMLSubmitted={this.isMLSubmitted(metadata)}/>
))
}
</HeatmapScrollbar>
@@ -197,7 +232,9 @@ export class ReportLayoutBase extends React.Component<Props, State> {
const ReportLayout = connect(
(state: AppState): StateProps => ({
report: state.report,
- selectedTestCaseId: state.selected.selectedTestCaseId
+ selectedTestCaseId: state.selected.selectedTestCaseId,
+ submittedData: state.machineLearning.submittedData,
+ mlEnabled: state.machineLearning.token != null
})
)(ReportLayoutBase);

MODIFY
FrontEnd/Sailfish-JS-report/src/components/TestCaseCard.tsx
FrontEnd/Sailfish-JS-report/src/components/TestCaseCard.tsx
@@ -18,7 +18,7 @@ import * as React from 'react';
import {TestCaseMetadata, isTestCaseMetadata} from '../models/TestcaseMetadata';
import {formatTime, getSecondsPeriod} from '../helpers/date';
import '../styles/report.scss';
-import {createStyleSelector} from '../helpers/styleCreators';
+import { createStyleSelector, createBemElement } from '../helpers/styleCreators';
import {KnownBugIndicator} from "./knownbugs/KnownBugIndicator";
import {KnownBugSummary} from "./knownbugs/KnownBugSummary";
import {connect} from "react-redux";
@@ -33,6 +33,7 @@ interface OwnProps {
knownBugsEnabled: boolean;
index: number;
isSelected: boolean;
+ isMLSubmitted: boolean;
}
interface DispatchProps {
@@ -41,7 +42,7 @@ interface DispatchProps {
interface Props extends OwnProps, DispatchProps {}
-function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSelected }: Props) {
+function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSelected, isMLSubmitted }: Props) {
const baseRef = React.useRef<HTMLDivElement>();
@@ -67,7 +68,9 @@ function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSe
"tc-card",
status,
isSelected ? "selected" : null
- );
+ ), mlSubmittedBoxClass = createBemElement("tc-card",
+ "ml-submitted",
+ isMLSubmitted? null: "hidden");
const baseClickHandler = () => {
// Don't trigger 'click' event when user selects text
@@ -98,6 +101,9 @@ function TestCaseCardBase({ metadata, handleClick, index, knownBugsEnabled, isSe
null
}
</div>
+ <div className={mlSubmittedBoxClass}>
+ <div className="tc-card__ml-submitted icon"/>
+ </div>
<div className="tc-card__status">
{status}
</div>

MODIFY
FrontEnd/Sailfish-JS-report/src/models/TestcaseMetadata.ts
FrontEnd/Sailfish-JS-report/src/models/TestcaseMetadata.ts
@@ -31,6 +31,9 @@ export interface TestCaseMetadata {
jsonFileName: string;
jsonpFileName: string;
bugs: (KnownBug | KnownBugCategory) [];
+ firstActionId: number;
+ lastActionId: number;
+ failedActionCount: number;
}
export function isTestCaseMetadata(testCase: TestCaseMetadata | LiveTestCase): testCase is TestCaseMetadata {

MODIFY
FrontEnd/Sailfish-JS-report/src/styles/report.scss
FrontEnd/Sailfish-JS-report/src/styles/report.scss
}
}
+.ml-stats {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-left: 20px;
+
+ &__title {
+ @include unselectable;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 19px;
+ color: #45A155;
+ white-space: nowrap;
+ margin-right: 8px;
+ }
+
+ &__bar {
+ width: 200px;
+ height: 10px;
+ background-color: #CCCCCC;
+ }
+
+ &__progress-bar {
+ background-color: #59B368;
+ height: 100%;
+ }
+
+ &__bar, &__progress-bar {
+ border-radius: 5px;
+ }
+
+ &__percents {
+ @include unselectable;
+ margin-left: 8px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 19px;
+ text-align: right;
+
+ color: #3D994D;
+ }
+
+ &.hidden {
+ display: none;
+ }
+}
+
.tc-card {
@include status-card-with-running;
width: 100%;
display: grid;
grid-template: auto auto / 55px 1fr auto;
- grid-template-areas: "index title status"
- "index info elapsed-time"
- "divider divider divider"
- "bugs-indicator bugs-summary bugs-summary";
+ grid-template-areas: "index title status ml"
+ "index info elapsed-time ml"
+ "divider divider divider ml"
+ "bugs-indicator bugs-summary bugs-summary ml";
border: 2px solid var(--status-secondary-border-color);
border-radius: 5px;
padding-left: 15px;
}
+ &__ml-submitted {
+ margin-left: 16px;
+ grid-area: ml;
+ align-self: center;
+
+ &.icon {
+ @include icon(url(../../resources/icons/ml-message-submitted.svg), 22px, 22px);
+ }
+
+ &.hidden {
+ display: none;
+ }
+ }
+
&__status {
grid-area: status;
}

Feature #63126. Implement ml data collection progress bar and test case ml indicators

Change-Id: I6842807ac8984a67800270d281002c214ef123b5