-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Conversation
* Column order: | RUN | experimentAlias | HPARAMs | other | | ||
*/ | ||
export const getGroupedColumns = ( | ||
headersSelector: Selector<object, ColumnHeader[]> |
There was a problem hiding this comment.
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
);
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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') | ||
), | ||
]; |
There was a problem hiding this comment.
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.
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'], |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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.
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(); |
There was a problem hiding this comment.
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
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(); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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)
export type ColumnGroup = 'RUN' | 'EXPERIMENT_ALIAS' | 'HPARAM' | 'OTHER'; | |
export enum ColumnGroup { | |
RUN, | |
EXPERIMENT_ALIAS, | |
HPARAM, | |
OTHER | |
} |
There was a problem hiding this comment.
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.
## 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
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:
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