Skip to content

Commit

Permalink
feat: minify URL query parameters
Browse files Browse the repository at this point in the history
- Added the object `minifiedParams` to map all the params to their
  minified forms.
- Added the function `minifyParmas` that return a object with the params
  minified based on the object `minifiedParams`
- Added the object `groupedMinifiedParams` that divide the minified
  params in five groups based on the depth of the nested objects in
  searchParams: general, df (for DiffFilter nested object), ti (for
  TreeInfo nested object), tf (for TableFilter nested object) and value
  (for minified values instead of keys). This was required to identify
  different attributes that were minified to the same value (e.g. 't'
  for treeInfo.treeName, TableFilter.testsTable and diffFilter.trees)
- Added the function `unminifyParams` that return a object with the
  original param names based on the object `groupedMinfiedParams`.
- Added `minifyParams` to `stringifySearch` so the processing steps
  are minify -> flatten -> stringify
- Added `unminifyParams` to `parseSearch` so the processing steps are
  parse -> unflatten -> unminify
- Added tests for the new functions and changed tests for the old
  functions

Closes #725
  • Loading branch information
murilx committed Jan 8, 2025
1 parent baacaf8 commit 7aa610b
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 27 deletions.
108 changes: 93 additions & 15 deletions dashboard/src/utils/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
parseSearch,
stringifySearch,
isEncodedJSONArrayParam,
minifyParams,
unminifyParams,
} from './search';

const KEY_FLAT_CHAR = '|';
Expand All @@ -19,8 +21,14 @@ const simpleObject = {
treeIndexes: [1, 2, 3],
};

const simpleObjectStringify =
'?origin=maestro&intervalInDays=7&treeIndexes[]=1,2,3';
const simpleObjectMinify = {
o: 'maestro',
i: 7,
// eslint-disable-next-line no-magic-numbers
x: [1, 2, 3],
};

const simpleObjectStringify = '?o=maestro&i=7&x[]=1,2,3';

