)[] = [] as any;
+ files
+ .map((f: any) => f.suites)
+ .forEach((suite: any) => {
+ suite.forEach((col: any) =>
+ col.tests.forEach((test: any) => {
+ test.url = test.url.replace(
+ /column-store\/api/,
+ 'column-store'
+ );
+
+ invokers.push(
+ // @ts-ignore
+ () => {
+ return validate(
+ test.url,
+ test.data,
+ test.label,
+ test.hash
+ ).then((report: any) => {
+ report.test = test;
+ place = place + 1;
+ const prefix = `${place} of ${totalCount}`;
+
+ if (test?.skip) {
+ skips.push(test.hash);
+ } else if (!report.status) {
+ report.httpError
+ ? httpErrors.push(test.hash)
+ : errors.push(test.hash);
+ } else if (report.status)
+ passed.push(test.hash);
+
+ reportValidationResult(report, prefix);
+ });
+ }
+ );
+ })
+ );
+ });
+
+ for (const el of invokers) {
+ await el();
+ }
+
+ console.group('FINAL REPORT');
+ console.log(`PASSED: ${passed.length} of ${totalCount}`);
+ console.log(`FAILED: ${errors.length} (${errors.join(',')})`);
+ console.log(
+ `HTTP ERRORS: ${httpErrors.length} (${httpErrors.join(',')})`
+ );
+ console.log(`SKIPPED: ${skips.length} (${skips.join(',')})`);
+ console.groupEnd();
+
+ //Promise.all(promises).then(() => {
+ console.groupEnd();
+ // });
+ }, []);
+
+ useEffect(() => {
+ if (getCache()) {
+ const tests = getCache();
+ const parsed = _.values(tests).map((j: any) => j);
+ store.tests = parsed;
+ }
+
+ const checker = setInterval(() => {
+ if (getCache()) {
+ const tests = getCache();
+ const parsed = _.values(tests);
+ store.tests = parsed;
+ } else {
+ store.tests = [];
+ }
+ }, 1000);
+
+ return () => {
+ clearInterval(checker);
+ };
+ }, []);
+
+ const txt = `
+ {
+ "name":"",
+ "note":"",
+ "studies":[],
+ "tests":[
+ ${store.tests.map((t: any) => JSON.stringify(t)).join(',\n\n')}
+ ]
+ }`;
+
+ if (!store.show) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {
+
+ }
+
+ );
+});
diff --git a/src/pages/studyView/table/treatments/PatientTreatmentsTable.tsx b/src/pages/studyView/table/treatments/PatientTreatmentsTable.tsx
index 8a5a335c95a..4c9cc9c1f2e 100644
--- a/src/pages/studyView/table/treatments/PatientTreatmentsTable.tsx
+++ b/src/pages/studyView/table/treatments/PatientTreatmentsTable.tsx
@@ -7,7 +7,10 @@ import {
Column,
SortDirection,
} from '../../../../shared/components/lazyMobXTable/LazyMobXTable';
-import { PatientTreatmentRow } from 'cbioportal-ts-api-client';
+import {
+ PatientTreatmentReport,
+ PatientTreatment,
+} from 'cbioportal-ts-api-client';
import { correctColumnWidth } from 'pages/studyView/StudyViewUtils';
import LabeledCheckbox from 'shared/components/labeledCheckbox/LabeledCheckbox';
import styles from 'pages/studyView/table/tables.module.scss';
@@ -36,7 +39,7 @@ export type PatientTreatmentsTableColumn = {
export type PatientTreatmentsTableProps = {
tableType: TreatmentTableType;
- promise: MobxPromise;
+ promise: MobxPromise;
width: number;
height: number;
filters: string[][];
@@ -59,9 +62,7 @@ const DEFAULT_COLUMN_WIDTH_RATIO: {
[PatientTreatmentsTableColumnKey.COUNT]: 0.2,
};
-class MultiSelectionTableComponent extends FixedHeaderTable<
- PatientTreatmentRow
-> {}
+class MultiSelectionTableComponent extends FixedHeaderTable {}
@observer
export class PatientTreatmentsTable extends TreatmentsTable<
@@ -80,7 +81,7 @@ export class PatientTreatmentsTable extends TreatmentsTable<
}
createNubmerColumnCell(
- row: PatientTreatmentRow,
+ row: PatientTreatment,
cellMargin: number
): JSX.Element {
return (
@@ -111,9 +112,7 @@ export class PatientTreatmentsTable extends TreatmentsTable<
cellMargin: number
) => {
const defaults: {
- [key in PatientTreatmentsTableColumnKey]: Column<
- PatientTreatmentRow
- >;
+ [key in PatientTreatmentsTableColumnKey]: Column;
} = {
[PatientTreatmentsTableColumnKey.TREATMENT]: {
name: columnKey,
@@ -123,10 +122,10 @@ export class PatientTreatmentsTable extends TreatmentsTable<
headerName={columnKey}
/>
),
- render: (data: PatientTreatmentRow) => (
+ render: (data: PatientTreatment) => (
),
- sortBy: (data: PatientTreatmentRow) => data.treatment,
+ sortBy: (data: PatientTreatment) => data.treatment,
defaultSortDirection: 'asc' as 'asc',
filter: filterTreatmentCell,
width: columnWidth,
@@ -140,9 +139,9 @@ export class PatientTreatmentsTable extends TreatmentsTable<
headerName={columnKey}
/>
),
- render: (data: PatientTreatmentRow) =>
+ render: (data: PatientTreatment) =>
this.createNubmerColumnCell(data, 28),
- sortBy: (data: PatientTreatmentRow) =>
+ sortBy: (data: PatientTreatment) =>
data.count + toNumericValue(data.treatment),
defaultSortDirection: 'desc' as 'desc',
filter: filterTreatmentCell,
@@ -181,8 +180,8 @@ export class PatientTreatmentsTable extends TreatmentsTable<
);
}
- @computed get tableData(): PatientTreatmentRow[] {
- return this.props.promise.result || [];
+ @computed get tableData(): PatientTreatment[] {
+ return this.props.promise.result?.patientTreatments || [];
}
@computed
@@ -206,7 +205,7 @@ export class PatientTreatmentsTable extends TreatmentsTable<
.filter(data =>
this.flattenedFilters.includes(treatmentUniqueKey(data))
)
- .sortBy(data =>
+ .sortBy(data =>
ifNotDefined(
order[treatmentUniqueKey(data)],
Number.POSITIVE_INFINITY
diff --git a/src/pages/studyView/table/treatments/SampleTreatmentsTable.tsx b/src/pages/studyView/table/treatments/SampleTreatmentsTable.tsx
index 43070757afe..025fff9d7b8 100644
--- a/src/pages/studyView/table/treatments/SampleTreatmentsTable.tsx
+++ b/src/pages/studyView/table/treatments/SampleTreatmentsTable.tsx
@@ -7,7 +7,10 @@ import {
Column,
SortDirection,
} from '../../../../shared/components/lazyMobXTable/LazyMobXTable';
-import { SampleTreatmentRow } from 'cbioportal-ts-api-client';
+import {
+ SampleTreatmentReport,
+ SampleTreatmentRow,
+} from 'cbioportal-ts-api-client';
import { correctColumnWidth } from 'pages/studyView/StudyViewUtils';
import LabeledCheckbox from 'shared/components/labeledCheckbox/LabeledCheckbox';
import styles from 'pages/studyView/table/tables.module.scss';
@@ -37,7 +40,7 @@ export type SampleTreatmentsTableColumn = {
export type SampleTreatmentsTableProps = {
tableType: TreatmentTableType;
- promise: MobxPromise;
+ promise: MobxPromise;
width: number;
height: number;
filters: string[][];
@@ -214,7 +217,7 @@ export class SampleTreatmentsTable extends TreatmentsTable<
}
@computed get tableData(): SampleTreatmentRow[] {
- return this.props.promise.result || [];
+ return this.props.promise.result?.treatments || [];
}
@computed get selectableTableData() {
diff --git a/src/pages/studyView/table/treatments/treatmentsTableUtil.tsx b/src/pages/studyView/table/treatments/treatmentsTableUtil.tsx
index d18e2694bf4..af38c602b92 100644
--- a/src/pages/studyView/table/treatments/treatmentsTableUtil.tsx
+++ b/src/pages/studyView/table/treatments/treatmentsTableUtil.tsx
@@ -3,6 +3,7 @@ import {
SampleTreatmentFilter,
PatientTreatmentFilter,
PatientTreatmentRow,
+ PatientTreatment,
} from 'cbioportal-ts-api-client';
import { ChartMeta } from 'pages/studyView/StudyViewUtils';
import styles from 'pages/studyView/table/tables.module.scss';
@@ -90,7 +91,7 @@ export const TreatmentGenericColumnHeader = class GenericColumnHeader extends Re
};
export const TreatmentColumnCell = class TreatmentColumnCell extends React.Component<
- { row: PatientTreatmentRow | SampleTreatmentRow },
+ { row: PatientTreatment | SampleTreatmentRow },
{}
> {
render() {
@@ -99,7 +100,7 @@ export const TreatmentColumnCell = class TreatmentColumnCell extends React.Compo
};
export function filterTreatmentCell(
- cell: PatientTreatmentRow | SampleTreatmentRow,
+ cell: PatientTreatment | SampleTreatmentRow,
filter: string
): boolean {
return cell.treatment.toUpperCase().includes(filter.toUpperCase());
diff --git a/src/shared/api/cbioportalInternalClientInstance.ts b/src/shared/api/cbioportalInternalClientInstance.ts
index 0aa9e2f3c57..6f4352f4144 100644
--- a/src/shared/api/cbioportalInternalClientInstance.ts
+++ b/src/shared/api/cbioportalInternalClientInstance.ts
@@ -1,5 +1,147 @@
import { CBioPortalAPIInternal } from 'cbioportal-ts-api-client';
+import { getLoadConfig } from 'config/config';
+import { getBrowserWindow, hashString } from 'cbioportal-frontend-commons';
+import { toJS } from 'mobx';
+import { reportValidationResult, validate } from 'shared/api/validation';
+import _ from 'lodash';
+import { makeTest, urlChopper } from 'shared/api/testMaker';
+
+// function invokeValidation(func){
+// getBrowserWindow().invokeCache = getBrowserWindow().invokeCache || [];
+//
+// getBrowserWindow().invokeCache.push(func);
+//
+//
+//
+// }
+
+function proxyColumnStore(client: any, endpoint: string) {
+ if (getBrowserWindow().location.search.includes('legacy')) {
+ return;
+ }
+
+ const method = endpoint.match(
+ new RegExp('fetchPatientTreatmentCounts|fetchSampleTreatmentCounts')
+ )
+ ? `${endpoint}UsingWithHttpInfo`
+ : `${endpoint}UsingPOSTWithHttpInfo`;
+ const old = client[method];
+
+ client[method] = function(params: any) {
+ const host =
+ getBrowserWindow().location.hostname ===
+ 'genie-public-beta.cbioportal.org'
+ ? 'genie-public-beta1.cbioportal.org'
+ : getLoadConfig().baseUrl;
+
+ const oldRequest = this.request;
+
+ const endpoints = [
+ 'ClinicalDataCounts',
+ 'MutatedGenes',
+ 'CaseList',
+ 'ClinicalDataBin',
+ 'MolecularProfileSample',
+ 'CNAGenes',
+ 'StructuralVariantGenes',
+ 'FilteredSamples',
+ 'ClinicalDataDensity',
+ 'MutationDataCounts',
+ 'PatientTreatmentCounts',
+ 'SampleTreatmentCounts',
+ 'GenomicData',
+ 'GenericAssay',
+ ];
+
+ const matchedMethod = method.match(new RegExp(endpoints.join('|')));
+ if (localStorage.getItem('LIVE_VALIDATE_KEY') && matchedMethod) {
+ this.request = function(...origArgs: any[]) {
+ const params = toJS(arguments[2]);
+
+ const oldSuccess = arguments[7];
+
+ arguments[7] = function() {
+ const url =
+ origArgs[1].replace(
+ /column-store\/api/,
+ 'column-store'
+ ) +
+ '?' +
+ _.map(origArgs[4], (v, k) => `${k}=${v}&`).join('');
+
+ setTimeout(() => {
+ makeTest(params, urlChopper(url), matchedMethod[0]);
+ }, 1000);
+
+ const hash = hashString(
+ JSON.stringify({ data: params, url: urlChopper(url) })
+ );
+ validate(
+ url,
+ params,
+ matchedMethod[0],
+ hash,
+ arguments[0].body,
+ arguments[0].xhr.getResponseHeader('elapsed-time')
+ ).then((result: any) => {
+ reportValidationResult(result, 'LIVE');
+ });
+
+ return oldSuccess.apply(this, arguments);
+ };
+
+ oldRequest.apply(this, arguments);
+ };
+ }
+
+ params.$domain = method.match(
+ new RegExp('PatientTreatmentCounts|SampleTreatmentCounts')
+ )
+ ? `//${host}`
+ : `//${host}/api/column-store`;
+ const url = old.apply(this, [params]);
+
+ this.request = oldRequest;
+
+ return url;
+ };
+}
const internalClient = new CBioPortalAPIInternal();
+export const internalClientColumnStore = new CBioPortalAPIInternal();
+
+const oldRequest = (internalClientColumnStore as any).request;
+(internalClientColumnStore as any).request = function(...args: any) {
+ args[1] = args[1].replace(/column-store\/api/, 'column-store');
+ return oldRequest.apply(this, args);
+};
+
+proxyColumnStore(internalClientColumnStore, 'fetchCNAGenes');
+proxyColumnStore(internalClientColumnStore, 'fetchStructuralVariantGenes');
+proxyColumnStore(internalClientColumnStore, 'fetchCaseListCounts');
+proxyColumnStore(
+ internalClientColumnStore,
+ 'fetchMolecularProfileSampleCounts'
+);
+proxyColumnStore(internalClientColumnStore, 'fetchMutatedGenes');
+proxyColumnStore(internalClientColumnStore, 'fetchFilteredSamples');
+proxyColumnStore(internalClientColumnStore, 'fetchClinicalDataCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchClinicalDataBinCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchClinicalDataDensityPlot');
+proxyColumnStore(internalClientColumnStore, 'fetchMutationDataCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchPatientTreatmentCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchSampleTreatmentCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchClinicalDataDensityPlot');
+proxyColumnStore(internalClientColumnStore, 'getClinicalEventTypeCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchMutationDataCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchGenomicDataCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchGenomicDataBinCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchGenericAssayDataBinCounts');
+proxyColumnStore(internalClientColumnStore, 'fetchGenericAssayDataCounts');
+
export default internalClient;
+
+export function getInteralClient() {
+ return internalClientColumnStore;
+}
diff --git a/src/shared/api/testMaker.ts b/src/shared/api/testMaker.ts
new file mode 100644
index 00000000000..758b157808d
--- /dev/null
+++ b/src/shared/api/testMaker.ts
@@ -0,0 +1,80 @@
+import { getBrowserWindow, hashString } from 'cbioportal-frontend-commons';
+import { toJS } from 'mobx';
+import _ from 'lodash';
+
+export const SAVE_TEST_KEY = 'save_test_enabled';
+
+export function urlChopper(url: string) {
+ try {
+ if (typeof url === 'string') {
+ return url.match(/[^\/]*\/\/[^\/]*(\/.*)/)![1];
+ } else {
+ return url;
+ }
+ } catch (ex) {
+ return url;
+ }
+}
+
+export async function makeTest(data: any, url: string, label: string) {
+ const hash = hashString(JSON.stringify({ data, url: urlChopper(url) }));
+
+ const filterString = $('.userSelections')
+ .find('*')
+ .contents()
+ .filter(function() {
+ return this.nodeType === 3;
+ })
+ .toArray()
+ .map(n => n.textContent)
+ .slice(0, -1)
+ .reduce((acc, s) => {
+ switch (s) {
+ case null:
+ acc += '';
+ break;
+ case '(':
+ acc += ' (';
+ break;
+ case ')':
+ acc += ') ';
+ break;
+ case 'or':
+ acc += ' OR ';
+ break;
+ case 'and':
+ acc += ' AND ';
+ break;
+ default:
+ acc += s || '';
+ break;
+ }
+ return acc;
+ }, '');
+
+ const entry = {
+ hash,
+ filterString,
+ data,
+ url,
+ label,
+ studies: toJS(getBrowserWindow().studyViewPageStore.studyIds),
+ filterUrl: urlChopper(
+ getBrowserWindow().studyPage.studyViewFullUrlWithFilter
+ ),
+ };
+
+ if (getBrowserWindow().localStorage.getItem(SAVE_TEST_KEY))
+ saveTest(hash, entry);
+
+ return entry;
+}
+
+function saveTest(hash: number, entry: any) {
+ const testCache = getBrowserWindow().testCache || {};
+
+ if (!(hash in testCache)) {
+ testCache[hash] = entry;
+ getBrowserWindow().testCache = testCache;
+ }
+}
diff --git a/src/shared/api/validation.ts b/src/shared/api/validation.ts
new file mode 100644
index 00000000000..d3454964340
--- /dev/null
+++ b/src/shared/api/validation.ts
@@ -0,0 +1,360 @@
+import _ from 'lodash';
+import { array } from 'yargs';
+
+export const isObject = (value: any) => {
+ return (
+ typeof value === 'object' &&
+ value !== null &&
+ !Array.isArray(value) &&
+ !(value instanceof RegExp) &&
+ !(value instanceof Date) &&
+ !(value instanceof Set) &&
+ !(value instanceof Map)
+ );
+};
+
+export function dynamicSortSingle(property: string) {
+ var sortOrder = 1;
+ if (property[0] === '-') {
+ sortOrder = -1;
+ property = property.substr(1);
+ }
+ return function(a: any, b: any) {
+ /* next line works with strings and numbers,
+ * and you may want to customize it to your needs
+ */
+ var result =
+ a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
+ return result * sortOrder;
+ };
+}
+
+export function dynamicSort(property: string[]) {
+ if (property.length === 1) {
+ return dynamicSortSingle(property[0]);
+ } else {
+ const prop1 = property[0];
+ const prop2 = property[1];
+ return function(a: any, b: any) {
+ /* next line works with strings and numbers,
+ * and you may want to customize it to your needs
+ */
+ let af = a[prop1];
+ let bf = b[prop1];
+ let as = a[prop2];
+ let bs = b[prop2];
+
+ // If first value is same
+ if (af == bf) {
+ return as < bs ? -1 : as > bs ? 1 : 0;
+ } else {
+ return af < bf ? -1 : 1;
+ }
+ };
+ }
+}
+
+export function getArrays(inp: any, output: Array) {
+ if (inp instanceof Array) {
+ output.push(inp);
+ inp.forEach(n => getArrays(n, output));
+ } else if (isObject(inp)) {
+ // this is to get rid of discrepancy deep in decimals
+ _.forEach(inp, (v, k) => {
+ if (/\d\.\d{10,}$/.test(v)) {
+ inp[k] = inp[k].toFixed(5);
+ }
+ });
+
+ // this is get rid
+ delete inp.matchingGenePanelIds;
+ delete inp.cytoband;
+ delete inp.numberOfProfiledCases;
+
+ // do nothing
+ Object.values(inp).forEach(nn => getArrays(nn, output));
+ }
+ return output;
+}
+
+const deleteFields: Record = {
+ MolecularProfileSample: ['label'],
+ CaseList: ['label'],
+};
+
+const sortFields: Record = {
+ ClinicalDataBin: 'attributeId,specialValue',
+ FilteredSamples: 'patientId,sampleId',
+ SampleTreatmentCounts: 'treatment,time',
+ PatientTreatmentCounts: 'treatment',
+};
+
+function getLegacyPatientTreatmentCountUrl(url: string) {
+ return url.replace(
+ /api\/treatments\/patient-counts\/fetch?/,
+ 'api/treatments/patient'
+ );
+}
+
+function getLegacySampleTreatmentCountUrl(url: string) {
+ return url.replace(
+ /api\/treatments\/sample-counts\/fetch?/,
+ 'api/treatments/sample'
+ );
+}
+
+const treatmentLegacyUrl: Record string> = {
+ PatientTreatmentCounts: getLegacyPatientTreatmentCountUrl,
+ SampleTreatmentCounts: getLegacySampleTreatmentCountUrl,
+};
+
+const treatmentConverter: Record any> = {
+ PatientTreatmentCounts: convertLegacyPatientTreatmentCountsToCh,
+ SampleTreatmentCounts: convertLegacySampleTreatmentCountsToCh,
+};
+
+function convertLegacySampleTreatmentCountsToCh(legacyData: any) {
+ const sampleIdSet = new Set();
+ const treatments: Array<{
+ time: string;
+ treatment: string;
+ count: number;
+ samples: Array;
+ }> = [];
+
+ legacyData.forEach((legacySampleTreatment: any) => {
+ let treatment = {
+ time: legacySampleTreatment['time'],
+ treatment: legacySampleTreatment['treatment'],
+ count: legacySampleTreatment['count'],
+ samples: new Array(),
+ };
+
+ treatments.push(treatment);
+ const samples = legacySampleTreatment['samples'];
+ if (samples instanceof Array) {
+ samples.forEach(sample => {
+ sampleIdSet.add(sample['sampleId']);
+ });
+ }
+ });
+ return {
+ totalSamples: sampleIdSet.size,
+ treatments: treatments,
+ };
+}
+
+function convertLegacyPatientTreatmentCountsToCh(legacyData: any) {
+ const patientIdSet = new Set();
+ const treatments: Array<{ treatment: string; count: number }> = [];
+
+ legacyData.forEach((legacyTreatment: any) => {
+ let treatment = {
+ treatment: legacyTreatment['treatment'],
+ count: legacyTreatment['count'],
+ };
+ treatments.push(treatment);
+
+ const samples = legacyTreatment['samples'];
+ if (samples instanceof Array) {
+ samples.forEach(sample => {
+ patientIdSet.add(sample['patientId']);
+ });
+ }
+ });
+
+ return {
+ totalPatients: patientIdSet.size,
+ totalSamples: 0,
+ patientTreatments: treatments,
+ };
+}
+
+export function deepSort(inp: any, label: string) {
+ const arrs = getArrays(inp, []);
+
+ arrs.forEach(arr => {
+ if (label in deleteFields) {
+ arr.forEach((m: any) => {
+ deleteFields[label].forEach(l => {
+ delete m[l];
+ });
+ });
+ }
+
+ if (!arr.length) return;
+ if (!isObject(arr[0])) {
+ arr.sort();
+ } else {
+ // it's an array of objects
+
+ // this is going to make sure the keys in the objects
+ // are in a sorted order
+ arr.forEach((o: any) => {
+ _.keys(o)
+ .sort()
+ .forEach(k => {
+ const val = o[k];
+ delete o[k];
+ o[k] = val;
+ });
+ });
+
+ //927275539
+
+ if (sortFields[label]) {
+ attemptSort(sortFields[label].split(','), arr);
+ } else {
+ const fields = [
+ 'attributeId',
+ 'value',
+ 'hugoGeneSymbol',
+ 'uniqueSampleKey',
+ 'alteration',
+ ];
+ fields.forEach(f => attemptSort([f], arr));
+ }
+ }
+ });
+
+ return inp;
+}
+
+function attemptSort(keys: string[], arr: any) {
+ arr.sort(dynamicSort(keys));
+}
+
+export function compareCounts(clData: any, legacyData: any, label: string) {
+ // @ts-ignore
+ const clDataClone = structuredClone(clData);
+ // @ts-ignore
+ const legacyDataClone = structuredClone(legacyData);
+
+ const clDataSorted = deepSort(clDataClone, label);
+ var legacyDataSorted = deepSort(legacyDataClone, label);
+
+ if (treatmentConverter[label]) {
+ legacyDataSorted = treatmentConverter[label](legacyDataSorted);
+ }
+ const result =
+ JSON.stringify(clDataSorted) === JSON.stringify(legacyDataSorted);
+
+ return {
+ clDataSorted,
+ legacyDataSorted,
+ status: result,
+ label,
+ };
+}
+
+export function validate(
+ url: string,
+ params: any,
+ label: string,
+ hash: number,
+ body?: any,
+ elapsedTime?: any
+) {
+ const clStart = performance.now();
+ let chDuration: number, legacyDuration: number;
+
+ let chXHR: any;
+
+ if (body) {
+ chXHR = Promise.resolve({ body, elapsedTime });
+ } else {
+ chXHR = $.ajax({
+ method: 'post',
+ url: url,
+ data: JSON.stringify(params),
+ contentType: 'application/json',
+ }).then((body, state, xhr) => {
+ return { body, elapsedTime: xhr.getResponseHeader('elapsed-time') };
+ });
+ }
+
+ return chXHR
+ .then(({ body, elapsedTime }: any) => {
+ let legacyUrl = url.replace(/column-store\//, '');
+ if (treatmentLegacyUrl[label]) {
+ legacyUrl = treatmentLegacyUrl[label](legacyUrl);
+ }
+ const legacyXHR = $.ajax({
+ method: 'post',
+ url: legacyUrl,
+ data: JSON.stringify(params),
+ contentType: 'application/json',
+ });
+ return legacyXHR.then(legacyResult => {
+ const result: any = compareCounts(body, legacyResult, label);
+ result.url = url;
+ result.hash = hash;
+ result.data = params;
+ result.chDuration = parseFloat(elapsedTime);
+ result.legacyDuration = parseFloat(
+ legacyXHR.getResponseHeader('elapsed-time') || ''
+ );
+ return result;
+ });
+ })
+ .catch(() => {
+ const result: any = {};
+ result.url = url;
+ result.hash = hash;
+ result.status = false;
+ result.data = params;
+ result.httpError = true;
+ return result;
+ });
+}
+
+export function reportValidationResult(result: any, prefix = '') {
+ const skipMessage =
+ result.test && result.test.skip ? `(SKIPPED ${result.test.skip})` : '';
+
+ !result.status &&
+ console.groupCollapsed(
+ `${prefix} ${result.label} (${result.hash}) ${skipMessage} failed :(`
+ );
+
+ !result.status &&
+ console.log('failed test', {
+ url: result.url,
+ test: result.test,
+ studies: result?.test?.studies,
+ legacyDuration: result.legacyDuration,
+ chDuration: result.chDuration,
+ equal: result.status,
+ httpError: result.httpError,
+ });
+
+ result.status &&
+ console.log(
+ `${prefix} ${result.label} (${
+ result.hash
+ }) passed :) ch: ${result.chDuration.toFixed(
+ 0
+ )} legacy: ${result.legacyDuration.toFixed(0)}`
+ );
+
+ if (!result.status) {
+ _.forEach(result.clDataSorted, (cl: any, i: number) => {
+ if (
+ JSON.stringify(cl) !==
+ JSON.stringify(result.legacyDataSorted[i])
+ ) {
+ console.groupCollapsed(`First invalid item (${result.label})`);
+ console.log('Clickhouse:', cl);
+ console.log('Legacy:', result.legacyDataSorted[i]);
+ console.groupEnd();
+ return false;
+ }
+ });
+ console.groupCollapsed('All Data');
+ console.log('legacy', result.legacyDataSorted);
+ console.log('CH', result.clDataSorted);
+ console.groupEnd();
+ }
+
+ !result.status && console.groupEnd();
+}
diff --git a/webpack.config.js b/webpack.config.js
index 3580b03db73..ebc65d486ff 100755
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -6,6 +6,8 @@ var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
var TerserPlugin = require('terser-webpack-plugin');
var { TypedCssModulesPlugin } = require('typed-css-modules-webpack-plugin');
+const fsProm = require('fs/promises');
+
var commit = '"unknown"';
var version = '"unknown"';
// Don't show COMMIT/VERSION on Heroku (crashes, because no git dir)
@@ -41,6 +43,9 @@ const dotenv = require('dotenv');
const webpack = require('webpack');
const path = require('path');
+const { watch } = require('fs');
+const fs = require('fs/promises');
+const { mergeApiTestJson } = require('./api-e2e/mergeJson');
const join = path.join;
const resolve = path.resolve;
@@ -170,6 +175,7 @@ var config = {
{ from: './common-dist', to: 'reactapp' },
{ from: './src/rootImages', to: 'images' },
{ from: './src/common', to: 'common' },
+ { from: './api-e2e/json', to: 'common' },
{
from: './src/globalStyles/prefixed-bootstrap.min.css',
to: 'reactapp/prefixed-bootstrap.min.css',
@@ -593,4 +599,14 @@ if (isTest) {
}
// End Testing
+mergeApiTestJson();
+
+if (isDev) {
+ watch('./apiTests/specs', async function(event, filename) {
+ if (event === 'change') {
+ mergeApiTestJson();
+ }
+ });
+}
+
module.exports = config;