Skip to content

Commit

Permalink
feat: add ability to sort by duration and start time (#623)
Browse files Browse the repository at this point in the history
* feat: add ability to sort by duration and start time

* fix: display average time, use duration field for testplane tests

* test: implement unit tests for tree sorting

* test: fix tests after modifying tree builder helpers and fix review issues
  • Loading branch information
shadowusr authored Dec 17, 2024
1 parent a8428a5 commit f1a7b7e
Show file tree
Hide file tree
Showing 31 changed files with 1,462 additions and 489 deletions.
3 changes: 3 additions & 0 deletions lib/adapters/test-result/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export interface ReporterTestResult {
readonly state: { name: string };
readonly status: TestStatus;
readonly testPath: string[];
/** Test start timestamp in ms */
readonly timestamp: number | undefined;
readonly url?: string;
/** Test duration in ms */
readonly duration: number;
}
4 changes: 4 additions & 0 deletions lib/adapters/test-result/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,8 @@ export class PlaywrightTestResultAdapter implements ReporterTestResult {

return _.groupBy(imageAttachments, a => a.name.replace(ANY_IMAGE_ENDING_REGEXP, ''));
}

get duration(): number {
return this._testResult.duration;
}
}
4 changes: 4 additions & 0 deletions lib/adapters/test-result/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,8 @@ export class ReporterTestAdapter implements ReporterTestResult {
get url(): string | undefined {
return this._testResult.url;
}

get duration(): number {
return this._testResult.duration;
}
}
4 changes: 4 additions & 0 deletions lib/adapters/test-result/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,8 @@ export class SqliteTestResultAdapter implements ReporterTestResult {
get url(): string | undefined {
return this._testResult[DB_COLUMN_INDEXES.suiteUrl];
}

get duration(): number {
return this._testResult[DB_COLUMN_INDEXES.duration];
}
}
18 changes: 13 additions & 5 deletions lib/adapters/test-result/testplane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ const getHistory = (history?: TestplaneTestResult['history']): TestStepCompresse
export interface TestplaneTestResultAdapterOptions {
attempt: number;
status: TestStatus;
duration: number;
}

export class TestplaneTestResultAdapter implements ReporterTestResult {
private _testResult: TestplaneTest | TestplaneTestResult;
private _errorDetails: ErrorDetails | null;
private _timestamp: number | undefined;
private _timestamp: number;
private _attempt: number;
private _status: TestStatus;
private _duration: number;

static create(
this: new (testResult: TestplaneTest | TestplaneTestResult, options: TestplaneTestResultAdapterOptions) => TestplaneTestResultAdapter,
Expand All @@ -90,18 +92,20 @@ export class TestplaneTestResultAdapter implements ReporterTestResult {
return new this(testResult, options);
}

constructor(testResult: TestplaneTest | TestplaneTestResult, {attempt, status}: TestplaneTestResultAdapterOptions) {
constructor(testResult: TestplaneTest | TestplaneTestResult, {attempt, status, duration}: TestplaneTestResultAdapterOptions) {
this._testResult = testResult;
this._errorDetails = null;
this._timestamp = (this._testResult as TestplaneTestResult).timestamp ??
(this._testResult as TestplaneTestResult).startTime ?? Date.now();
this._timestamp = (this._testResult as TestplaneTestResult).startTime
?? (this._testResult as TestplaneTestResult).timestamp
?? Date.now();
this._status = status;

const browserVersion = _.get(this._testResult, 'meta.browserVersion', this._testResult.browserVersion);

_.set(this._testResult, 'meta.browserVersion', browserVersion);

this._attempt = attempt;
this._duration = duration;
}

get fullName(): string {
Expand Down Expand Up @@ -259,7 +263,11 @@ export class TestplaneTestResultAdapter implements ReporterTestResult {

set timestamp(timestamp) {
if (!_.isNumber(this._timestamp)) {
this._timestamp = timestamp;
this._timestamp = timestamp ?? 0;
}
}

get duration(): number {
return this._duration;
}
}
3 changes: 2 additions & 1 deletion lib/adapters/test-result/transformers/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export class DbTestResultTransformer {
screenshot: Boolean(testResult.screenshot),
multipleTabs: testResult.multipleTabs,
status: testResult.status,
timestamp: testResult.timestamp ?? Date.now()
timestamp: testResult.timestamp ?? Date.now(),
duration: testResult.duration
};
}
}
3 changes: 2 additions & 1 deletion lib/adapters/test-result/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const copyAndUpdate = (
'status',
'testPath',
'timestamp',
'url'
'url',
'duration'
] as const;

// Type-level check that we didn't forget to include any keys
Expand Down
3 changes: 2 additions & 1 deletion lib/adapters/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export interface CreateTestResultOpts {
sessionId?: string;
meta?: {
url?: string;
}
};
duration: number;
}

export interface TestAdapter {
Expand Down
4 changes: 2 additions & 2 deletions lib/adapters/test/testplane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class TestplaneTestAdapter implements TestAdapter {
}

createTestResult(opts: CreateTestResultOpts): ReporterTestResult {
const {status, assertViewResults, error, sessionId, meta, attempt = UNKNOWN_ATTEMPT} = opts;
const {status, assertViewResults, error, sessionId, meta, attempt = UNKNOWN_ATTEMPT, duration} = opts;
const test = this._test.clone();

[
Expand All @@ -68,7 +68,7 @@ export class TestplaneTestAdapter implements TestAdapter {
}
});

return TestplaneTestResultAdapter.create(test, {attempt, status});
return TestplaneTestResultAdapter.create(test, {attempt, status, duration});
}
}

