Skip to content

Commit

Permalink
feat: implement tests grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowusr committed Nov 14, 2024
1 parent 146737a commit 8691f28
Show file tree
Hide file tree
Showing 37 changed files with 1,001 additions and 407 deletions.
12 changes: 10 additions & 2 deletions lib/db-utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from 'lodash';
import {logger} from '../common-utils';
import {DB_MAX_AVAILABLE_PAGE_SIZE, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, DB_COLUMN_INDEXES} from '../constants';
import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types';
import type {Database, Statement} from 'better-sqlite3';
import type {Database as BetterSqlite3Database, Statement} from 'better-sqlite3';
import {ReadonlyDeep} from 'type-fest';

export const selectAllQuery = (tableName: string): string => `SELECT * FROM ${tableName}`;
Expand All @@ -16,10 +16,18 @@ export const compareDatabaseRowsByTimestamp = (row1: RawSuitesRow, row2: RawSuit
return (row1[DB_COLUMN_INDEXES.timestamp] as number) - (row2[DB_COLUMN_INDEXES.timestamp] as number);
};

export interface Database {}

export interface DbLoadResult {
url: string; status: string; data: null | unknown
}

export interface DbDetails {
url: string;
status: string;
success: boolean;
}

export interface HandleDatabasesOptions {
pluginConfig: ReporterConfig;
loadDbJsonUrl: (dbJsonUrl: string) => Promise<{data: DbUrlsJsonData | null; status?: string}>;
Expand Down Expand Up @@ -59,7 +67,7 @@ export const handleDatabases = async (dbJsonUrls: string[], opts: HandleDatabase
);
};

export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => []}: { db: Database, dbPaths: string[], getExistingTables?: (getTablesStatement: Statement<[]>) => string[] }): void => {
export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => []}: { db: BetterSqlite3Database, dbPaths: string[], getExistingTables?: (getTablesStatement: Statement<[]>) => string[] }): void => {
db.prepare(`PRAGMA page_size = ${DB_MAX_AVAILABLE_PAGE_SIZE}`).run();

for (const dbPath of dbPaths) {
Expand Down
14 changes: 12 additions & 2 deletions lib/gui/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import {ServerArgs} from './index';
import {ServerReadyData} from './api';
import {ToolName} from '../constants';
import type {TestplaneToolAdapter} from '../adapters/tool/testplane';
import {ToolRunnerTree} from '@/gui/tool-runner';

interface CustomGuiError {
response: {
status: number;
data: string;
}
}

export type GetInitResponse = (ToolRunnerTree & {customGuiError?: CustomGuiError}) | null;

export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
const {toolAdapter} = args;
Expand Down Expand Up @@ -54,7 +64,7 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
await (toolAdapter as TestplaneToolAdapter).initGuiHandler();
}

res.json(app.data);
res.json(app.data satisfies GetInitResponse);
} catch (e: unknown) {
const error = e as Error;
if (!app.data) {
Expand All @@ -68,7 +78,7 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
data: `Error while trying to initialize custom GUI: ${error.message}`
}
}
});
} satisfies GetInitResponse);
}
});

Expand Down
2 changes: 1 addition & 1 deletion lib/static/components/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Gui extends Component {
};

componentDidMount() {
this.props.actions.initGuiReport();
this.props.actions.thunkInitGuiReport();
this._subscribeToEvents();
}

