Skip to content

Commit

Permalink
feat: create Searches view (#9089)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkim-det authored Apr 5, 2024
1 parent 65339d2 commit bdab9e4
Show file tree
Hide file tree
Showing 14 changed files with 1,691 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface ColumnMenuProps {
onVisibleColumnChange?: (newColumns: string[]) => void;
projectColumns: Loadable<ProjectColumn[]>;
projectId: number;
tabs: (V1LocationType | V1LocationType[])[];
}

interface ColumnTabProps {
Expand Down Expand Up @@ -233,6 +234,7 @@ const ColumnPickerMenu: React.FC<ColumnMenuProps> = ({
projectId,
isMobile = false,
onVisibleColumnChange,
tabs,
}) => {
const [searchString, setSearchString] = useState('');
const [open, setOpen] = useState(false);
Expand All @@ -259,32 +261,46 @@ const ColumnPickerMenu: React.FC<ColumnMenuProps> = ({
<Dropdown
content={
<div className={css.base}>
<Pivot
items={[
V1LocationType.EXPERIMENT,
[V1LocationType.VALIDATIONS, V1LocationType.TRAINING, V1LocationType.CUSTOMMETRIC],
V1LocationType.HYPERPARAMETERS,
].map((tab) => {
const canonicalTab = Array.isArray(tab) ? tab[0] : tab;
return {
children: (
<ColumnPickerTab
columnState={initialVisibleColumns}
handleShowSuggested={handleShowSuggested}
projectId={projectId}
searchString={searchString}
setSearchString={setSearchString}
tab={tab}
totalColumns={totalColumns}
onVisibleColumnChange={onVisibleColumnChange}
/>
),
forceRender: true,
key: canonicalTab,
label: locationLabelMap[canonicalTab],
};
})}
/>
{tabs.length > 1 && (
<Pivot
items={[
V1LocationType.EXPERIMENT,
[V1LocationType.VALIDATIONS, V1LocationType.TRAINING, V1LocationType.CUSTOMMETRIC],
V1LocationType.HYPERPARAMETERS,
].map((tab) => {
const canonicalTab = Array.isArray(tab) ? tab[0] : tab;
return {
children: (
<ColumnPickerTab
columnState={initialVisibleColumns}
handleShowSuggested={handleShowSuggested}
projectId={projectId}
searchString={searchString}
setSearchString={setSearchString}
tab={tab}
totalColumns={totalColumns}
onVisibleColumnChange={onVisibleColumnChange}
/>
),
forceRender: true,
key: canonicalTab,
label: locationLabelMap[canonicalTab],
};
})}
/>
)}
{tabs.length === 1 && (
<ColumnPickerTab
columnState={initialVisibleColumns}
handleShowSuggested={handleShowSuggested}
projectId={projectId}
searchString={searchString}
setSearchString={setSearchString}
tab={tabs[0]}
totalColumns={totalColumns}
onVisibleColumnChange={onVisibleColumnChange}
/>
)}
</div>
}
open={open}
Expand Down
14 changes: 5 additions & 9 deletions webui/react/src/components/DynamicTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,23 @@ import { useNavigate, useParams } from 'react-router-dom';
interface DynamicTabBarProps extends Omit<PivotProps, 'activeKey' | 'type'> {
basePath: string;
type?: PivotTabType;
children?: React.ReactNode;
}

type TabBarUpdater = (node?: JSX.Element) => void;

const TabBarContext = createContext<TabBarUpdater | undefined>(undefined);

const DynamicTabs: React.FC<DynamicTabBarProps> = ({
basePath,
children,
...props
}): JSX.Element => {
const DynamicTabs: React.FC<DynamicTabBarProps> = ({ basePath, items, ...props }): JSX.Element => {
const [tabBarExtraContent, setTabBarExtraContent] = useState<JSX.Element | undefined>();

const navigate = useNavigate();

const [tabKeys, setTabKeys] = useState<string[]>([]);

useEffect(() => {
const newTabKeys = React.Children.map(children, (c) => (c as { key: string })?.key ?? '');
const newTabKeys = items?.map((c) => c.key ?? '');
if (Array.isArray(newTabKeys) && !_.isEqual(newTabKeys, tabKeys)) setTabKeys(newTabKeys);
}, [children, tabKeys]);
}, [items, tabKeys]);

const { tab } = useParams<{ tab: string }>();

Expand All @@ -46,7 +41,7 @@ const DynamicTabs: React.FC<DynamicTabBarProps> = ({
}, [tab]);

useEffect(() => {
if (!activeKey && tabKeys.length) {
if ((!activeKey || !tabKeys.includes(activeKey)) && tabKeys.length) {
navigate(`${basePath}/${tabKeys[0]}`, { replace: true });
}
}, [activeKey, tabKeys, handleTabSwitch, basePath, navigate]);
Expand All @@ -60,6 +55,7 @@ const DynamicTabs: React.FC<DynamicTabBarProps> = ({
<Pivot
{...props}
activeKey={activeKey}
items={items}
tabBarExtraContent={tabBarExtraContent}
onTabClick={handleTabSwitch}
/>
Expand Down
16 changes: 16 additions & 0 deletions webui/react/src/components/Searches/Searches.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.content {
height: 100%;
overflow: hidden;

.paneWrapper {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
height: 100%;
width: 100%;

& > *:first-child {
min-height: 0;
}
}
}
96 changes: 96 additions & 0 deletions webui/react/src/components/Searches/Searches.settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as t from 'io-ts';

import { INIT_FORMSET } from 'components/FilterForm/components/FilterFormStore';
import { ioRowHeight, ioTableViewMode, RowHeight, TableViewMode } from 'components/OptionsMenu';
import { SettingsConfig } from 'hooks/useSettings';

import { defaultColumnWidths, defaultExperimentColumns } from './columns';

const SelectAllType = t.type({
exclusions: t.array(t.number),
type: t.literal('ALL_EXCEPT'),
});

const RegularSelectionType = t.type({
selections: t.array(t.number),
type: t.literal('ONLY_IN'),
});

export const SelectionType = t.union([RegularSelectionType, SelectAllType]);
export type SelectionType = t.TypeOf<typeof SelectionType>;
export const DEFAULT_SELECTION: t.TypeOf<typeof RegularSelectionType> = {
selections: [],
type: 'ONLY_IN',
};

// have to intersect with an empty object bc of settings store type issue
export const ProjectSettings = t.intersection([
t.type({}),
t.partial({
columns: t.array(t.string),
columnWidths: t.record(t.string, t.number),
compare: t.boolean,
filterset: t.string, // save FilterFormSet as string
heatmapOn: t.boolean,
heatmapSkipped: t.array(t.string),
pageLimit: t.number,
pinnedColumnsCount: t.number,
selection: SelectionType,
sortString: t.string,
}),
]);
export type ProjectSettings = t.TypeOf<typeof ProjectSettings>;

export const ProjectUrlSettings = t.partial({
compare: t.boolean,
page: t.number,
});

export const settingsPathForProject = (id: number): string => `searchesForProject${id}`;
export const defaultProjectSettings: Required<ProjectSettings> = {
columns: defaultExperimentColumns,
columnWidths: defaultColumnWidths,
compare: false,
filterset: JSON.stringify(INIT_FORMSET),
heatmapOn: false,
heatmapSkipped: [],
pageLimit: 20,
pinnedColumnsCount: 3,
selection: DEFAULT_SELECTION,
sortString: 'id=desc',
};

export interface SearchesGlobalSettings {
rowHeight: RowHeight;
tableViewMode: TableViewMode;
}

export const experimentListGlobalSettingsConfig = t.partial({
rowHeight: ioRowHeight,
tableViewMode: ioTableViewMode,
});

export const experimentListGlobalSettingsDefaults = {
rowHeight: RowHeight.MEDIUM,
tableViewMode: 'scroll',
} as const;

export const experimentListGlobalSettingsPath = 'globalTableSettings';

export const settingsConfigGlobal: SettingsConfig<SearchesGlobalSettings> = {
settings: {
rowHeight: {
defaultValue: RowHeight.MEDIUM,
skipUrlEncoding: true,
storageKey: 'rowHeight',
type: ioRowHeight,
},
tableViewMode: {
defaultValue: 'scroll',
skipUrlEncoding: true,
storageKey: 'tableViewMode',
type: ioTableViewMode,
},
},
storagePath: experimentListGlobalSettingsPath,
};
Loading

0 comments on commit bdab9e4

Please sign in to comment.