Expand Down
8 changes: 5 additions & 3 deletions lib/constants/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const DB_COLUMNS = {
SCREENSHOT: 'screenshot',
MULTIPLE_TABS: 'multipleTabs',
STATUS: 'status',
TIMESTAMP: 'timestamp'
TIMESTAMP: 'timestamp',
DURATION: 'duration'
} as const;

export const SUITES_TABLE_COLUMNS = [
Expand All @@ -31,7 +32,8 @@ export const SUITES_TABLE_COLUMNS = [
{name: DB_COLUMNS.SCREENSHOT, type: DB_TYPES.int}, //boolean - 0 or 1
{name: DB_COLUMNS.MULTIPLE_TABS, type: DB_TYPES.int}, //boolean - 0 or 1
{name: DB_COLUMNS.STATUS, type: DB_TYPES.text},
{name: DB_COLUMNS.TIMESTAMP, type: DB_TYPES.int}
{name: DB_COLUMNS.TIMESTAMP, type: DB_TYPES.int},
{name: DB_COLUMNS.DURATION, type: DB_TYPES.int}
] as const;

export const DB_MAX_AVAILABLE_PAGE_SIZE = 65536; // helps to speed up queries
Expand All @@ -55,7 +57,7 @@ interface DbColumnIndexes {
[DB_COLUMNS.MULTIPLE_TABS]: 11,
[DB_COLUMNS.STATUS]: 12,
[DB_COLUMNS.TIMESTAMP]: 13,

[DB_COLUMNS.DURATION]: 14,
}

export const DB_COLUMN_INDEXES = SUITES_TABLE_COLUMNS.reduce((acc: Record<string, number>, {name}, index) => {
Expand Down
13 changes: 8 additions & 5 deletions lib/gui/tool-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ export class ToolRunner {
attempt: latestAttempt,
error: test.error,
sessionId,
meta: {url}
meta: {url},
duration: 0
});

const estimatedStatus = reportBuilder.getUpdatedReferenceTestStatus(latestResult);
Expand All @@ -205,7 +206,8 @@ export class ToolRunner {
attempt: UNKNOWN_ATTEMPT,
error: test.error,
sessionId,
meta: {url}
meta: {url},
duration: 0
});

const formattedResult = reportBuilder.provideAttempt(formattedResultWithoutAttempt);
Expand All @@ -232,7 +234,8 @@ export class ToolRunner {
attempt: UNKNOWN_ATTEMPT,
error: test.error,
sessionId,
meta: {url}
meta: {url},
duration: 0
});

await Promise.all(formattedResultWithoutAttempt.imagesInfo.map(async (imageInfo) => {
Expand Down Expand Up @@ -343,9 +346,9 @@ export class ToolRunner {
this._testAdapters[testId] = test;

if (test.pending) {
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({status: SKIPPED})));
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({status: SKIPPED, duration: 0})));
} else {
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({status: IDLE})));
queue.add(async () => reportBuilder.addTestResult(test.createTestResult({status: IDLE, duration: 0})));
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export const formatTestResult = (
status: TestStatus,
attempt: number = UNKNOWN_ATTEMPT
): ReporterTestResult => {
return new TestplaneTestResultAdapter(rawResult, {attempt, status});
return new TestplaneTestResultAdapter(rawResult, {attempt, status, duration: (rawResult as TestplaneTestResult).duration});
};

