Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds common selectors for shared hparams #6732

Merged
merged 6 commits into from
Jan 31, 2024
Merged

Conversation

hoonji
Copy link
Member

@hoonji hoonji commented Jan 24, 2024

Motivation for features / changes

Displaying shared hparam columns in the runs and scalar tables requires new selectors.

Technical description of changes

Adds two new common selectors that will be used by both runs and scalar tables:

  • getSelectableColumns
  • getGroupedColumns

Also updates getDashboardDisplayedHparamColumns to only return relevant hparams (i.e. those with specs defined by selected experiments)

Detailed steps to verify changes work correctly (as executed by you)

Unit tests pass

@hoonji hoonji requested a review from rileyajones January 25, 2024 09:30
* Column order: | RUN | experimentAlias | HPARAMs | other |
*/
export const getGroupedColumns = (
headersSelector: Selector<object, ColumnHeader[]>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my opinion, feel free to push back.

We don't use the pattern of passing a selector to a selector anywhere else in the TB codebase. Due to this I expect this to confuse future devs coming to the project.
I would rather see this selectors projector moved to a utility function and shared by a series of selectors (not selector factories) created to select each type.

The result might look something like this

function groupHeaders(tableHeaders: ColumnHeader[], hparamHeaders: []) {
   // The projector function from the getGroupedColumns selector
}

export const selectGroupedRunTableHeaders = createSelector(
  getRunsTableHeaders,
  getDashboardDisplayedHparamColumns,
  groupHeaders
);

export const selectGroupedDataTableHeaders = createSelector(
  getDataTableHeaders,
  getDashboardDisplayedHparamColumns,
  groupHeaders
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather see this selectors projector moved to a utility function and shared by a series of selectors

This is indeed a much simpler abstraction than passing around selectors as function arguments. I created a groupColumns helper similar to what you described. I put it in /widgets/data_table/utils.ts because it doesn't really belong in the /metrics directory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also moved the runs and metrics selectors to their respective directories, and added a simple selector test for each.

Comment on lines 319 to 331
return [
...tableHeaders.filter((header) => header.type === 'RUN'),
...tableHeaders.filter(
(header) =>
header.type === 'CUSTOM' && header.name === 'experimentAlias'
),
...hparamHeaders,
...tableHeaders.filter(
(header) =>
header.type !== 'RUN' &&
!(header.type === 'CUSTOM' && header.name === 'experimentAlias')
),
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this approach of running a series of filters, then dumping everything which doesn't match at the end will scale if you end up with a handful of additional confitions.

Suggested change
return [
...tableHeaders.filter((header) => header.type === 'RUN'),
...tableHeaders.filter(
(header) =>
header.type === 'CUSTOM' && header.name === 'experimentAlias'
),
...hparamHeaders,
...tableHeaders.filter(
(header) =>
header.type !== 'RUN' &&
!(header.type === 'CUSTOM' && header.name === 'experimentAlias')
),
];
const headersGroups: Record<string, ColumnHeader[]> = {
RUN: [],
EXPERIMENT_ALIAS: [],
OTHER: [],
};
tableHeaders.forEach((header) => {
if (header.type === 'RUN') {
headersGroups['RUN'].push(header);
return;
}
if (header.type === 'CUSTOM' && header.name === 'experimentAlias') {
headerGroups['EXPERIMENT_ALIAS'].push(header);
return;
}
headersGroups['OTHER'].push(header);
})
return [
...headerGroups['RUN'],
...headerGroups['EXPERIMENT_ALIAS'],
...hparamHeaders,
...headerGroups['OTHER'],

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also wondering about a more robust way to do this and you gave me an idea. I implemented something along these lines in /widgets/data_table/utils.ts:groupColumns, PTAL!

import {
MetricsState,
METRICS_FEATURE_KEY,
TagMetadata,
TimeSeriesData,
} from './store';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed dependency on store to prevent a circular dependency. All required symbols can be imported from /metrics_types instead.

@hoonji hoonji requested a review from rileyajones January 26, 2024 08:08
Comment on lines 32 to 41
const headerGroups = new Map<ColumnGroup, ColumnHeader[]>([
['RUN', []],
['EXPERIMENT_ALIAS', []],
['HPARAM', []],
['OTHER', []],
]);
columns.forEach((column) => {
headerGroups.get(columnToGroup(column))?.push(column);
});
return Array.from(headerGroups.values()).flat();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for setting up the proper mapped type, I was hoping you would do that when I left my previous comment about this structure.

If you use a record here (instead of a map) you won't need that null check

Suggested change
const headerGroups = new Map<ColumnGroup, ColumnHeader[]>([
['RUN', []],
['EXPERIMENT_ALIAS', []],
['HPARAM', []],
['OTHER', []],
]);
columns.forEach((column) => {
headerGroups.get(columnToGroup(column))?.push(column);
});
return Array.from(headerGroups.values()).flat();
const headerGroups: Record<ColumnGroup, ColumnHeader[]> = {
RUN: [],
EXPERIMENT_ALIAS: [],
HPARAM: [],
OTHER: [],
};
columns.forEach((column) => {
headerGroups[columnToGroup(column)].push(column);
});
return Object.values(headerGroups).flat();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think we need Map here to guarantee key order [1] - I added a comment to clarify this

[1] (see "Key Order") https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#objects_vs._maps

@@ -129,3 +129,5 @@ export interface AddColumnEvent {
nextTo?: ColumnHeader | undefined;
side?: Side | undefined;
}

export type ColumnGroup = 'RUN' | 'EXPERIMENT_ALIAS' | 'HPARAM' | 'OTHER';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this should be an enum as it is a bit more consistent with the rest of our code (though I personally do like string literals)

Suggested change
export type ColumnGroup = 'RUN' | 'EXPERIMENT_ALIAS' | 'HPARAM' | 'OTHER';
export enum ColumnGroup {
RUN,
EXPERIMENT_ALIAS,
HPARAM,
OTHER
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I'll prefer enums going forward.

@hoonji hoonji merged commit 304046d into master Jan 31, 2024
16 checks passed
@hoonji hoonji deleted the hparam_common_selectors branch January 31, 2024 06:33
@hoonji hoonji mentioned this pull request Jan 31, 2024
hoonji added a commit that referenced this pull request Feb 1, 2024
## Motivation for features / changes
Recently merged hparam column PRs (#6732, #6733, #6736) will cause build
and lint errors during 1p sync.

## Technical description of changes
- import map from rxjs/operators instead of rxjs
- `DataTableUtils` -> `dataTableUtils`
- use proper string signature on CardStateMap in tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants