Skip to content

Commit

Permalink
feat: add metadata column type badge
Browse files Browse the repository at this point in the history
  • Loading branch information
thiagodallacqua-hpe committed Oct 31, 2024
1 parent 06b8f48 commit 29d837c
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 83 deletions.
70 changes: 51 additions & 19 deletions webui/react/src/components/ColumnPickerMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Badge from 'hew/Badge';
import Button from 'hew/Button';
import Checkbox, { CheckboxChangeEvent } from 'hew/Checkbox';
import Dropdown from 'hew/Dropdown';
Expand All @@ -10,9 +11,10 @@ import { Loadable } from 'hew/utils/loadable';
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react';
import { FixedSizeList as List } from 'react-window';

import { V1LocationType } from 'services/api-ts-sdk';
import { V1ColumnType, V1LocationType } from 'services/api-ts-sdk';
import { ProjectColumn } from 'types';
import { ensureArray } from 'utils/data';
import { formatColumnKey, removeColumnTypePrefix } from 'utils/flatRun';

import css from './ColumnPickerMenu.module.scss';

Expand Down Expand Up @@ -63,6 +65,8 @@ interface ColumnTabProps {
onHeatmapSelectionRemove?: (id: string) => void;
}

const KNOWN_BOOLEAN_COLUMNS = ['archived', 'isExpMultitrial', 'parentArchived'];