export const saveErrorDetails = async (testResult: ReporterTestResult, reportPath: string): Promise<void> => {
Expand Down
2 changes: 2 additions & 0 deletions lib/sqlite-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export interface DbTestResult {
suiteUrl: string;
/* Unix time in ms. Example: `1700563430266` */
timestamp: number;
/* Duration in ms. Example: `1601` */
duration: number;
}

interface SqliteClientOptions {
Expand Down
9 changes: 9 additions & 0 deletions lib/static/constants/sort-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ import {SortByExpression, SortType} from '@/static/new-ui/types/store';
export const SORT_BY_NAME: SortByExpression = {id: 'by-name', label: 'Name', type: SortType.ByName};
export const SORT_BY_FAILED_RETRIES: SortByExpression = {id: 'by-failed-runs', label: 'Failed runs count', type: SortType.ByFailedRuns};
export const SORT_BY_TESTS_COUNT: SortByExpression = {id: 'by-tests-count', label: 'Tests count', type: SortType.ByTestsCount};
export const SORT_BY_START_TIME: SortByExpression = {id: 'by-start-time', label: 'Start time', type: SortType.ByStartTime};
export const SORT_BY_DURATION: SortByExpression = {id: 'by-duration', label: 'Duration', type: SortType.ByDuration};

export const DEFAULT_AVAILABLE_EXPRESSIONS: SortByExpression[] = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES,
SORT_BY_START_TIME,
SORT_BY_DURATION
];
18 changes: 7 additions & 11 deletions lib/static/modules/reducers/sort-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {SortByExpression, SortDirection, State} from '@/static/new-ui/types/stor
import {SomeAction} from '@/static/modules/actions/types';
import actionNames from '@/static/modules/action-names';
import {applyStateUpdate} from '@/static/modules/utils';
import {SORT_BY_FAILED_RETRIES, SORT_BY_NAME, SORT_BY_TESTS_COUNT} from '@/static/constants/sort-tests';
import {
DEFAULT_AVAILABLE_EXPRESSIONS,
SORT_BY_TESTS_COUNT
} from '@/static/constants/sort-tests';

export default (state: State, action: SomeAction): State => {
switch (action.type) {
case actionNames.INIT_STATIC_REPORT:
case actionNames.INIT_GUI_REPORT: {
const availableExpressions: SortByExpression[] = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES
];
const availableExpressions = DEFAULT_AVAILABLE_EXPRESSIONS;

return applyStateUpdate(state, {
app: {
Expand Down Expand Up @@ -46,15 +46,11 @@ export default (state: State, action: SomeAction): State => {

if (action.payload.expressionIds.length > 0) {
availableExpressions = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES,
...DEFAULT_AVAILABLE_EXPRESSIONS,
SORT_BY_TESTS_COUNT
];
} else {
availableExpressions = [
SORT_BY_NAME,
SORT_BY_FAILED_RETRIES
];
availableExpressions = DEFAULT_AVAILABLE_EXPRESSIONS;
}

return applyStateUpdate(state, {
Expand Down
7 changes: 7 additions & 0 deletions lib/static/new-ui/components/MetaInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ function MetaInfoInternal(props: MetaInfoInternalProps): ReactNode {
};
});

if (result.duration !== undefined) {
metaInfoItemsWithResolvedUrls.push({
label: 'duration',
content: `${new Intl.NumberFormat().format(result.duration)} ms`
});
}

const hasUrlMetaInfoItem = metaInfoItemsWithResolvedUrls.some(item => item.label === 'url');
if (!hasUrlMetaInfoItem && result.suiteUrl) {
metaInfoItemsWithResolvedUrls.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
BarsAscendingAlignLeftArrowUp,
BarsDescendingAlignLeftArrowDown,
FontCase,
SquareLetterT
SquareLetterT,
Clock,
Stopwatch
} from '@gravity-ui/icons';
import {Icon, Select, SelectProps} from '@gravity-ui/uikit';
import React, {ReactNode} from 'react';
Expand All @@ -26,6 +28,12 @@ const getSortIcon = (sortByExpression: SortByExpression): ReactNode => {
case SortType.ByTestsCount:
iconData = SquareLetterT;
break;
case SortType.ByStartTime:
iconData = Clock;
break;
case SortType.ByDuration:
iconData = Stopwatch;
break;
}
return <Icon data={iconData} className={styles.optionIcon} width={14} height={14} />;
};
Expand Down
Loading

0 comments on commit f1a7b7e

Please sign in to comment.