Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,93 @@
padding: 16px 30px 16px;
width: 35vw;
font-style: italic;
}
}

/* ---- Nested filter UI ---- */

.nested-section {
background-color: var(--primary-background-color);
padding: 8px 30px 16px;
width: 35vw;
}

.nested-mode-bar {
display: flex;
gap: 8px;
margin-bottom: 12px;
}

.nested-mode-btn {
flex: 1;
padding: 4px 8px;
font-size: 0.8rem;
cursor: pointer;
border: 1px solid var(--border-color, #ccc);
border-radius: 3px;
background: var(--secondary-background-color, #f4f4f4);
color: var(--primary-text-color);
}

.nested-mode-btn.active {
background: var(--accent-color, #0078d4);
color: #fff;
border-color: var(--accent-color, #0078d4);
}

.nested-condition-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}

.nested-path-input,
.nested-value-input {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--border-color, #ccc);
border-radius: 3px;
background: var(--input-background-color, #fff);
color: var(--primary-text-color);
font-size: 0.85rem;
}

.nested-remove-btn {
background: none;
border: none;
cursor: pointer;
color: var(--error-color, #a80000);
font-size: 1rem;
padding: 2px 4px;
}

.nested-add-btn {
margin-top: 4px;
font-size: 0.8rem;
cursor: pointer;
background: none;
border: 1px dashed var(--border-color, #ccc);
border-radius: 3px;
padding: 4px 10px;
color: var(--secondary-text-color, #555);
width: 100%;
}

.nested-apply-btn {
margin-top: 8px;
width: 100%;
padding: 5px;
cursor: pointer;
background: var(--accent-color, #0078d4);
color: #fff;
border: none;
border-radius: 3px;
font-size: 0.85rem;
}

.nested-note {
font-size: 0.75rem;
color: var(--secondary-text-color, #666);
font-style: italic;
margin-bottom: 8px;
}
63 changes: 38 additions & 25 deletions packages/core/components/AnnotationFilterForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { interaction, selection } from "../../state";

import styles from "./AnnotationFilterForm.module.css";


interface AnnotationFilterFormProps {
annotation: Annotation;
}
Expand All @@ -37,18 +38,24 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
const canFuzzySearch = useSelector(selection.selectors.isQueryingAicsFms);
const annotationService = useSelector(interaction.selectors.getAnnotationService);
const [annotationValues, isLoading, errorMessage] = useAnnotationValues(
props.annotation.name,
// Use full dotted path (e.g. "Well.Column") so DatabaseAnnotationService can
// resolve the correct sub-field annotation. For flat annotations path.join(".")
// equals name, so this is backward-compatible.
props.annotation.path.join("."),
annotationService
);

// FileFilter.name is path.join("."), so we need the full dotted path for comparison
const annotationFilterKey = props.annotation.path.join(".");

const fuzzySearchEnabled = React.useMemo(
() => !!fuzzyFilters?.some((filter) => filter.name === props.annotation.name),
[fuzzyFilters, props.annotation]
() => !!fuzzyFilters?.some((filter) => filter.name === annotationFilterKey),
[fuzzyFilters, annotationFilterKey]
);

const filtersForAnnotation = React.useMemo(
() => allFilters.filter((filter) => filter.name === props.annotation.name),
[allFilters, props.annotation]
() => allFilters.filter((filter) => filter.name === annotationFilterKey),
[allFilters, annotationFilterKey]
);

// Assume all filters use same type
Expand Down Expand Up @@ -80,24 +87,28 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
};

const onSelect = (item: ListItem) => {
dispatch(selection.actions.changeFileFilterType(props.annotation.name, FilterType.DEFAULT));
dispatch(selection.actions.changeFileFilterType(props.annotation.path, FilterType.DEFAULT));
dispatch(selection.actions.addFileFilter(createFileFilter(item)));
};

// TODO: Should this select ALL or just the visible items in list?
const onSelectAll = () => {
dispatch(selection.actions.changeFileFilterType(props.annotation.name, FilterType.DEFAULT));
dispatch(selection.actions.changeFileFilterType(props.annotation.path, FilterType.DEFAULT));
dispatch(selection.actions.addFileFilter(items.map((item) => createFileFilter(item))));
};

const createFileFilter = (item: ListItem) => {
const formattedValue = props.annotation.formatter.valueOf(item.value);
const value = isNil(formattedValue)
? item.value
: formattedValue;

return new FileFilter(
props.annotation.name,
isNil(props.annotation.valueOf(item.value))
? item.value
: props.annotation.valueOf(item.value),
props.annotation.path,
value,
filterType,
props.annotation.type as AnnotationType
props.annotation.type,
props.annotation.pathIsArray
);
};

Expand All @@ -116,7 +127,7 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
default:
dispatch(
selection.actions.changeFileFilterType(
props.annotation.name,
props.annotation.path,
option.key as FilterType
)
);
Expand All @@ -128,12 +139,13 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
if (filterValue && filterValue.trim()) {
dispatch(
selection.actions.setFileFilters([
...allFilters.filter((filter) => filter.name !== props.annotation.name),
...allFilters.filter((filter) => filter.name !== annotationFilterKey),
new FileFilter(
props.annotation.name,
props.annotation.path,
filterValue,
type,
props.annotation.type as AnnotationType
props.annotation.type,
props.annotation.pathIsArray
),
])
);
Expand Down Expand Up @@ -164,7 +176,7 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
const typeHasDedicatedPicker =
props.annotation.name !== AnnotationName.FILE_SIZE &&
[AnnotationType.NUMBER, AnnotationType.DATE, AnnotationType.DATETIME].includes(
props.annotation.type as AnnotationType
props.annotation.type
);

const searchFormType = () => {
Expand Down Expand Up @@ -254,14 +266,15 @@ export default function AnnotationFilterForm(props: AnnotationFilterFormProps) {
onChange={(_, option) => onFilterTypeOptionChange(option)}
/>
</div>
{filterType === FilterType.DEFAULT || filterType === FilterType.FUZZY ? (
searchFormType()
) : (
<div className={styles.footer}>
All files with {filterType === FilterType.EXCLUDE ? "no " : "any "}
value for {props.annotation.displayName}
</div>
)}
{(filterType === FilterType.DEFAULT || filterType === FilterType.FUZZY)
? searchFormType()
: (
<div className={styles.footer}>
All files with {filterType === FilterType.EXCLUDE ? "no " : "any "}
value for {props.annotation.displayName}
</div>
)
}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("<AnnotationFilterForm />", () => {
// setup
const fooAnnotation = new Annotation({
annotationDisplayName: "Foo",
annotationName: "foo",
path: ["foo"],
description: "",
type: AnnotationType.STRING,
});
Expand Down Expand Up @@ -168,7 +168,7 @@ describe("<AnnotationFilterForm />", () => {
// setup
const fooAnnotation = new Annotation({
annotationDisplayName: "Foo",
annotationName: "foo",
path: ["foo"],
description: "",
type: AnnotationType.BOOLEAN,
});
Expand Down Expand Up @@ -242,21 +242,21 @@ describe("<AnnotationFilterForm />", () => {
</Provider>
);
await waitFor(
() => expect(getByTestId(`${LISTROW_TESTID_PREFIX}False`)).to.not.be.undefined
() => expect(getByTestId(`${LISTROW_TESTID_PREFIX}false`)).to.not.be.undefined
);

// (sanity-check): Check that the "False" input is selected
expect(selection.selectors.getFileFilters(store.getState())).to.be.lengthOf(1);

// Act: Deselect the "False" input
fireEvent.click(getByTestId(`${LISTROW_TESTID_PREFIX}False`));
fireEvent.click(getByTestId(`${LISTROW_TESTID_PREFIX}false`));
await logicMiddleware.whenComplete();

// Assert: Check that the "False" input is deselected
expect(selection.selectors.getFileFilters(store.getState())).to.be.lengthOf(0);

// Act: Reselect the "False" input
fireEvent.click(getByTestId(`${LISTROW_TESTID_PREFIX}False`));
fireEvent.click(getByTestId(`${LISTROW_TESTID_PREFIX}false`));
await logicMiddleware.whenComplete();

// Assert: Check that the "False" input is selected again
Expand All @@ -267,7 +267,7 @@ describe("<AnnotationFilterForm />", () => {
describe("Number annotation", () => {
const fooAnnotation = new Annotation({
annotationDisplayName: "Foo",
annotationName: "foo",
path: ["foo"],
description: "",
type: AnnotationType.NUMBER,
});
Expand Down Expand Up @@ -316,7 +316,7 @@ describe("<AnnotationFilterForm />", () => {
describe("Duration annotation", () => {
const fooAnnotation = new Annotation({
annotationDisplayName: "Foo",
annotationName: "foo",
path: ["foo"],
description: "",
type: AnnotationType.DURATION,
});
Expand Down
Loading