const ColumnPickerTab: React.FC<ColumnTabProps> = ({
columnState,
compare,
Expand All @@ -75,7 +79,7 @@ const ColumnPickerTab: React.FC<ColumnTabProps> = ({
onVisibleColumnChange,
onHeatmapSelectionRemove,
}) => {
const checkedColumns = useMemo(
const checkedColumnNames = useMemo(
() => (compare ? new Set(columnState.slice(0, pinnedColumnsCount)) : new Set(columnState)),
[columnState, compare, pinnedColumnsCount],
);
Expand All @@ -95,18 +99,21 @@ const ColumnPickerTab: React.FC<ColumnTabProps> = ({
}, [searchString, totalColumns, tab]);

const allFilteredColumnsChecked = useMemo(() => {
return filteredColumns.every((col) => columnState.includes(col.column));
return filteredColumns.every((col) => columnState.includes(formatColumnKey(col)));
}, [columnState, filteredColumns]);

const handleShowHideAll = useCallback(() => {
const filteredColumnMap: Record<string, boolean> = filteredColumns.reduce(
(acc, col) => ({ ...acc, [col.column]: columnState.includes(col.column) }),
(acc, col) => ({
...acc,
[formatColumnKey(col)]: columnState.includes(formatColumnKey(col)),
}),
{},
);

const newColumns = allFilteredColumnsChecked
? columnState.filter((col) => !filteredColumnMap[col])
: [...new Set([...columnState, ...filteredColumns.map((col) => col.column)])];
: [...new Set([...columnState, ...filteredColumns.map((col) => formatColumnKey(col))])];
const pinnedCount = allFilteredColumnsChecked
? // If uncheck something pinned, reduce the pinnedColumnsCount
newColumns.filter((col) => columnState.indexOf(col) < pinnedColumnsCount).length
Expand All @@ -123,32 +130,34 @@ const ColumnPickerTab: React.FC<ColumnTabProps> = ({

const handleColumnChange = useCallback(
(event: CheckboxChangeEvent) => {
const { id, checked } = event.target;
if (id === undefined) return;
const { id: targetCol, checked } = event.target;

if (targetCol === undefined) return;

if (compare) {
// pin or unpin column
const newColumns = columnState.filter((c) => c !== id);
const newColumns = columnState.filter((c) => c !== targetCol);
let pinnedCount = pinnedColumnsCount;
if (checked) {
newColumns.splice(pinnedColumnsCount, 0, id);
newColumns.splice(pinnedColumnsCount, 0, targetCol);
pinnedCount = Math.max(pinnedColumnsCount + 1, 0);
} else {
newColumns.splice(pinnedColumnsCount - 1, 0, id);
newColumns.splice(pinnedColumnsCount - 1, 0, targetCol);
pinnedCount = Math.max(pinnedColumnsCount - 1, 0);
}
onVisibleColumnChange?.(newColumns, pinnedCount);
} else {
let pinnedCount = pinnedColumnsCount;
// If uncheck something pinned, reduce the pinnedColumnsCount
if (!checked && columnState.indexOf(id) < pinnedColumnsCount) {
if (!checked && columnState.indexOf(targetCol) < pinnedColumnsCount) {
pinnedCount = Math.max(pinnedColumnsCount - 1, 0);
}
// If uncheck something had heatmap skipped, reset to heatmap visible
if (!checked) {
onHeatmapSelectionRemove?.(id);
onHeatmapSelectionRemove?.(targetCol);
}
const newColumnSet = new Set(columnState);
checked ? newColumnSet.add(id) : newColumnSet.delete(id);
checked ? newColumnSet.add(targetCol) : newColumnSet.delete(targetCol);
onVisibleColumnChange?.([...newColumnSet], pinnedCount);
}
},
Expand All @@ -165,24 +174,47 @@ const ColumnPickerTab: React.FC<ColumnTabProps> = ({
const rows = useCallback(
({ index, style }: { index: number; style: React.CSSProperties }) => {
const col = filteredColumns[index];
const colType =
KNOWN_BOOLEAN_COLUMNS.includes(col.column) && col.type === V1ColumnType.UNSPECIFIED
? 'BOOLEAN'
: removeColumnTypePrefix(col.type);
const getColDisplayName = (col: ProjectColumn) => {
return (
<>
{col.displayName || col.column} <Badge text={colType} />
</>
);
};
const getId = () => {
if (col.location === V1LocationType.RUNMETADATA) return formatColumnKey(col);

return col.column;
};
const getChecked = () => {
if (col.location === V1LocationType.RUNMETADATA)
return checkedColumnNames.has(formatColumnKey(col));

return checkedColumnNames.has(col.column);
};

return (
<div
className={css.rows}
data-test="row"
data-test-id={col.column}
key={col.column}
data-test-id={getId()}
key={getId()}
style={style}>
<Checkbox
checked={checkedColumns.has(col.column)}
checked={getChecked()}
data-test="checkbox"
id={col.column}
id={getId()}
onChange={handleColumnChange}>
{col.displayName || col.column}
{getColDisplayName(col)}
</Checkbox>
</div>
);
},
[filteredColumns, checkedColumns, handleColumnChange],
[filteredColumns, checkedColumnNames, handleColumnChange],
);

return (
Expand Down
3 changes: 2 additions & 1 deletion webui/react/src/components/FilterForm/TableFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FilterForm from 'components/FilterForm/components/FilterForm';
import { FilterFormStore } from 'components/FilterForm/components/FilterFormStore';
import { FormKind } from 'components/FilterForm/components/type';
import { V1ProjectColumn } from 'services/api-ts-sdk';
import { formatColumnKey } from 'utils/flatRun';

interface Props {
loadableColumns: Loadable<V1ProjectColumn[]>;
Expand All @@ -32,7 +33,7 @@ const TableFilter = ({
onIsOpenFilterChange,
}: Props): JSX.Element => {
const columns: V1ProjectColumn[] = Loadable.getOrElse([], loadableColumns).filter(
(column) => !bannedFilterColumns?.has(column.column),
(column) => !bannedFilterColumns?.has(formatColumnKey(column)),
);
const fieldCount = useObservable(formStore.fieldCount);
const formset = useObservable(formStore.formset);
Expand Down
38 changes: 26 additions & 12 deletions webui/react/src/components/FilterForm/components/FilterField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import dayjs from 'dayjs';
import Badge from 'hew/Badge';
import Button from 'hew/Button';
import DatePicker, { DatePickerProps } from 'hew/DatePicker';
import Icon from 'hew/Icon';
Expand Down Expand Up @@ -32,6 +33,7 @@ import { getMetadataValues } from 'services/api';
import { V1ColumnType, V1LocationType, V1ProjectColumn } from 'services/api-ts-sdk';
import clusterStore from 'stores/cluster';
import userStore from 'stores/users';
import { formatColumnKey, METADATA_SEPARATOR, removeColumnTypePrefix } from 'utils/flatRun';
import { alphaNumericSorter } from 'utils/sort';

import css from './FilterField.module.scss';
Expand Down Expand Up @@ -69,7 +71,10 @@ const FilterField = ({
}: Props): JSX.Element => {
const users = Loadable.getOrElse([], useObservable(userStore.getUsers()));
const resourcePools = Loadable.getOrElse([], useObservable(clusterStore.resourcePools));
const currentColumn = columns.find((c) => c.column === field.columnName);
const currentColumn = useMemo(
() => columns.find((c) => c.type === field.type && c.column === field.columnName),
[columns, field.columnName, field.type],
);

const columnType = useMemo(() => {
if (field.location === V1LocationType.RUNMETADATA && field.type === V1ColumnType.TEXT) {
Expand All @@ -96,19 +101,18 @@ const FilterField = ({
};

const onChangeColumnName = (value: SelectValue) => {
const prevType = currentColumn?.type;
const newColName = value?.toString() ?? '';
const newCol = columns.find((c) => c.column === newColName);
const prevType = field.type;
const [type, newColName] = (value?.toString() ?? '').split(METADATA_SEPARATOR, 2);
const newCol = columns.find((c) => c.column === newColName && type === c.type);
if (newCol) {
Observable.batch(() => {
formStore.setFieldColumnName(field.id, newCol);

if ((SpecialColumnNames as ReadonlyArray<string>).includes(newColName)) {
formStore.setFieldOperator(field.id, Operator.Eq);
updateFieldValue(field.id, null);
} else if (prevType !== newCol?.type) {
} else if (prevType !== newCol.type) {
const defaultOperator: Operator =
AvailableOperators[newCol?.type ?? V1ColumnType.UNSPECIFIED][0];
AvailableOperators[newCol.type ?? V1ColumnType.UNSPECIFIED][0];
formStore.setFieldOperator(field.id, defaultOperator);
updateFieldValue(field.id, null);
}
Expand Down Expand Up @@ -218,6 +222,16 @@ const FilterField = ({
[columnType, field.type, formStore, index, inputOpen, parentId],
);

const getColDisplayName = (col: V1ProjectColumn) => {
const colType = removeColumnTypePrefix(col.type);

return (
<>
{col.displayName || col.column} <Badge text={colType} />
</>
);
};

return (
<div className={css.base} data-test-component="FilterField" ref={(node) => drop(node)}>
<ConjunctionContainer
Expand All @@ -232,12 +246,12 @@ const FilterField = ({
autoFocus
data-test="columnName"
dropdownMatchSelectWidth={300}
options={columns.map((col, idx) => ({
key: `${col.column} ${idx}`,
label: col.displayName || col.column,
value: col.column,
options={columns.map((col) => ({
key: formatColumnKey(col, true),
label: getColDisplayName(col),
value: formatColumnKey(col, true),
}))}
value={field.columnName}
value={`${field.type}${METADATA_SEPARATOR}${field.columnName}`}
width={'100%'}
onChange={onChangeColumnName}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ export class FilterFormStore {
col: Pick<V1ProjectColumn, 'location' | 'type' | 'column'>,
): void {
return this.#updateField(id, (form) => {
if (form.columnName === col.column && form.location === col.location) {
const isSameColumn = form.columnName === col.column && form.type === col.type;
if (isSameColumn && form.location === col.location) {
return form;
}
return {
Expand Down
Loading

0 comments on commit 29d837c

Please sign in to comment.