Expand Down
3 changes: 2 additions & 1 deletion lib/static/modules/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ export default {
TOGGLE_SUITE_CHECKBOX: 'TOGGLE_SUITE_CHECKBOX',
TOGGLE_GROUP_CHECKBOX: 'TOGGLE_GROUP_CHECKBOX',
UPDATE_BOTTOM_PROGRESS_BAR: 'UPDATE_BOTTOM_PROGRESS_BAR',
GROUP_TESTS_BY_KEY: 'GROUP_TESTS_BY_KEY',
GROUP_TESTS_SET_CURRENT_EXPRESSION: 'GROUP_TESTS_SET_CURRENT_EXPRESSION',
TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX',
SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE',
SUITES_PAGE_SET_SECTION_EXPANDED: 'SUITES_PAGE_SET_SECTION_EXPANDED',
SUITES_PAGE_SET_TREE_NODE_EXPANDED: 'SUITES_PAGE_SET_TREE_NODE_EXPANDED',
SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED',
VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: 'VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE',
UPDATE_LOADING_PROGRESS: 'UPDATE_LOADING_PROGRESS',
Expand Down
11 changes: 11 additions & 0 deletions lib/static/modules/actions/group-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import actionNames from '@/static/modules/action-names';
import {Action} from '@/static/modules/actions/types';

type SetCurrentGroupByExpressionAction = Action<typeof actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION, {
expressionIds: string[];
}>;

export const setCurrentGroupByExpression = (payload: SetCurrentGroupByExpressionAction['payload']): SetCurrentGroupByExpressionAction =>
({type: actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION, payload});

export type GroupTestsAction = SetCurrentGroupByExpressionAction;
96 changes: 2 additions & 94 deletions lib/static/modules/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {DiffModes} from '../../../constants/diff-modes';
import {getHttpErrorMessage} from '../utils';
import {fetchDataFromDatabases, mergeDatabases, connectToDatabase, getMainDatabaseUrl, getSuitesTableRows} from '../../../db-utils/client';
import {setFilteredBrowsers} from '../query-params';
import * as plugins from '../plugins';
import performanceMarks from '../../../constants/performance-marks';

export * from './lifecycle';
export * from './group-tests';
export * from './static-accepter';
export * from './suites-page';
export * from './suites';
Expand All @@ -35,97 +35,6 @@ export const createNotificationError = (id, error, props = {dismissAfter: 0}) =>

export const dismissNotification = dismissNotify;

export const initGuiReport = () => {
return async (dispatch) => {
performance?.mark?.(performanceMarks.JS_EXEC);
try {
const appState = await axios.get('/init');

const mainDatabaseUrl = getMainDatabaseUrl();
const db = await connectToDatabase(mainDatabaseUrl.href);

performance?.mark?.(performanceMarks.DBS_LOADED);

await plugins.loadAll(appState.data.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);

dispatch({
type: actionNames.INIT_GUI_REPORT,
payload: {...appState.data, db}
});

const {customGuiError} = appState.data;

if (customGuiError) {
dispatch(createNotificationError('initGuiReport', {...customGuiError}));
delete appState.data.customGuiError;
}
} catch (e) {
dispatch(createNotificationError('initGuiReport', e));
}
};
};

export const initStaticReport = () => {
return async dispatch => {
performance?.mark?.(performanceMarks.JS_EXEC);
const dataFromStaticFile = window.data || {};
let fetchDbDetails = [];
let db = null;

try {
const mainDatabaseUrls = new URL('databaseUrls.json', window.location.href);
const fetchDbResponses = await fetchDataFromDatabases([mainDatabaseUrls.href], (dbUrl, progress) => {
dispatch({
type: actionNames.UPDATE_LOADING_PROGRESS,
payload: {[dbUrl]: progress}
});
});

performance?.mark?.(performanceMarks.DBS_LOADED);

plugins.preloadAll(dataFromStaticFile.config);

fetchDbDetails = fetchDbResponses.map(({url, status, data}) => ({url, status, success: !!data}));

const dataForDbs = fetchDbResponses.map(({data}) => data).filter(data => data);

db = await mergeDatabases(dataForDbs);

performance?.mark?.(performanceMarks.DBS_MERGED);
} catch (e) {
dispatch(createNotificationError('initStaticReport', e));
}

await plugins.loadAll(dataFromStaticFile.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);
const testsTreeBuilder = StaticTestsTreeBuilder.create();

if (!db || isEmpty(fetchDbDetails)) {
return dispatch({
type: actionNames.INIT_STATIC_REPORT,
payload: {...dataFromStaticFile, db, fetchDbDetails, tree: testsTreeBuilder.build([]).tree, stats: {}, skips: [], browsers: []}
});
}

const suitesRows = getSuitesTableRows(db);

performance?.mark?.(performanceMarks.DB_EXTRACTED_ROWS);

const {tree, stats, skips, browsers} = testsTreeBuilder.build(suitesRows);

dispatch({
type: actionNames.INIT_STATIC_REPORT,
payload: {...dataFromStaticFile, db, fetchDbDetails, tree, stats, skips, browsers}
});
};
};

export const finGuiReport = () => ({type: actionNames.FIN_GUI_REPORT});
export const finStaticReport = () => ({type: actionNames.FIN_STATIC_REPORT});

const runTests = ({tests = [], action = {}} = {}) => {
return async (dispatch) => {
try {
Expand Down Expand Up @@ -266,7 +175,6 @@ export const toggleSuiteCheckbox = (payload) => ({type: actionNames.TOGGLE_SUITE
export const toggleGroupCheckbox = (payload) => ({type: actionNames.TOGGLE_GROUP_CHECKBOX, payload});
export const updateBottomProgressBar = (payload) => ({type: actionNames.UPDATE_BOTTOM_PROGRESS_BAR, payload});
export const toggleTestsGroup = (payload) => ({type: actionNames.TOGGLE_TESTS_GROUP, payload});
export const groupTestsByKey = (payload) => ({type: actionNames.GROUP_TESTS_BY_KEY, payload});
export const changeViewMode = (payload) => ({type: actionNames.CHANGE_VIEW_MODE, payload});

export const runCustomGuiAction = (payload) => {
Expand Down
134 changes: 134 additions & 0 deletions lib/static/modules/actions/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import axios from 'axios';
import {isEmpty} from 'lodash';

import performanceMarks from '@/constants/performance-marks';
import {
connectToDatabase, Database, DbDetails, DbLoadResult,
fetchDataFromDatabases,
getMainDatabaseUrl,
getSuitesTableRows,
mergeDatabases
} from '@/db-utils/client';
import * as plugins from '@/static/modules/plugins';
import actionNames from '@/static/modules/action-names';
import {FinalStats, SkipItem, StaticTestsTreeBuilder} from '@/tests-tree-builder/static';
import {createNotificationError} from '@/static/modules/actions/index';
import {Action, AppThunk} from '@/static/modules/actions/types';
import {DataForStaticFile} from '@/server-utils';
import {GetInitResponse} from '@/gui/server';
import {Tree} from '@/tests-tree-builder/base';
import {BrowserItem} from '@/types';

export type InitGuiReportAction = Action<typeof actionNames.INIT_GUI_REPORT, GetInitResponse & {db: Database}>;
const initGuiReport = (payload: InitGuiReportAction['payload']): InitGuiReportAction =>
({type: actionNames.INIT_GUI_REPORT, payload});

export const thunkInitGuiReport = (): AppThunk => {
return async (dispatch) => {
performance?.mark?.(performanceMarks.JS_EXEC);
try {
const appState = await axios.get<GetInitResponse>('/init');

if (!appState.data) {
throw new Error('Could not load app data. The report might be broken. Please check your project settings or try deleting results folder and relaunching UI server.');
}

const mainDatabaseUrl = getMainDatabaseUrl();
const db = await connectToDatabase(mainDatabaseUrl.href);

performance?.mark?.(performanceMarks.DBS_LOADED);

await plugins.loadAll(appState.data.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);

dispatch(initGuiReport({...appState.data, db}));

if (appState.data.customGuiError) {
const {customGuiError} = appState.data;

dispatch(createNotificationError('initGuiReport', {...customGuiError}));
delete appState.data.customGuiError;
}
} catch (e) {
dispatch(createNotificationError('initGuiReport', e));
}
};
};

export type InitStaticReportAction = Action<typeof actionNames.INIT_STATIC_REPORT,
Partial<DataForStaticFile> & {
db: Database;
fetchDbDetails: DbDetails[],
tree: Tree;
stats: FinalStats | null;
skips: SkipItem[];
browsers: BrowserItem[];
}>;
const initStaticReport = (payload: InitStaticReportAction['payload']): InitStaticReportAction =>
({type: actionNames.INIT_STATIC_REPORT, payload});

export const thunkInitStaticReport = (): AppThunk => {
return async dispatch => {
performance?.mark?.(performanceMarks.JS_EXEC);
const dataFromStaticFile = (window as {data?: DataForStaticFile}).data || {} as Partial<DataForStaticFile>;

let fetchDbDetails: DbDetails[] = [];
let db = null;

try {
const mainDatabaseUrls = new URL('databaseUrls.json', window.location.href);
const fetchDbResponses = await fetchDataFromDatabases([mainDatabaseUrls.href], (dbUrl: string, progress: number) => {
dispatch({
type: actionNames.UPDATE_LOADING_PROGRESS,
payload: {[dbUrl]: progress}
});
}) as DbLoadResult[];

performance?.mark?.(performanceMarks.DBS_LOADED);

plugins.preloadAll(dataFromStaticFile.config);

fetchDbDetails = fetchDbResponses.map(({url, status, data}) => ({url, status, success: !!data}));

const dataForDbs = fetchDbResponses.map(({data}) => data).filter(data => data);

db = await mergeDatabases(dataForDbs);

performance?.mark?.(performanceMarks.DBS_MERGED);
} catch (e) {
dispatch(createNotificationError('thunkInitStaticReport', e));
}

await plugins.loadAll(dataFromStaticFile.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);
const testsTreeBuilder = StaticTestsTreeBuilder.create();

if (!db || isEmpty(fetchDbDetails)) {
dispatch(initStaticReport({...dataFromStaticFile, db, fetchDbDetails, tree: testsTreeBuilder.build([]).tree, stats: null, skips: [], browsers: []}));

return;
}

const suitesRows = getSuitesTableRows(db);

performance?.mark?.(performanceMarks.DB_EXTRACTED_ROWS);

const {tree, stats, skips, browsers} = testsTreeBuilder.build(suitesRows);

dispatch(initStaticReport({...dataFromStaticFile, db, fetchDbDetails, tree, stats, skips, browsers}));
};
};

export type FinGuiReportAction = Action<typeof actionNames.FIN_GUI_REPORT>;
export const finGuiReport = (): FinGuiReportAction => ({type: actionNames.FIN_GUI_REPORT});

export type FinStaticReportAction = Action<typeof actionNames.FIN_STATIC_REPORT>;
export const finStaticReport = (): FinStaticReportAction => ({type: actionNames.FIN_STATIC_REPORT});

export type LifecycleAction =
| InitGuiReportAction
| InitStaticReportAction
| FinGuiReportAction
| FinStaticReportAction;
18 changes: 15 additions & 3 deletions lib/static/modules/actions/suites-page.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import actionNames from '@/static/modules/action-names';
import {Action} from '@/static/modules/actions/types';
import {TreeViewItemData} from '@/static/new-ui/features/suites/components/SuitesPage/types';

export type SuitesPageSetCurrentSuiteAction = Action<typeof actionNames.SUITES_PAGE_SET_CURRENT_SUITE, {
suiteId: string;
treeNodeId: string;
browserId: string;
groupId: string | null;
}>;

export const suitesPageSetCurrentSuite = (suiteId: string): SuitesPageSetCurrentSuiteAction => {
return {type: actionNames.SUITES_PAGE_SET_CURRENT_SUITE, payload: {suiteId}};
export const setCurrentTreeNode = (payload: SuitesPageSetCurrentSuiteAction['payload']): SuitesPageSetCurrentSuiteAction => {
return {type: actionNames.SUITES_PAGE_SET_CURRENT_SUITE, payload};
};

type SetTreeNodeExpandedStateAction = Action<typeof actionNames.SUITES_PAGE_SET_TREE_NODE_EXPANDED, {
nodeId: string;
isExpanded: boolean;
}>;

export const setTreeNodeExpandedState = (payload: SetTreeNodeExpandedStateAction['payload']): SetTreeNodeExpandedStateAction =>
({type: actionNames.SUITES_PAGE_SET_TREE_NODE_EXPANDED, payload});

type SetSectionExpandedStateAction = Action<typeof actionNames.SUITES_PAGE_SET_SECTION_EXPANDED, {
sectionId: string;
isExpanded: boolean;
Expand All @@ -26,6 +37,7 @@ export const setStepsExpandedState = (payload: SetStepsExpandedStateAction['payl
({type: actionNames.SUITES_PAGE_SET_STEPS_EXPANDED, payload});

export type SuitesPageAction =
| SetTreeNodeExpandedStateAction
| SuitesPageSetCurrentSuiteAction
| SetSectionExpandedStateAction
| SetStepsExpandedStateAction;
Loading

0 comments on commit 8691f28

Please sign in to comment.