const nestedObject = {
origin: 'maestro',
Expand All @@ -32,7 +40,7 @@ const nestedObject = {
},
treeInfo: {
treeName: 'android',
gitCommitHash: 'hash',
headCommitHash: 'hash',
},
diffFilter: {
configs: { defconfig: true },
Expand All @@ -42,26 +50,60 @@ const nestedObject = {
treeIndexes: [0, 1, 2],
};

const nestedObjectMinify = {
o: 'maestro',
i: 7,
tf: {
bt: 'a',
b: 'f',
t: 'a',
},
ti: {
t: 'android',
ch: 'hash',
},
df: {
c: { defconfig: true },
a: { arm: true },
tp: 'amlogic',
},
x: [0, 1, 2],
};

const nestedObjectStringify =
'?o=maestro&i=7' +
'&tf|bt=a&tf|b=f&tf|t=a' +
'&ti|t=android&ti|ch=hash' +
'&df|c|defconfig=true&df|a|arm=true&df|tp=amlogic' +
'&x[]=0,1,2';

const flatObject = {
origin: 'maestro',
intervalInDays: 7,
'tableFilter|bootsTable': 'all',
'tableFilter|buildsTable': 'failed',
'tableFilter|testsTable': 'all',
'treeInfo|treeName': 'android',
'treeInfo|gitCommitHash': 'hash',
'treeInfo|headCommitHash': 'hash',
'diffFilter|configs|defconfig': true,
'diffFilter|archs|arm': true,
'diffFilter|testPath': 'amlogic',
treeIndexes: [0, 1, 2],
};

const nestedObjectStringify =
'?origin=maestro&intervalInDays=7' +
'&tableFilter|bootsTable=all&tableFilter|buildsTable=failed&tableFilter|testsTable=all' +
'&treeInfo|treeName=android&treeInfo|gitCommitHash=hash' +
'&diffFilter|configs|defconfig=true&diffFilter|archs|arm=true&diffFilter|testPath=amlogic' +
'&treeIndexes[]=0,1,2';
const flatObjectMinify = {
o: 'maestro',
i: 7,
'tf|bt': 'a',
'tf|b': 'f',
'tf|t': 'a',
'ti|t': 'android',
'ti|ch': 'hash',
'df|c|defconfig': true,
'df|a|arm': true,
'df|tp': 'amlogic',
x: [0, 1, 2],
};

describe('isEncodedArrayParam', () => {
const emptyJSONArrayStr = 'treeIndexes=[]';
Expand Down Expand Up @@ -149,15 +191,50 @@ describe('unflattenObject', () => {
});
});

describe('minifyParams', () => {
it('Simple object with filled array', () => {
expect(minifyParams(simpleObject)).toStrictEqual(simpleObjectMinify);
});

it('Simple object with empty array', () => {
const simpleObjectEmptyArray = { ...simpleObject, treeIndexes: [] };
const simpleObjectEmptyArrayMinify = { ...simpleObjectMinify, x: [] };
expect(minifyParams(simpleObjectEmptyArray)).toStrictEqual(
simpleObjectEmptyArrayMinify,
);
});

it('Nested object', () => {
expect(minifyParams(nestedObject)).toStrictEqual(nestedObjectMinify);
});
});

describe('unminifyParams', () => {
it('Simple object with filled array', () => {
expect(unminifyParams(simpleObjectMinify)).toStrictEqual(simpleObject);
});

it('Simple object with empty array', () => {
const simpleObjectEmptyArray = { ...simpleObject, treeIndexes: [] };
const simpleObjectEmptyArrayMinify = { ...simpleObjectMinify, x: [] };
expect(unminifyParams(simpleObjectEmptyArrayMinify)).toStrictEqual(
simpleObjectEmptyArray,
);
});

it('Nested object', () => {
expect(unminifyParams(nestedObjectMinify)).toStrictEqual(nestedObject);
});
});

describe('parseSearch', () => {
it('Simple object with filled array', () => {
expect(parseSearch(simpleObjectStringify)).toStrictEqual(simpleObject);
});

it('Simple object with empty array', () => {
const simpleObjectEmptyArray = { ...simpleObject, treeIndexes: [] };
const simpleObjectEmptyArrayStringify =
'?origin=maestro&intervalInDays=7&treeIndexes[]';
const simpleObjectEmptyArrayStringify = '?o=maestro&i=7&x[]';
expect(parseSearch(simpleObjectEmptyArrayStringify)).toStrictEqual(
simpleObjectEmptyArray,
);
Expand Down Expand Up @@ -235,17 +312,18 @@ describe('stringifySearch', () => {

it('Simple object with filled array', () => {
const result = stringifySearch(simpleObject);
assertSearchParams(result, simpleObject);
assertSearchParams(result, simpleObjectMinify);
});

it('Simple object with empty array', () => {
const simpleObjectEmptyArray = { ...simpleObject, treeIndexes: [] };
const simpleObjectEmptyArrayMinify = { ...simpleObjectMinify, x: [] };
const result = stringifySearch(simpleObjectEmptyArray);
assertSearchParams(result, simpleObjectEmptyArray);
assertSearchParams(result, simpleObjectEmptyArrayMinify);
});

it('Nested object', () => {
const result = stringifySearch(nestedObject);
assertSearchParams(result, flatObject);
assertSearchParams(result, flatObjectMinify);
});
});
184 changes: 172 additions & 12 deletions dashboard/src/utils/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,36 @@ export const parseSearch = (searchStr: string): AnySchema => {
arrayFormatSeparator: ARRAY_SEPARATOR,
parseBooleans: true,
types: {
intervalInDays: 'number',
diffFilter_buildDurationMax: 'number',
diffFilter_buildDurationMin: 'number',
diffFilter_bootDurationMin: 'number',
diffFilter_bootDurationMax: 'number',
diffFilter_testDurationMin: 'number',
diffFilter_testDurationMax: 'number',
treeIndexes: 'number[]',
startTimestampInSeconds: 'number',
endTimestampInSeconds: 'number',
i: 'number',
df_bdx: 'number',
df_bdm: 'number',
df_btdx: 'number',
df_btdm: 'number',
df_tdx: 'number',
df_tdm: 'number',
x: 'number[]',
st: 'number',
et: 'number',
},
});

Object.setPrototypeOf(flattenedParsedSearch, Object.prototype);
const parsedSearch = unflattenObject(flattenedParsedSearch, KEY_FLAT_CHAR);
const minifiedParsedSearch = unflattenObject(
flattenedParsedSearch,
KEY_FLAT_CHAR,
);
const parsedSearch = unminifyParams(minifiedParsedSearch);
return parsedSearch;
};

export const stringifySearch = (
searchParams: Record<string, unknown>,
): string => {
const flattenedSearchParams = flattenObject(searchParams, KEY_FLAT_CHAR);
const minifiedSearchParams = minifyParams(searchParams);
const flattenedSearchParams = flattenObject(
minifiedSearchParams,
KEY_FLAT_CHAR,
);
const stringifiedSearch = qs.stringify(flattenedSearchParams, {
arrayFormat: 'bracket-separator',
arrayFormatSeparator: ARRAY_SEPARATOR,
Expand Down Expand Up @@ -122,3 +130,155 @@ export const unflattenObject = (
}
return result;
};

const minifiedParams = {
origin: 'o',
intervalInDays: 'i',
currentPageTab: 'p',
tableFilter: 'tf',
diffFilter: 'df',
treeSearch: 'ts',
hardwareSearch: 'hs',
treeInfo: 'ti',
treeIndexes: 'x',
treeCommits: 'c',
startTimestampInSeconds: 'st',
endTimestampInSeconds: 'et',

//TreeInfo
gitBranch: 'gb',
gitUrl: 'gu',
treeName: 't',
commitName: 'c',
headCommitHash: 'ch',

// TableFilter
buildsTable: 'b',
bootsTable: 'bt',
testsTable: 't',

// DiffFilter
configs: 'c',
archs: 'a',
buildStatus: 'bs',
compilers: 'cp',
bootStatus: 'bts',
testStatus: 'ts',
testPath: 'tp',
bootPath: 'bp',
buildDurationMax: 'bdx',
buildDurationMin: 'bdm',
bootDurationMax: 'btdx',
bootDurationMin: 'btdm',
testDurationMax: 'tdx',
testDurationMin: 'tdm',
hardware: 'h',
trees: 't',
buildPlatform: 'bpf',
bootPlatform: 'btpf',
testPlatform: 'tpf',
buildIssue: 'bi',
bootIssue: 'bti',
testIssue: 'ti',

// Values
all: 'a',
success: 's',
failed: 'f',
inconclusive: 'i',
valid: 'v',
invalid: 'iv',
null: 'n',
} as const;
type MinifiedParamsKeys = keyof typeof minifiedParams;

export const minifyParams = (
searchParams: Record<string, unknown>,
): Record<string, unknown> => {
const result: Record<string, unknown> = {};
for (const key in searchParams) {
if (Object.prototype.hasOwnProperty.call(searchParams, key)) {
const newKey =
key in minifiedParams ? minifiedParams[key as MinifiedParamsKeys] : key;
if (isStringRecord(searchParams[key])) {
result[newKey] = minifyParams(searchParams[key]);
} else {
const value =
typeof searchParams[key] === 'string' &&
searchParams[key] in minifiedParams
? minifiedParams[searchParams[key] as MinifiedParamsKeys]
: searchParams[key];
result[newKey] = value;
}
}
}
return result;
};

const GENERAL_LAST_ENTRY = 12;
const TI_LAST_ENTRY = 17;
const TF_LAST_ENTRY = 20;
const DF_LAST_ENTRY = 41;
const VALUE_LAST_ENTRY = 49;
const minifiedArray = Object.entries(minifiedParams);
const groupedMinifiedParams = {
general: Object.fromEntries(
minifiedArray
.slice(0, GENERAL_LAST_ENTRY)
.map(([key, value]) => [value, key]),
),
ti: Object.fromEntries(
minifiedArray
.slice(GENERAL_LAST_ENTRY, TI_LAST_ENTRY)
.map(([key, value]) => [value, key]),
),
tf: Object.fromEntries(
minifiedArray
.slice(TI_LAST_ENTRY, TF_LAST_ENTRY)
.map(([key, value]) => [value, key]),
),
df: Object.fromEntries(
minifiedArray
.slice(TF_LAST_ENTRY, DF_LAST_ENTRY)
.map(([key, value]) => [value, key]),
),
value: Object.fromEntries(
minifiedArray
.slice(DF_LAST_ENTRY, VALUE_LAST_ENTRY)
.map(([key, value]) => [value, key]),
),
};
type GroupedMinifiedKeys = keyof typeof groupedMinifiedParams;

export const unminifyParams = (
searchParams: Record<string, unknown>,
group: GroupedMinifiedKeys = 'general',
): Record<string, unknown> => {
const result: Record<string, unknown> = {};
for (const key in searchParams) {
if (Object.prototype.hasOwnProperty.call(searchParams, key)) {
const newKey =
key in groupedMinifiedParams[group]
? groupedMinifiedParams[group][key as GroupedMinifiedKeys]
: key;
if (isStringRecord(searchParams[key])) {
result[newKey] = unminifyParams(
searchParams[key],
key in groupedMinifiedParams
? (key as GroupedMinifiedKeys)
: 'general',
);
} else {
const value =
typeof searchParams[key] === 'string' &&
searchParams[key] in groupedMinifiedParams['value']
? groupedMinifiedParams['value'][
searchParams[key] as GroupedMinifiedKeys
]
: searchParams[key];
result[newKey] = value;
}
}
}
return result;
};

0 comments on commit 7aa610b

Please sign in to comment.