(() => ({
+ tests: [],
+ show: !!localStorage.getItem(RFC_TEST_SHOW),
+ listening: !!localStorage.getItem(SAVE_TEST_KEY),
+ validate: !!localStorage.getItem(LIVE_VALIDATE_KEY),
+ }));
+
+ const clearCacheCallback = useCallback(() => {
+ clearCache();
+ }, []);
+
+ const toggleListener = useCallback(() => {
+ store.listening = !store.listening;
+ if (getBrowserWindow().localStorage.getItem(SAVE_TEST_KEY)) {
+ getBrowserWindow().localStorage.removeItem(SAVE_TEST_KEY);
+ } else {
+ getBrowserWindow().localStorage.setItem(SAVE_TEST_KEY, 'true');
+ }
+ }, []);
+
+ const toggleShow = useCallback(() => {
+ !!localStorage.getItem(RFC_TEST_SHOW)
+ ? localStorage.removeItem(RFC_TEST_SHOW)
+ : localStorage.setItem(RFC_TEST_SHOW, 'true');
+ store.show = !store.show;
+ }, []);
+
+ const toggleLiveValidate = useCallback(() => {
+ !!localStorage.getItem(LIVE_VALIDATE_KEY)
+ ? localStorage.removeItem(LIVE_VALIDATE_KEY)
+ : localStorage.setItem(LIVE_VALIDATE_KEY, 'true');
+ store.validate = !store.validate;
+ }, []);
+
+ const runTests = useCallback(async () => {
+ let json = [];
+
+ try {
+ json = await $.getJSON(
+ 'https://localhost:3000/common/merged-tests.json'
+ );
+ } catch (ex) {
+ alert('merged-tests.json not found');
+ }
+
+ const fileFilter = $('#apiTestFilter')
+ .val()
+ ?.toString();
+
+ const files: any[] = fileFilter?.trim().length
+ ? json.filter((f: any) => new RegExp(fileFilter).test(f.file))
+ : json;
+
+ await runSpecs(files, axios, '', 'verbose');
+ }, []);
+
+ 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..efe2de7ed8c 100644
--- a/src/shared/api/cbioportalInternalClientInstance.ts
+++ b/src/shared/api/cbioportalInternalClientInstance.ts
@@ -1,5 +1,148 @@
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';
+import axios from 'axios';
+
+// 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 = getLoadConfig().baseUrl;
+
+ const oldRequest = this.request;
+
+ const endpoints = [
+ 'ClinicalDataCounts',
+ 'MutatedGenes',
+ 'CaseList',
+ 'ClinicalDataBin',
+ 'MolecularProfileSample',
+ 'CNAGenes',
+ 'StructuralVariantGenes',
+ 'FilteredSamples',
+ 'ClinicalDataDensity',
+ 'MutationDataCounts',
+ 'PatientTreatmentCounts',
+ 'SampleTreatmentCounts',
+ 'GenomicData',
+ 'GenericAssay',
+ 'ViolinPlots',
+ 'ClinicalEventTypeCounts',
+ ];
+
+ 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(response: any) {
+ 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(
+ axios,
+ url,
+ params,
+ matchedMethod[0],
+ hash,
+ response.body
+ ).then((result: any) => {
+ reportValidationResult(result, 'LIVE', 'verbose');
+ });
+
+ 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');
+proxyColumnStore(internalClientColumnStore, 'fetchClinicalDataViolinPlots');
+
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..67edd38697e
--- /dev/null
+++ b/src/shared/api/validation.ts
@@ -0,0 +1,619 @@
+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)) {
+ for (const k in inp) {
+ if (/\d\.\d{10,}$/.test(inp[k])) {
+ try {
+ inp[k] = inp[k].toFixed(5);
+ } catch (ex) {}
+ }
+ }
+
+ if (inp.counts) {
+ inp.counts = inp.counts.filter((n: any) => {
+ return n.label != 'NA';
+ });
+ }
+
+ // this is get rid if extraneouys properties that conflict
+ delete inp.matchingGenePanelIds;
+ delete inp.cytoband;
+ delete inp.numberOfProfiledCases;
+
+ Object.values(inp).forEach(nn => getArrays(nn, output));
+ }
+ return output;
+}
+
+const deleteFields: Record = {
+ MolecularProfileSampleCounts: ['label'],
+ CaseList: ['label'],
+ SampleListsCounts: ['label'],
+ CnaGenes: ['qValue'],
+ MutatedGenes: ['qValue'],
+};
+
+const sortFields: Record = {
+ ClinicalDataBinCounts: 'attributeId,specialValue',
+ ClinicalDataBin: 'attributeId,specialValue',
+ FilteredSamples: 'studyId,patientId,sampleId',
+ SampleTreatmentCounts: 'treatment,time',
+ PatientTreatmentCounts: 'treatment',
+ ClinicalDataCounts: 'attributeId,value',
+ ClinicalDataTypeCounts: 'eventType',
+ ClinicalEventTypeCounts: 'eventType',
+};
+
+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 = {
+ count: legacySampleTreatment['count'],
+ samples: new Array(),
+ time: legacySampleTreatment['time'],
+ treatment: legacySampleTreatment['treatment'],
+ };
+
+ 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 = {
+ count: legacyTreatment['count'],
+ treatment: legacyTreatment['treatment'],
+ };
+ 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];
+ });
+ });
+ }
+
+ arr.forEach((m: any) => {
+ if (m.value && m.value.toLowerCase) m.value = m.value.toLowerCase();
+ });
+
+ arr.forEach((m: any) => {
+ if (m.specialValue && m.specialValue.toLowerCase)
+ m.specialValue = m.specialValue.toLowerCase();
+ });
+
+ 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) => {
+ Object.keys(o)
+ .sort()
+ .forEach(k => {
+ const val = o[k];
+ delete o[k];
+ o[k] = val;
+ });
+ });
+
+ 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));
+}
+
+let win: any;
+
+try {
+ win = window;
+} catch (ex) {
+ win = {};
+}
+
+function removeElement(nums: any[], val: any) {
+ for (let i = 0; i < nums.length; i++) {
+ if (nums[i] === val) {
+ nums.splice(i, 1);
+ i--;
+ }
+ }
+}
+
+export function compareCounts(clData: any, legacyData: any, label: string) {
+ // @ts-ignore
+ let clDataClone = win.structuredClone ? structuredClone(clData) : clData;
+
+ let legacyDataClone = win.structuredClone
+ ? // @ts-ignore
+ structuredClone(legacyData)
+ : legacyData;
+
+ // get trid of duplicates
+ //clDataClone = filterDuplicates(clDataClone);
+
+ var clDataSorted = deepSort(clDataClone, label);
+ var legacyDataSorted = deepSort(legacyDataClone, label);
+
+ getArrays(clDataSorted, []).forEach((arr: any) => {
+ arr.filter((n: any) => /NA/i.test(n.value)).forEach((val: any) => {
+ removeElement(arr, val);
+ });
+ });
+
+ getArrays(legacyDataSorted, []).forEach((arr: any) => {
+ arr.filter((n: any) => /NA/i.test(n.value)).forEach((val: any) => {
+ removeElement(arr, val);
+ });
+ });
+
+ // get rid of these little guys
+ if (clDataSorted && clDataSorted.filter)
+ clDataSorted = clDataSorted.filter((n: any) => n.specialValue != 'NA');
+
+ if (legacyDataSorted && legacyDataSorted.filter)
+ legacyDataSorted = legacyDataSorted.filter(
+ (n: any) => n.specialValue != 'NA'
+ );
+
+ if (treatmentConverter[label]) {
+ legacyDataSorted = treatmentConverter[label](legacyDataSorted);
+ }
+ const result =
+ JSON.stringify(clDataSorted) === JSON.stringify(legacyDataSorted);
+
+ return {
+ clDataSorted,
+ legacyDataSorted,
+ status: result,
+ label,
+ };
+}
+
+export async function validate(
+ ajax: any,
+ url: string,
+ params: any,
+ label: string,
+ hash: number,
+ body?: any,
+ elapsedTime: any = 0,
+ assertResponse: any[] | undefined = undefined,
+ onFail: (...args: any[]) => void = () => {}
+) {
+ let chXHR: any;
+
+ let chResult;
+ let legacyResult;
+
+ if (body) {
+ chResult = { body, elapsedTime, status: 200 };
+ } else {
+ chResult = await ajax
+ .post(url, params)
+ .then(function(response: any) {
+ return {
+ status: response.status,
+ body: response.data,
+ elapsedTime: response.headers['elapsed-time'],
+ };
+ })
+ .catch(function(error: any) {
+ return {
+ body: null,
+ error,
+ elapsedTime: null,
+ status: error.status,
+ };
+ });
+ }
+
+ if (assertResponse) {
+ legacyResult = assertResponse;
+ } else {
+ let legacyUrl = url.replace(/column-store\//, '');
+
+ if (treatmentLegacyUrl[label]) {
+ legacyUrl = treatmentLegacyUrl[label](legacyUrl);
+ }
+
+ legacyResult = await ajax
+ .post(legacyUrl, params)
+ .then(function(response: any) {
+ return {
+ status: response.status,
+ body: response.data,
+ elapsedTime: response.headers['elapsed-time'],
+ };
+ })
+ .catch(function(error: any) {
+ return {
+ body: null,
+ error,
+ elapsedTime: null,
+ status: error.status,
+ };
+ });
+ }
+
+ const result: any = compareCounts(chResult.body, legacyResult.body, label);
+ result.url = url;
+ result.hash = hash;
+ result.data = params;
+ result.chDuration = chResult.elapsedTime;
+ result.legacyDuration = !assertResponse && legacyResult.elapsedTime;
+ result.chError = chResult.error;
+
+ if (!result.status) {
+ onFail(url);
+ }
+
+ return result;
+}
+
+const red = '\x1b[31m';
+const green = '\x1b[32m';
+const blue = '\x1b[36m';
+const reset = '\x1b[0m';
+
+export function reportValidationResult(
+ result: any,
+ prefix = '',
+ logLevel = ''
+) {
+ const skipMessage =
+ result.test && result.test.skip ? `(SKIPPED ${result.test.skip})` : '';
+
+ const errorStatus = result.chError ? `(${result.chError.status})` : '';
+
+ const data = result.data || result?.test.data;
+ const studies = (data.studyIds || data.studyViewFilter.studyIds).join(',');
+
+ !result.status &&
+ !result.supressed &&
+ console.groupCollapsed(
+ `${red} ${prefix} ${result.label} (${result.hash}) ${skipMessage} failed (${errorStatus}) ${studies} :( ${reset}`
+ );
+
+ if (result.supressed) {
+ console.log(
+ `${blue} ${prefix} ${result.label} (${result.hash}) ${skipMessage} SUPPRESSED :( ${reset}`
+ );
+ }
+
+ if (logLevel === 'verbose' && !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,
+ });
+ }
+
+ if (result.status) {
+ console.log(
+ `${prefix} ${result.label} (${result.hash}) passed :) ch: ${
+ result.chDuration
+ } legacy: ${result.legacyDuration && result.legacyDuration}`
+ );
+ }
+
+ if (!result.status && logLevel == 'verbose') {
+ if (result?.clDataSorted?.length && result?.legacyDataSorted?.length) {
+ for (var i = 0; i < result?.clDataSorted?.length; i++) {
+ const cl = result.clDataSorted[i];
+ 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();
+ break;
+ }
+ }
+ }
+ console.groupCollapsed('All Data');
+ console.log(
+ `CH: ${result?.clDataSorted?.length}, Legacy:${result?.legacyDataSorted?.length}`
+ );
+ console.log('legacy', result.legacyDataSorted);
+ console.log('CH', result.clDataSorted);
+ console.groupEnd();
+ }
+
+ !result.status && console.groupEnd();
+}
+
+export async function runSpecs(
+ files: any,
+ axios: any,
+ host: string = '',
+ logLevel = '',
+ onFail: any = () => {},
+ supressors: any = []
+) {
+ // @ts-ignore
+ const allTests = files
+ // @ts-ignore
+ .flatMap((n: any) => n.suites)
+ // @ts-ignore
+ .flatMap((n: any) => n.tests);
+
+ const totalCount = allTests.length;
+
+ const onlyDetected = allTests.some((t: any) => t.only === true);
+
+ console.log(`Running specs (${files.length} of ${totalCount})`);
+
+ if (logLevel === 'verbose') {
+ console.groupCollapsed('specs');
+ //console.log('raw', json);
+ console.log('filtered', files);
+ console.groupEnd();
+ }
+
+ let place = 0;
+ let errors: any[] = [];
+ let skips: any[] = [];
+ let passed: any[] = [];
+ let httpErrors: any[] = [];
+ let supressed: any[] = [];
+
+ const invokers: (() => Promise)[] = [] 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'
+ );
+
+ if (!onlyDetected || test.only) {
+ invokers.push(
+ // @ts-ignore
+ () => {
+ return validate(
+ axios,
+ host + test.url,
+ test.data,
+ test.label,
+ test.hash,
+ undefined,
+ undefined,
+ test.assertResponse
+ ).then((report: any) => {
+ if (!report.status) {
+ onFail(test, report);
+ }
+
+ report.test = test;
+ place = place + 1;
+ const prefix = `${place} of ${totalCount}`;
+ if (report instanceof Promise) {
+ report.then((report: any) => {
+ 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,
+ logLevel
+ );
+ });
+ } else {
+ if (test?.skip) {
+ skips.push(test.hash);
+ } else if (!report.status) {
+ let supress = [];
+
+ supress = supressors
+ .map((f: any) => {
+ try {
+ return f(report);
+ } catch (exc) {
+ return false;
+ }
+ })
+ .filter((r: any) => r);
+
+ if (supress.length) {
+ supressed.push(test.hash);
+ report.supressed = true;
+ } else {
+ report.httpError
+ ? httpErrors.push(test.hash)
+ : errors.push(test.hash);
+ }
+ } else if (report.status)
+ passed.push(test.hash);
+
+ reportValidationResult(
+ report,
+ prefix,
+ logLevel
+ );
+ }
+ });
+ }
+ );
+ }
+ })
+ );
+ });
+
+ const concurrent = 10;
+ const batches = Math.ceil(invokers.length / concurrent);
+
+ for (var i = 0; i < batches; i++) {
+ const proms = [];
+ for (const inv of invokers.slice(
+ i * concurrent,
+ (i + 1) * concurrent
+ )) {
+ proms.push(inv());
+ }
+ await Promise.all(proms);
+ }
+
+ 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.log(`SUPRESSED: ${supressed.length} (${supressed.join(',')})`);
+ console.groupEnd();
+ // console.groupEnd();
+}
diff --git a/webpack.config.js b/webpack.config.js
index 3580b03db73..9434fb440b6 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',
diff --git a/yarn.lock b/yarn.lock
index 840732ddb91..f254a2c2041 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1135,6 +1135,18 @@
dependencies:
commander "^2.15.1"
+"@clickhouse/client-common@1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@clickhouse/client-common/-/client-common-1.6.0.tgz#371a64592f55758ac7ddca707a30b78cfc656f84"
+ integrity sha512-DPdfPUIrBumWff8JdE2oLqnOIN1DwxXt48juV+tB6zaonufQR1QImtUGEhIFIqRWJU7zErtQ1eKfkwjLY0MYDg==
+
+"@clickhouse/client@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@clickhouse/client/-/client-1.6.0.tgz#703d7b4aef35c60987db9e8f8f4420da1f93adc3"
+ integrity sha512-cjuOL5O11Y/axYIvSaNgZjZqD53BBbScrG0DCgZ7XBCz5KWlA5lIG3fP6A4jME277q8ZUYAQxUOlJjeF18872A==
+ dependencies:
+ "@clickhouse/client-common" "1.6.0"
+
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
@@ -5411,6 +5423,15 @@ aws4@^1.2.1, aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+axios@^1.7.7:
+ version "1.7.7"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
+ integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
babel-cli@^6.4.5:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1"
@@ -6920,6 +6941,17 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
+call-bind@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+ integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.1"
+
call-me-maybe@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -8941,6 +8973,15 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
+csvtojson@^2.0.10:
+ version "2.0.10"
+ resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574"
+ integrity sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==
+ dependencies:
+ bluebird "^3.5.1"
+ lodash "^4.17.3"
+ strip-bom "^2.0.0"
+
cubic-hermite@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cubic-hermite/-/cubic-hermite-1.0.0.tgz#84e3b2f272b31454e8393b99bb6aed45168c14e5"
@@ -9463,6 +9504,15 @@ defaults@^1.0.3:
dependencies:
clone "^1.0.2"
+define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
@@ -10268,6 +10318,18 @@ es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.5.0, es-abstract@^1.5.1
is-regex "^1.0.4"
object-keys "^1.0.12"
+es-define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+ integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
+ dependencies:
+ get-intrinsic "^1.2.4"
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
es-module-lexer@^0.9.0:
version "0.9.1"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.1.tgz#f203bf394a630a552d381acf01a17ef08843b140"
@@ -11211,6 +11273,11 @@ follow-redirects@^1.0.0:
dependencies:
debug "^3.2.6"
+follow-redirects@^1.15.6:
+ version "1.15.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
font-atlas-sdf@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/font-atlas-sdf/-/font-atlas-sdf-1.3.3.tgz#8323f136c69d73a235aa8c6ada640e58f180b8c0"
@@ -11313,6 +11380,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+form-data@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
+ integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
form-data@~2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
@@ -11322,6 +11398,11 @@ form-data@~2.1.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
+format-curl@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/format-curl/-/format-curl-2.2.1.tgz#06c68beb7568616dfb4a62e7f935449064845ecb"
+ integrity sha512-6w/KmhD9wjBdqRezh9zXUHcCtJeaaqtz3li1R/2yEmLnFlj7C06+B0r30tyRKQ0siwoRjk/JRi6P3IUaeSBObA==
+
formidable@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659"
@@ -11476,6 +11557,11 @@ function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
function.prototype.name@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
@@ -11611,6 +11697,17 @@ get-intrinsic@^1.0.2:
has "^1.0.3"
has-symbols "^1.0.1"
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+ integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
get-own-enumerable-property-symbols@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b"
@@ -12431,6 +12528,13 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
@@ -12544,6 +12648,18 @@ has-passive-events@^1.0.0:
dependencies:
is-browser "^2.0.1"
+has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-proto@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+ integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
@@ -12554,6 +12670,11 @@ has-symbols@^1.0.1:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
has-unicode@^2.0.0, has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -12613,6 +12734,13 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
+hasown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
hast-to-hyperscript@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz#3decd7cb4654bca8883f6fcbd4fb3695628c4296"
@@ -14598,6 +14726,11 @@ jest@^27.2.1:
import-local "^3.0.2"
jest-cli "^27.2.1"
+jquery-deferred@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/jquery-deferred/-/jquery-deferred-0.3.1.tgz#596eca1caaff54f61b110962b23cafea74c35355"
+ integrity sha512-YTzoTYR/yrjmNh6B6exK7lC1jlDazEzt9ZlZvdRscv+I1AJqN1SmU3ZAn4iMGiVhwAavCrbijDVyTc0lmr9ZCA==
+
jquery-migrate@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/jquery-migrate/-/jquery-migrate-3.0.0.tgz#03f320596fe1a1db09cdcfb74b14571994aad7bb"
@@ -15510,7 +15643,7 @@ lodash.words@^3.0.0:
dependencies:
lodash._root "^3.0.0"
-lodash@4.x, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@4.x, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -16928,6 +17061,15 @@ mz@^2.5.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
+najax@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/najax/-/najax-1.0.7.tgz#706dce52d4b738dce01aee97f392ccdb79d51eef"
+ integrity sha512-JqBMguf2plv1IDqhOE6eebnTivjS/ej0C/Sw831jVc+dRQIMK37oyktdQCGAQtwpl5DikOWI2xGfIlBPSSLgXg==
+ dependencies:
+ jquery-deferred "^0.3.0"
+ lodash "^4.17.21"
+ qs "^6.2.0"
+
nan@^2.12.1:
version "2.13.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
@@ -17477,6 +17619,11 @@ object-inspect@^1.1.0, object-inspect@~1.6.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
+object-inspect@^1.13.1:
+ version "1.13.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
+ integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
+
object-inspect@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
@@ -19663,6 +19810,11 @@ proxy-addr@~2.0.4, proxy-addr@~2.0.5:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -19772,6 +19924,13 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+qs@^6.2.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+ dependencies:
+ side-channel "^1.0.6"
+
qs@^6.3.0, qs@^6.5.1:
version "6.6.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2"
@@ -22165,6 +22324,18 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+set-function-length@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
set-immediate-shim@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
@@ -22307,6 +22478,16 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
+side-channel@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+ integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+ dependencies:
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.4"
+ object-inspect "^1.13.1"
+
sigmund@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"