diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py index c2eba6949e0..bd8d5c35fec 100644 --- a/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py +++ b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer.py @@ -8,31 +8,22 @@ import abc import logging import operator -from typing import ( - TYPE_CHECKING, - Callable, - Dict, - List, - Optional, - Sequence, - Set, - Tuple, -) +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Sequence, Set, Tuple import comm from .access_keys import decode_access_key from .data_explorer_comm import ( BackendState, + ColumnDisplayType, ColumnFrequencyTable, ColumnHistogram, - ColumnSummaryStats, - CompareFilterParamsOp, - ColumnProfileType, ColumnProfileResult, + ColumnProfileType, ColumnSchema, - ColumnDisplayType, ColumnSortKey, + ColumnSummaryStats, + CompareFilterParamsOp, DataExplorerBackendMessageContent, DataExplorerFrontendEvent, FilterResult, @@ -63,7 +54,6 @@ from .third_party import pd_ from .utils import guid - if TYPE_CHECKING: import pandas as pd @@ -753,6 +743,11 @@ def _is_supported_filter(self, filt: RowFilter) -> bool: RowFilterType.NotBetween, ]: return display_type in _FILTER_RANGE_COMPARE_SUPPORTED + elif filt.filter_type in [ + RowFilterType.IsTrue, + RowFilterType.IsFalse, + ]: + return display_type == ColumnDisplayType.Boolean else: # Filters always supported assert filt.filter_type in [ @@ -797,6 +792,10 @@ def _eval_filter(self, filt: RowFilter): mask = col.str.len() != 0 elif filt.filter_type == RowFilterType.NotNull: mask = col.notnull() + elif filt.filter_type == RowFilterType.IsTrue: + mask = col == True # noqa: E712 + elif filt.filter_type == RowFilterType.IsFalse: + mask = col == False # noqa: E712 elif filt.filter_type == RowFilterType.SetMembership: params = filt.set_membership_params assert params is not None @@ -955,10 +954,12 @@ def _prof_histogram(self, column_index: int): RowFilterType.Between, RowFilterType.Compare, RowFilterType.IsEmpty, - RowFilterType.NotEmpty, + RowFilterType.IsFalse, RowFilterType.IsNull, - RowFilterType.NotNull, + RowFilterType.IsTrue, RowFilterType.NotBetween, + RowFilterType.NotEmpty, + RowFilterType.NotNull, RowFilterType.Search, RowFilterType.SetMembership, } diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py index 1fda36b7b30..ba6f788398d 100644 --- a/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py +++ b/extensions/positron-python/python_files/positron/positron_ipykernel/data_explorer_comm.py @@ -65,8 +65,12 @@ class RowFilterType(str, enum.Enum): IsEmpty = "is_empty" + IsFalse = "is_false" + IsNull = "is_null" + IsTrue = "is_true" + NotBetween = "not_between" NotEmpty = "not_empty" diff --git a/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py index dc9006d830d..0573ae3860b 100644 --- a/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py +++ b/extensions/positron-python/python_files/positron/positron_ipykernel/tests/test_data_explorer.py @@ -13,15 +13,15 @@ from ..data_explorer import COMPARE_OPS, DataExplorerService from ..data_explorer_comm import ( ColumnDisplayType, - RowFilter, ColumnProfileResult, ColumnSchema, ColumnSortKey, FilterResult, + RowFilter, ) +from ..utils import guid from .conftest import DummyComm, PositronShell from .test_variables import BIG_ARRAY_LENGTH -from ..utils import guid from .utils import json_rpc_notification, json_rpc_request, json_rpc_response TARGET_NAME = "positron.dataExplorer" @@ -459,13 +459,15 @@ def test_pandas_supported_features(dxf: DataExplorerFixture): assert row_filters["supported"] assert row_filters["supports_conditions"] assert set(row_filters["supported_types"]) == { + "between", + "compare", "is_empty", + "is_false", "is_null", + "is_true", + "not_between", "not_empty", "not_null", - "between", - "compare", - "not_between", "search", "set_membership", } @@ -850,6 +852,19 @@ def test_pandas_filter_empty(dxf: DataExplorerFixture): dxf.check_filter_case(df, [_filter("not_empty", schema[1])], df[df["b"].str.len() != 0]) +def test_pandas_filter_boolean(dxf: DataExplorerFixture): + df = pd.DataFrame( + { + "a": [True, True, None, False, False, False, True, True], + } + ) + + schema = dxf.get_schema_for(df)["columns"] + + dxf.check_filter_case(df, [_filter("is_true", schema[0])], df[df["a"] == True]) # noqa: E712 + dxf.check_filter_case(df, [_filter("is_false", schema[0])], df[df["a"] == False]) # noqa: E712 + + def test_pandas_filter_is_null_not_null(dxf: DataExplorerFixture): df = SIMPLE_PANDAS_DF schema = dxf.get_schema_for(df)["columns"] diff --git a/positron/comms/data_explorer-backend-openrpc.json b/positron/comms/data_explorer-backend-openrpc.json index bedca032bf0..27b4e2b2bee 100644 --- a/positron/comms/data_explorer-backend-openrpc.json +++ b/positron/comms/data_explorer-backend-openrpc.json @@ -461,7 +461,9 @@ "between", "compare", "is_empty", + "is_false", "is_null", + "is_true", "not_between", "not_empty", "not_null", diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx index 8a88cb76fbb..195037bd69e 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup.tsx @@ -34,6 +34,8 @@ import { RowFilterDescriptorIsNotNull, RowFilterDescriptorIsNull, RowFilterDescriptorSearch, + RowFilterDescriptorIsTrue, + RowFilterDescriptorIsFalse, } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; /** @@ -94,6 +96,8 @@ const filterNumParams = (filterType: RowFilterDescrType | undefined) => { case RowFilterDescrType.IS_NOT_EMPTY: case RowFilterDescrType.IS_NULL: case RowFilterDescrType.IS_NOT_NULL: + case RowFilterDescrType.IS_TRUE: + case RowFilterDescrType.IS_FALSE: return 0; case RowFilterDescrType.IS_EQUAL_TO: case RowFilterDescrType.IS_NOT_EQUAL_TO: @@ -310,7 +314,6 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp // Add is equal to, is not equal to conditions. switch (selectedColumnSchema.type_display) { case ColumnDisplayType.Number: - case ColumnDisplayType.Boolean: case ColumnDisplayType.String: case ColumnDisplayType.Date: case ColumnDisplayType.Datetime: @@ -334,6 +337,27 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Add is true, is false conditions. + switch (selectedColumnSchema.type_display) { + case ColumnDisplayType.Boolean: + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterDescrType.IS_TRUE, + title: localize( + 'positron.addEditRowFilter.conditionIsTrue', + "is true" + ), + value: RowFilterDescrType.IS_TRUE + })); + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterDescrType.IS_FALSE, + title: localize( + 'positron.addEditRowFilter.conditionIsFalse', + "is false" + ), + value: RowFilterDescrType.IS_FALSE + })); + } + // Add is between / is not between conditions. switch (selectedColumnSchema.type_display) { case ColumnDisplayType.Number: @@ -608,6 +632,16 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply boolean filters. + case RowFilterDescrType.IS_TRUE: { + applyRowFilter(new RowFilterDescriptorIsTrue({ columnSchema: selectedColumnSchema })); + break; + } + case RowFilterDescrType.IS_FALSE: { + applyRowFilter(new RowFilterDescriptorIsFalse({ columnSchema: selectedColumnSchema })); + break; + } + // Apply comparison row filter. case RowFilterDescrType.SEARCH_CONTAINS: case RowFilterDescrType.SEARCH_STARTS_WITH: diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts index b40a5734eec..b598893666f 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor.ts @@ -21,6 +21,8 @@ export enum RowFilterDescrType { IS_NOT_EMPTY = 'is-not-empty', IS_NULL = 'is-null', IS_NOT_NULL = 'is-not-null', + IS_TRUE = 'is-true', + IS_FALSE = 'is-false', // Filters with one parameter. IS_LESS_THAN = 'is-less-than', @@ -172,6 +174,66 @@ export class RowFilterDescriptorIsNull extends BaseRowFilterDescriptor { } } +/** + * RowFilterDescriptorIsTrue class. + */ +export class RowFilterDescriptorIsTrue extends BaseRowFilterDescriptor { + /** + * Constructor. + * @param props The common row filter descriptor properties. + */ + constructor(props: RowFilterCommonProps) { + super(props); + } + + /** + * Gets the row filter condition. + */ + get descrType() { + return RowFilterDescrType.IS_TRUE; + } + + /** + * Get the backend OpenRPC type. + */ + get backendFilter() { + return { + filter_type: RowFilterType.IsTrue, + ...this._sharedBackendParams() + }; + } +} + +/** + * RowFilterDescriptorIsFalse class. + */ +export class RowFilterDescriptorIsFalse extends BaseRowFilterDescriptor { + /** + * Constructor. + * @param props The common row filter descriptor properties. + */ + constructor(props: RowFilterCommonProps) { + super(props); + } + + /** + * Gets the row filter condition. + */ + get descrType() { + return RowFilterDescrType.IS_FALSE; + } + + /** + * Get the backend OpenRPC type. + */ + get backendFilter() { + return { + filter_type: RowFilterType.IsFalse, + ...this._sharedBackendParams() + }; + } +} + /** * RowFilterDescriptorIsNotEmpty class. */ @@ -525,7 +587,7 @@ function getSearchDescrType(searchType: SearchFilterType) { } } -export function getRowFilterDescriptor(backendFilter: RowFilter) { +export function getRowFilterDescriptor(backendFilter: RowFilter): RowFilterDescriptor { const commonProps = { columnSchema: backendFilter.column_schema, isValid: backendFilter.is_valid, @@ -558,6 +620,10 @@ export function getRowFilterDescriptor(backendFilter: RowFilter) { return new RowFilterDescriptorIsNull(commonProps); case RowFilterType.NotNull: return new RowFilterDescriptorIsNotNull(commonProps); + case RowFilterType.IsTrue: + return new RowFilterDescriptorIsTrue(commonProps); + case RowFilterType.IsFalse: + return new RowFilterDescriptorIsFalse(commonProps); case RowFilterType.Search: { const params = backendFilter.search_params!; return new RowFilterDescriptorSearch(commonProps, @@ -581,6 +647,8 @@ export type RowFilterDescriptor = RowFilterDescriptorIsNotEmpty | RowFilterDescriptorIsNull | RowFilterDescriptorIsNotNull | + RowFilterDescriptorIsTrue | + RowFilterDescriptorIsFalse | RowFilterDescriptorIsBetween | RowFilterDescriptorIsNotBetween | RowFilterDescriptorSearch; diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx index 7f0ac7a89f6..e1b63f5a316 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget.tsx @@ -17,10 +17,12 @@ import { RowFilterDescriptorComparison, RowFilterDescriptorIsBetween, RowFilterDescriptorIsEmpty, + RowFilterDescriptorIsFalse, RowFilterDescriptorIsNotBetween, RowFilterDescriptorIsNotEmpty, RowFilterDescriptorIsNotNull, RowFilterDescriptorIsNull, + RowFilterDescriptorIsTrue, RowFilterDescriptorSearch } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; @@ -70,7 +72,20 @@ export const RowFilterWidget = forwardRef ; - + } else if (props.rowFilter instanceof RowFilterDescriptorIsTrue) { + return <> + {props.rowFilter.schema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isTrue', "is true")} + + ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsFalse) { + return <> + {props.rowFilter.schema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isFalse', "is false")} + + ; } else if (props.rowFilter instanceof RowFilterDescriptorComparison) { return <> {props.rowFilter.schema.column_name} diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts index 245fc8d4fb5..13de404f2da 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts @@ -620,7 +620,9 @@ export enum RowFilterType { Between = 'between', Compare = 'compare', IsEmpty = 'is_empty', + IsFalse = 'is_false', IsNull = 'is_null', + IsTrue = 'is_true', NotBetween = 'not_between', NotEmpty = 'not_empty', NotNull = 'not_null',