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 f9d6cf19dcb..c88c33e724c 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 @@ -24,6 +24,7 @@ from .access_keys import decode_access_key from .data_explorer_comm import ( + BackendState, ColumnFrequencyTable, ColumnHistogram, ColumnSummaryStats, @@ -41,8 +42,8 @@ GetDataValuesRequest, GetSchemaRequest, GetStateRequest, - GetSupportedFeaturesRequest, RowFilter, + RowFilterCondition, RowFilterType, SchemaUpdateParams, SearchFilterType, @@ -59,7 +60,6 @@ TableData, TableSchema, TableShape, - TableState, ) from .positron_comm import CommMessage, PositronComm from .third_party import pd_ @@ -166,9 +166,6 @@ def get_column_profiles(self, request: GetColumnProfilesRequest): def get_state(self, request: GetStateRequest): return self._get_state().dict() - def get_supported_features(self, request: GetSupportedFeaturesRequest): - return self._get_supported_features().dict() - @abc.abstractmethod def invalidate_computations(self): pass @@ -225,11 +222,7 @@ def _prof_histogram(self, column_index: int) -> ColumnHistogram: pass @abc.abstractmethod - def _get_state(self) -> TableState: - pass - - @abc.abstractmethod - def _get_supported_features(self) -> SupportedFeatures: + def _get_state(self) -> BackendState: pass @@ -482,7 +475,7 @@ def _update_view_indices(self): # reflect the filtered_indices that have just been updated self._sort_data() - def _set_row_filters(self, filters) -> FilterResult: + def _set_row_filters(self, filters: List[RowFilter]) -> FilterResult: self.filters = filters if len(filters) == 0: @@ -491,20 +484,32 @@ def _set_row_filters(self, filters) -> FilterResult: self._update_view_indices() return FilterResult(selected_num_rows=len(self.table)) - # Evaluate all the filters and AND them together + # Evaluate all the filters and combine them using the + # indicated conditions combined_mask = None for filt in filters: + if filt.is_valid is False: + # If filter is invalid, do not evaluate it + continue + single_mask = self._eval_filter(filt) if combined_mask is None: combined_mask = single_mask - else: + elif filt.condition == RowFilterCondition.And: combined_mask &= single_mask + elif filt.condition == RowFilterCondition.Or: + combined_mask |= single_mask - self.filtered_indices = combined_mask.nonzero()[0] + if combined_mask is None: + self.filtered_indices = None + selected_num_rows = len(self.table) + else: + self.filtered_indices = combined_mask.nonzero()[0] + selected_num_rows = len(self.filtered_indices) # Update the view indices, re-sorting if needed self._update_view_indices() - return FilterResult(selected_num_rows=len(self.filtered_indices)) + return FilterResult(selected_num_rows=selected_num_rows) def _eval_filter(self, filt: RowFilter): col = self.table.iloc[:, filt.column_index] @@ -531,8 +536,12 @@ def _eval_filter(self, filt: RowFilter): op = COMPARE_OPS[params.op] # pandas comparison filters return False for null values mask = op(col, _coerce_value_param(params.value, col.dtype)) + elif filt.filter_type == RowFilterType.IsEmpty: + mask = col.str.len() == 0 elif filt.filter_type == RowFilterType.IsNull: mask = col.isnull() + elif filt.filter_type == RowFilterType.NotEmpty: + mask = col.str.len() != 0 elif filt.filter_type == RowFilterType.NotNull: mask = col.notnull() elif filt.filter_type == RowFilterType.SetMembership: @@ -687,45 +696,45 @@ def _prof_freq_table(self, column_index: int): def _prof_histogram(self, column_index: int): raise NotImplementedError - def _get_state(self) -> TableState: + _row_filter_features = SetRowFiltersFeatures( + supported=True, + supports_conditions=False, + supported_types=[ + RowFilterType.Between, + RowFilterType.Compare, + RowFilterType.IsNull, + RowFilterType.NotNull, + RowFilterType.NotBetween, + RowFilterType.Search, + RowFilterType.SetMembership, + ], + ) + + _column_profile_features = GetColumnProfilesFeatures( + supported=True, + supported_types=[ + ColumnProfileType.NullCount, + ColumnProfileType.SummaryStats, + ], + ) + + FEATURES = SupportedFeatures( + search_schema=SearchSchemaFeatures(supported=True), + set_row_filters=_row_filter_features, + get_column_profiles=_column_profile_features, + ) + + def _get_state(self) -> BackendState: if self.view_indices is not None: num_rows = len(self.view_indices) else: num_rows = self.table.shape[0] - return TableState( + return BackendState( table_shape=TableShape(num_rows=num_rows, num_columns=self.table.shape[1]), row_filters=self.filters, sort_keys=self.sort_keys, - ) - - def _get_supported_features(self) -> SupportedFeatures: - row_filter_features = SetRowFiltersFeatures( - supported=True, - supports_conditions=False, - supported_types=[ - RowFilterType.Between, - RowFilterType.Compare, - RowFilterType.IsNull, - RowFilterType.NotNull, - RowFilterType.NotBetween, - RowFilterType.Search, - RowFilterType.SetMembership, - ], - ) - - column_profile_features = GetColumnProfilesFeatures( - supported=True, - supported_types=[ - ColumnProfileType.NullCount, - ColumnProfileType.SummaryStats, - ], - ) - - return SupportedFeatures( - search_schema=SearchSchemaFeatures(supported=True), - set_row_filters=row_filter_features, - get_column_profiles=column_profile_features, + supported_features=self.FEATURES, ) @@ -1020,4 +1029,13 @@ def handle_msg(self, msg: CommMessage[DataExplorerBackendMessageContent], raw_ms table = self.table_views[comm_id] result = getattr(table, request.method.value)(request) + + # To help remember to convert pydantic types to dicts + if result is not None: + if isinstance(result, list): + for x in result: + assert isinstance(x, dict) + else: + assert isinstance(result, dict) + comm.send_result(result) 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 07c143074fa..fd06b9ad1d1 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 @@ -42,6 +42,17 @@ class ColumnDisplayType(str, enum.Enum): Unknown = "unknown" +@enum.unique +class RowFilterCondition(str, enum.Enum): + """ + Possible values for Condition in RowFilter + """ + + And = "and" + + Or = "or" + + @enum.unique class RowFilterType(str, enum.Enum): """ @@ -52,10 +63,14 @@ class RowFilterType(str, enum.Enum): Compare = "compare" + IsEmpty = "is_empty" + IsNull = "is_null" NotBetween = "not_between" + NotEmpty = "not_empty" + NotNull = "not_null" Search = "search" @@ -152,9 +167,9 @@ class FilterResult(BaseModel): ) -class TableState(BaseModel): +class BackendState(BaseModel): """ - The current backend table state + The current backend state for the data explorer """ table_shape: TableShape = Field( @@ -169,6 +184,10 @@ class TableState(BaseModel): description="The set of currently applied sorts", ) + supported_features: SupportedFeatures = Field( + description="The features currently supported by the backend instance", + ) + class TableShape(BaseModel): """ @@ -184,24 +203,6 @@ class TableShape(BaseModel): ) -class SupportedFeatures(BaseModel): - """ - For each field, returns flags indicating supported features - """ - - search_schema: SearchSchemaFeatures = Field( - description="Support for 'search_schema' RPC and its features", - ) - - set_row_filters: SetRowFiltersFeatures = Field( - description="Support for 'set_row_filters' RPC and its features", - ) - - get_column_profiles: GetColumnProfilesFeatures = Field( - description="Support for 'get_column_profiles' RPC and its features", - ) - - class ColumnSchema(BaseModel): """ Schema for a column in a table @@ -281,6 +282,15 @@ class RowFilter(BaseModel): description="Column index to apply filter to", ) + condition: RowFilterCondition = Field( + description="The binary condition to use to combine with preceding row filters", + ) + + is_valid: Optional[bool] = Field( + default=None, + description="Whether the filter is valid and supported by the backend, if undefined then true", + ) + between_params: Optional[BetweenFilterParams] = Field( default=None, description="Parameters for the 'between' and 'not_between' filter types", @@ -556,6 +566,24 @@ class ColumnSortKey(BaseModel): ) +class SupportedFeatures(BaseModel): + """ + For each field, returns flags indicating supported features + """ + + search_schema: SearchSchemaFeatures = Field( + description="Support for 'search_schema' RPC and its features", + ) + + set_row_filters: SetRowFiltersFeatures = Field( + description="Support for 'set_row_filters' RPC and its features", + ) + + get_column_profiles: GetColumnProfilesFeatures = Field( + description="Support for 'get_column_profiles' RPC and its features", + ) + + class SearchSchemaFeatures(BaseModel): """ Feature flags for 'search_schema' RPC @@ -625,9 +653,6 @@ class DataExplorerBackendRequest(str, enum.Enum): # Get the state GetState = "get_state" - # Query the backend to determine supported features - GetSupportedFeatures = "get_supported_features" - class GetSchemaParams(BaseModel): """ @@ -840,22 +865,6 @@ class GetStateRequest(BaseModel): ) -class GetSupportedFeaturesRequest(BaseModel): - """ - Query the backend to determine supported features, to enable feature - toggling - """ - - method: Literal[DataExplorerBackendRequest.GetSupportedFeatures] = Field( - description="The JSON-RPC method name (get_supported_features)", - ) - - jsonrpc: str = Field( - default="2.0", - description="The JSON-RPC version specifier", - ) - - class DataExplorerBackendMessageContent(BaseModel): comm_id: str data: Union[ @@ -866,7 +875,6 @@ class DataExplorerBackendMessageContent(BaseModel): SetSortColumnsRequest, GetColumnProfilesRequest, GetStateRequest, - GetSupportedFeaturesRequest, ] = Field(..., discriminator="method") @@ -899,12 +907,10 @@ class SchemaUpdateParams(BaseModel): FilterResult.update_forward_refs() -TableState.update_forward_refs() +BackendState.update_forward_refs() TableShape.update_forward_refs() -SupportedFeatures.update_forward_refs() - ColumnSchema.update_forward_refs() TableSchema.update_forward_refs() @@ -941,6 +947,8 @@ class SchemaUpdateParams(BaseModel): ColumnSortKey.update_forward_refs() +SupportedFeatures.update_forward_refs() + SearchSchemaFeatures.update_forward_refs() SetRowFiltersFeatures.update_forward_refs() @@ -973,6 +981,4 @@ class SchemaUpdateParams(BaseModel): GetStateRequest.update_forward_refs() -GetSupportedFeaturesRequest.update_forward_refs() - SchemaUpdateParams.update_forward_refs() 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 e429cb26566..76c7e271ded 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 @@ -373,9 +373,6 @@ def search_schema(self, table_name, search_term, start_index, max_results): def get_state(self, table_name): return self.do_json_rpc(table_name, "get_state") - def get_supported_features(self, table_name): - return self.do_json_rpc(table_name, "get_supported_features") - def get_data_values(self, table_name, **params): return self.do_json_rpc(table_name, "get_data_values", **params) @@ -460,9 +457,9 @@ def test_pandas_get_state(dxf: DataExplorerFixture): assert result["row_filters"] == [RowFilter(**f) for f in filters] -def test_pandas_get_supported_features(dxf: DataExplorerFixture): +def test_pandas_supported_features(dxf: DataExplorerFixture): dxf.register_table("example", SIMPLE_PANDAS_DF) - features = dxf.get_supported_features("example") + features = dxf.get_state("example")["supported_features"] search_schema = features["search_schema"] row_filters = features["set_row_filters"] @@ -672,37 +669,59 @@ def test_pandas_get_data_values(dxf: DataExplorerFixture): # ) -def _filter(filter_type, column_index, **kwargs): +def _filter(filter_type, column_index, condition="and", is_valid=None, **kwargs): kwargs.update( { "filter_id": guid(), "filter_type": filter_type, "column_index": column_index, + "condition": condition, + "is_valid": is_valid, } ) return kwargs -def _compare_filter(column_index, op, value): - return _filter("compare", column_index, compare_params={"op": op, "value": value}) +def _compare_filter(column_index, op, value, condition="and", is_valid=None): + return _filter( + "compare", + column_index, + condition=condition, + is_valid=is_valid, + compare_params={"op": op, "value": value}, + ) -def _between_filter(column_index, left_value, right_value, op="between"): +def _between_filter(column_index, left_value, right_value, op="between", condition="and"): return _filter( op, column_index, + condition=condition, between_params={"left_value": left_value, "right_value": right_value}, ) -def _not_between_filter(column_index, left_value, right_value): - return _between_filter(column_index, left_value, right_value, op="not_between") +def _not_between_filter(column_index, left_value, right_value, condition="and"): + return _between_filter( + column_index, + left_value, + right_value, + op="not_between", + condition=condition, + ) -def _search_filter(column_index, term, case_sensitive=False, search_type="contains"): +def _search_filter( + column_index, + term, + case_sensitive=False, + search_type="contains", + condition="and", +): return _filter( "search", column_index, + condition=condition, search_params={ "search_type": search_type, "term": term, @@ -711,10 +730,16 @@ def _search_filter(column_index, term, case_sensitive=False, search_type="contai ) -def _set_member_filter(column_index, values, inclusive=True): +def _set_member_filter( + column_index, + values, + inclusive=True, + condition="and", +): return _filter( "set_membership", column_index, + condition=condition, set_membership_params={"values": values, "inclusive": inclusive}, ) @@ -747,6 +772,26 @@ def test_pandas_filter_between(dxf: DataExplorerFixture): ) +def test_pandas_filter_conditions(dxf: DataExplorerFixture): + # Test AND/OR conditions when filtering + df = SIMPLE_PANDAS_DF + filters = [ + _compare_filter(0, ">=", 3, condition="or"), + _compare_filter(3, "<=", -4.5, condition="or"), + # Delbierately duplicated + _compare_filter(3, "<=", -4.5, condition="or"), + ] + + expected_df = df[(df["a"] >= 3) | (df["d"] <= -4.5)] + dxf.check_filter_case(df, filters, expected_df) + + # Test a single condition with or set + filters = [ + _compare_filter(0, ">=", 3, condition="or"), + ] + dxf.check_filter_case(df, filters, df[df["a"] >= 3]) + + def test_pandas_filter_compare(dxf: DataExplorerFixture): # Just use the 'a' column to smoke test comparison filters on # integers @@ -775,6 +820,40 @@ def test_pandas_filter_compare(dxf: DataExplorerFixture): dxf.compare_tables(table_name, ex_id, df.shape) +def test_pandas_filter_is_valid(dxf: DataExplorerFixture): + # Test AND/OR conditions when filtering + df = SIMPLE_PANDAS_DF + filters = [ + _compare_filter(0, ">=", 3), + _compare_filter(0, "<", 3, is_valid=False), + ] + + expected_df = df[df["a"] >= 3] + dxf.check_filter_case(df, filters, expected_df) + + # No filter is valid + filters = [ + _compare_filter(0, ">=", 3, is_valid=False), + _compare_filter(0, "<", 3, is_valid=False), + ] + + dxf.check_filter_case(df, filters, df) + + +def test_pandas_filter_empty(dxf: DataExplorerFixture): + df = pd.DataFrame( + { + "a": ["foo", "bar", "", "", "", None, "baz", ""], + "b": [b"foo", b"bar", b"", b"", None, b"", b"baz", b""], + } + ) + + dxf.check_filter_case(df, [_filter("is_empty", 0)], df[df["a"].str.len() == 0]) + dxf.check_filter_case(df, [_filter("not_empty", 0)], df[df["a"].str.len() != 0]) + dxf.check_filter_case(df, [_filter("is_empty", 1)], df[df["b"].str.len() == 0]) + dxf.check_filter_case(df, [_filter("not_empty", 1)], df[df["b"].str.len() != 0]) + + def test_pandas_filter_is_null_not_null(dxf: DataExplorerFixture): df = SIMPLE_PANDAS_DF b_is_null = _filter("is_null", 1) diff --git a/positron/comms/data_explorer-backend-openrpc.json b/positron/comms/data_explorer-backend-openrpc.json index f626b52c55f..a41ec4e57df 100644 --- a/positron/comms/data_explorer-backend-openrpc.json +++ b/positron/comms/data_explorer-backend-openrpc.json @@ -240,12 +240,13 @@ "result": { "schema": { "type": "object", - "name": "table_state", - "description": "The current backend table state", + "name": "backend_state", + "description": "The current backend state for the data explorer", "required": [ "table_shape", "row_filters", - "sort_keys" + "sort_keys", + "supported_features" ], "properties": { "table_shape": { @@ -280,38 +281,10 @@ "items": { "$ref": "#/components/schemas/column_sort_key" } - } - } - } - } - }, - { - "name": "get_supported_features", - "summary": "Query the backend to determine supported features", - "description": "Query the backend to determine supported features, to enable feature toggling", - "params": [], - "result": { - "schema": { - "type": "object", - "name": "supported_features", - "description": "For each field, returns flags indicating supported features", - "required": [ - "search_schema", - "set_row_filters", - "get_column_profiles" - ], - "properties": { - "search_schema": { - "description": "Support for 'search_schema' RPC and its features", - "$ref": "#/components/schemas/search_schema_features" - }, - "set_row_filters": { - "description": "Support for 'set_row_filters' RPC and its features", - "$ref": "#/components/schemas/set_row_filters_features" }, - "get_column_profiles": { - "description": "Support for 'get_column_profiles' RPC and its features", - "$ref": "#/components/schemas/get_column_profiles_features" + "supported_features": { + "description": "The features currently supported by the backend instance", + "$ref": "#/components/schemas/supported_features" } } } @@ -414,7 +387,8 @@ "required": [ "filter_id", "filter_type", - "column_index" + "column_index", + "condition" ], "properties": { "filter_id": { @@ -429,6 +403,18 @@ "type": "integer", "description": "Column index to apply filter to" }, + "condition": { + "type": "string", + "description": "The binary condition to use to combine with preceding row filters", + "enum": [ + "and", + "or" + ] + }, + "is_valid": { + "type": "boolean", + "description": "Whether the filter is valid and supported by the backend, if undefined then true" + }, "between_params": { "description": "Parameters for the 'between' and 'not_between' filter types", "$ref": "#/components/schemas/between_filter_params" @@ -453,8 +439,10 @@ "enum": [ "between", "compare", + "is_empty", "is_null", "not_between", + "not_empty", "not_null", "search", "set_membership" @@ -800,6 +788,29 @@ } } }, + "supported_features": { + "type": "object", + "description": "For each field, returns flags indicating supported features", + "required": [ + "search_schema", + "set_row_filters", + "get_column_profiles" + ], + "properties": { + "search_schema": { + "description": "Support for 'search_schema' RPC and its features", + "$ref": "#/components/schemas/search_schema_features" + }, + "set_row_filters": { + "description": "Support for 'set_row_filters' RPC and its features", + "$ref": "#/components/schemas/set_row_filters_features" + }, + "get_column_profiles": { + "description": "Support for 'get_column_profiles' RPC and its features", + "$ref": "#/components/schemas/get_column_profiles_features" + } + } + }, "search_schema_features": { "type": "object", "description": "Feature flags for 'search_schema' RPC", 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 94d0d6ee4a5..e9c89cdc700 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 @@ -21,7 +21,7 @@ import { DataExplorerClientInstance } from 'vs/workbench/services/languageRuntim import { DropDownListBox, DropDownListBoxEntry } from 'vs/workbench/browser/positronComponents/dropDownListBox/dropDownListBox'; import { RowFilterParameter } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/rowFilterParameter'; import { DropDownColumnSelector } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/dropDownColumnSelector'; -import { RangeRowFilterDescriptor, RowFilterDescriptor, RowFilterCondition, RowFilterDescriptorIsBetween, RowFilterDescriptorIsEmpty, RowFilterDescriptorIsEqualTo, RowFilterDescriptorIsGreaterThan, RowFilterDescriptorIsLessThan, RowFilterDescriptorIsNotBetween, RowFilterDescriptorIsNotEmpty, SingleValueRowFilterDescriptor } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; +import { RangeRowFilterDescriptor, RowFilterDescriptor, RowFilterCondition, RowFilterDescriptorIsBetween, RowFilterDescriptorIsEmpty, RowFilterDescriptorIsEqualTo, RowFilterDescriptorIsGreaterThan, RowFilterDescriptorIsLessThan, RowFilterDescriptorIsNotBetween, RowFilterDescriptorIsNotEmpty, SingleValueRowFilterDescriptor, RowFilterDescriptorIsLessOrEqual, RowFilterDescriptorIsGreaterOrEqual, RowFilterDescriptorIsNotNull, RowFilterDescriptorIsNull, RowFilterDescriptorIsNotEqualTo } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; /** * Validates a row filter value. @@ -74,6 +74,28 @@ const validateRowFilterValue = (columnSchema: ColumnSchema, value: string) => { } }; +/** + * Checks whether a RowFilterCondition is a comparison or not. + * @param cond A row filter condition. + * @returns Whether the condition is a comparison. + */ +const isComparison = (cond: RowFilterCondition | undefined) => { + if (cond === undefined) { + return false; + } + switch (cond) { + case RowFilterCondition.CONDITION_IS_EQUAL_TO: + case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO: + case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL: + case RowFilterCondition.CONDITION_IS_GREATER_THAN: + case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL: + case RowFilterCondition.CONDITION_IS_LESS_THAN: + return true; + default: + return false; + } +}; + /** * AddEditRowFilterModalPopupProps interface. */ @@ -141,25 +163,46 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp // Build the condition entries. const conditionEntries: DropDownListBoxEntry[] = []; - // Every type allows is empty and is not empty conditions. + // Every type allows is null and is not null conditions. conditionEntries.push(new DropDownListBoxItem({ - identifier: RowFilterCondition.CONDITION_IS_EMPTY, + identifier: RowFilterCondition.CONDITION_IS_NULL, title: localize( - 'positron.addEditRowFilter.conditionIsEmpty', - "is empty" + 'positron.addEditRowFilter.conditionIsNull', + "is null" ), - value: RowFilterCondition.CONDITION_IS_EMPTY + value: RowFilterCondition.CONDITION_IS_NULL })); conditionEntries.push(new DropDownListBoxItem({ - identifier: RowFilterCondition.CONDITION_IS_NOT_EMPTY, + identifier: RowFilterCondition.CONDITION_IS_NOT_NULL, title: localize( - 'positron.addEditRowFilter.conditionIsNotEmpty', - "is not empty" + 'positron.addEditRowFilter.conditionIsNotNull', + "is not null" ), - value: RowFilterCondition.CONDITION_IS_NOT_EMPTY + value: RowFilterCondition.CONDITION_IS_NOT_NULL })); conditionEntries.push(new DropDownListBoxSeparator()); + if (selectedColumnSchema.type_display === ColumnDisplayType.String) { + // String types support is empty, is not empty filter types + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterCondition.CONDITION_IS_EMPTY, + title: localize( + 'positron.addEditRowFilter.conditionIsEmpty', + "is empty" + ), + value: RowFilterCondition.CONDITION_IS_EMPTY + })); + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterCondition.CONDITION_IS_NOT_EMPTY, + title: localize( + 'positron.addEditRowFilter.conditionIsNotEmpty', + "is not empty" + ), + value: RowFilterCondition.CONDITION_IS_NOT_EMPTY + })); + conditionEntries.push(new DropDownListBoxSeparator()); + } + // Add is less than / is greater than conditions. switch (selectedColumnSchema.type_display) { case ColumnDisplayType.Number: @@ -174,6 +217,14 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp ), value: RowFilterCondition.CONDITION_IS_LESS_THAN })); + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL, + title: localize( + 'positron.addEditRowFilter.conditionIsLessThanOrEqual', + "is less than or equal to" + ), + value: RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL + })); conditionEntries.push(new DropDownListBoxItem({ identifier: RowFilterCondition.CONDITION_IS_GREATER_THAN, title: localize( @@ -182,10 +233,18 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp ), value: RowFilterCondition.CONDITION_IS_GREATER_THAN })); + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL, + title: localize( + 'positron.addEditRowFilter.conditionIsGreaterThanOrEqual', + "is greater than or equal to" + ), + value: RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL + })); break; } - // Add is equal to condition. + // Add is equal to, is not equal to conditions. switch (selectedColumnSchema.type_display) { case ColumnDisplayType.Number: case ColumnDisplayType.Boolean: @@ -201,6 +260,14 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp ), value: RowFilterCondition.CONDITION_IS_EQUAL_TO })); + conditionEntries.push(new DropDownListBoxItem({ + identifier: RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO, + title: localize( + 'positron.addEditRowFilter.conditionIsNotEqualTo', + "is not equal to" + ), + value: RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO + })); break; } @@ -240,14 +307,19 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp switch (selectedCondition) { // Do not render the first row filter parameter component. case undefined: + case RowFilterCondition.CONDITION_IS_NULL: + case RowFilterCondition.CONDITION_IS_NOT_NULL: case RowFilterCondition.CONDITION_IS_EMPTY: case RowFilterCondition.CONDITION_IS_NOT_EMPTY: return null; // Render the first row filter parameter component in single-value mode. case RowFilterCondition.CONDITION_IS_LESS_THAN: + case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL: case RowFilterCondition.CONDITION_IS_GREATER_THAN: + case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL: case RowFilterCondition.CONDITION_IS_EQUAL_TO: + case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO: placeholderText = localize( 'positron.addEditRowFilter.valuePlaceholder', "value" @@ -287,11 +359,16 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp switch (selectedCondition) { // Do not render the second row filter parameter component. case undefined: + case RowFilterCondition.CONDITION_IS_NULL: + case RowFilterCondition.CONDITION_IS_NOT_NULL: case RowFilterCondition.CONDITION_IS_EMPTY: case RowFilterCondition.CONDITION_IS_NOT_EMPTY: case RowFilterCondition.CONDITION_IS_LESS_THAN: + case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL: case RowFilterCondition.CONDITION_IS_GREATER_THAN: + case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL: case RowFilterCondition.CONDITION_IS_EQUAL_TO: + case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO: return null; // Render the second row filter parameter component in two-value mode. @@ -469,6 +546,18 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply the is null row filter. + case RowFilterCondition.CONDITION_IS_NULL: { + applyRowFilter(new RowFilterDescriptorIsNull(selectedColumnSchema)); + break; + } + + // Apply the is not null row filter. + case RowFilterCondition.CONDITION_IS_NOT_NULL: { + applyRowFilter(new RowFilterDescriptorIsNotNull(selectedColumnSchema)); + break; + } + // Apply the is less than row filter. case RowFilterCondition.CONDITION_IS_LESS_THAN: { if (!validateFirstRowFilterValue()) { @@ -481,6 +570,18 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply the is less than row filter. + case RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL: { + if (!validateFirstRowFilterValue()) { + return; + } + applyRowFilter(new RowFilterDescriptorIsLessOrEqual( + selectedColumnSchema, + firstRowFilterValue + )); + break; + } + // Apply the is greater than row filter. case RowFilterCondition.CONDITION_IS_GREATER_THAN: { if (!validateFirstRowFilterValue()) { @@ -493,6 +594,18 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply the is greater than row filter. + case RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL: { + if (!validateFirstRowFilterValue()) { + return; + } + applyRowFilter(new RowFilterDescriptorIsGreaterOrEqual( + selectedColumnSchema, + firstRowFilterValue + )); + break; + } + // Apply the is equal to row filter. case RowFilterCondition.CONDITION_IS_EQUAL_TO: { if (!validateFirstRowFilterValue()) { @@ -505,6 +618,18 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp break; } + // Apply the is not equal to row filter. + case RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO: { + if (!validateFirstRowFilterValue()) { + return; + } + applyRowFilter(new RowFilterDescriptorIsNotEqualTo( + selectedColumnSchema, + firstRowFilterValue + )); + break; + } + // Apply the is between row filter. case RowFilterCondition.CONDITION_IS_BETWEEN: { if (!validateFirstRowFilterValue()) { @@ -592,11 +717,15 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp entries={conditionEntries()} selectedIdentifier={selectedCondition} onSelectionChanged={dropDownListBoxItem => { + const prevSelected = selectedCondition; + const nextSelected = dropDownListBoxItem.options.identifier; // Set the selected condition. - setSelectedCondition(dropDownListBoxItem.options.identifier); + setSelectedCondition(nextSelected); // Clear the filter values and error text. - clearFilterValuesAndErrorText(); + if (!(isComparison(prevSelected) && isComparison(nextSelected))) { + clearFilterValuesAndErrorText(); + } }} /> 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 3434a0b968b..6566d4d2577 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 @@ -12,11 +12,16 @@ export enum RowFilterCondition { // Conditions with no parameters. CONDITION_IS_EMPTY = 'is-empty', CONDITION_IS_NOT_EMPTY = 'is-not-empty', + CONDITION_IS_NULL = 'is-null', + CONDITION_IS_NOT_NULL = 'is-not-null', // Conditions with one parameter. CONDITION_IS_LESS_THAN = 'is-less-than', + CONDITION_IS_LESS_OR_EQUAL = 'is-less-than-or-equal-to', CONDITION_IS_GREATER_THAN = 'is-greater-than', + CONDITION_IS_GREATER_OR_EQUAL = 'is-greater-than-or-equal-to', CONDITION_IS_EQUAL_TO = 'is-equal-to', + CONDITION_IS_NOT_EQUAL_TO = 'is-not-equal-to', // Conditions with two parameters. CONDITION_IS_BETWEEN = 'is-between', @@ -86,6 +91,46 @@ export class RowFilterDescriptorIsNotEmpty extends BaseRowFilterDescriptor { } } +/** + * RowFilterDescriptorIsNull class. + */ +export class RowFilterDescriptorIsNull extends BaseRowFilterDescriptor { + /** + * Constructor. + * @param columnSchema The column schema. + */ + constructor(columnSchema: ColumnSchema) { + super(columnSchema); + } + + /** + * Gets the row filter condition. + */ + get rowFilterCondition() { + return RowFilterCondition.CONDITION_IS_NULL; + } +} + +/** + * RowFilterDescriptorIsNotEmpty class. + */ +export class RowFilterDescriptorIsNotNull extends BaseRowFilterDescriptor { + /** + * Constructor. + * @param columnSchema The column schema. + */ + constructor(columnSchema: ColumnSchema) { + super(columnSchema); + } + + /** + * Gets the row filter condition. + */ + get rowFilterCondition() { + return RowFilterCondition.CONDITION_IS_NOT_NULL; + } +} + /** * SingleValueRowFilterDescriptor class. */ @@ -121,6 +166,27 @@ export class RowFilterDescriptorIsLessThan extends SingleValueRowFilterDescripto } } +/** + * RowFilterDescriptorIsLessOrEqual class. + */ +export class RowFilterDescriptorIsLessOrEqual extends SingleValueRowFilterDescriptor { + /** + * Constructor. + * @param columnSchema The column schema. + * @param value The value. + */ + constructor(columnSchema: ColumnSchema, value: string) { + super(columnSchema, value); + } + + /** + * Gets the row filter condition. + */ + get rowFilterCondition() { + return RowFilterCondition.CONDITION_IS_LESS_OR_EQUAL; + } +} + /** * RowFilterDescriptorIsGreaterThan class. */ @@ -142,6 +208,27 @@ export class RowFilterDescriptorIsGreaterThan extends SingleValueRowFilterDescri } } +/** + * RowFilterDescriptorIsGreaterOrEqual class. + */ +export class RowFilterDescriptorIsGreaterOrEqual extends SingleValueRowFilterDescriptor { + /** + * Constructor. + * @param columnSchema The column schema. + * @param value The value. + */ + constructor(columnSchema: ColumnSchema, value: string) { + super(columnSchema, value); + } + + /** + * Gets the row filter condition. + */ + get rowFilterCondition() { + return RowFilterCondition.CONDITION_IS_GREATER_OR_EQUAL; + } +} + /** * RowFilterDescriptorIsEqualTo class. */ @@ -163,6 +250,28 @@ export class RowFilterDescriptorIsEqualTo extends SingleValueRowFilterDescriptor } } + +/** + * RowFilterDescriptorIsNotEqualTo class. + */ +export class RowFilterDescriptorIsNotEqualTo extends SingleValueRowFilterDescriptor { + /** + * Constructor. + * @param columnSchema The column schema. + * @param value The value. + */ + constructor(columnSchema: ColumnSchema, value: string) { + super(columnSchema, value); + } + + /** + * Gets the row filter condition. + */ + get rowFilterCondition() { + return RowFilterCondition.CONDITION_IS_NOT_EQUAL_TO; + } +} + /** * RangeRowFilterDescriptor class. */ @@ -232,8 +341,12 @@ export class RowFilterDescriptorIsNotBetween extends RangeRowFilterDescriptor { export type RowFilterDescriptor = RowFilterDescriptorIsEmpty | RowFilterDescriptorIsNotEmpty | + RowFilterDescriptorIsNull | + RowFilterDescriptorIsNotNull | RowFilterDescriptorIsLessThan | + RowFilterDescriptorIsLessOrEqual | RowFilterDescriptorIsGreaterThan | + RowFilterDescriptorIsGreaterOrEqual | RowFilterDescriptorIsEqualTo | RowFilterDescriptorIsBetween | RowFilterDescriptorIsNotBetween; 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 25bbc0ea8ae..e5867031b17 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 @@ -12,7 +12,21 @@ import { forwardRef } from 'react'; // eslint-disable-line no-duplicate-imports // Other dependencies. import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; -import { RowFilterDescriptor, RowFilterDescriptorIsBetween, RowFilterDescriptorIsEmpty, RowFilterDescriptorIsEqualTo, RowFilterDescriptorIsGreaterThan, RowFilterDescriptorIsLessThan, RowFilterDescriptorIsNotBetween, RowFilterDescriptorIsNotEmpty } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; +import { + RowFilterDescriptor, + RowFilterDescriptorIsBetween, + RowFilterDescriptorIsEmpty, + RowFilterDescriptorIsEqualTo, + RowFilterDescriptorIsGreaterThan, + RowFilterDescriptorIsGreaterOrEqual, + RowFilterDescriptorIsLessThan, + RowFilterDescriptorIsLessOrEqual, + RowFilterDescriptorIsNotBetween, + RowFilterDescriptorIsNotEmpty, + RowFilterDescriptorIsNotNull, + RowFilterDescriptorIsNull, + RowFilterDescriptorIsNotEqualTo +} from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; /** * RowFilterWidgetProps interface. @@ -46,24 +60,57 @@ export const RowFilterWidget = forwardRef ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsNull) { + return <> + {props.rowFilter.columnSchema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isNull', "is null")} + + ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsNotNull) { + return <> + {props.rowFilter.columnSchema.column_name} + + {localize('positron.dataExplorer.rowFilterWidget.isNotNull', "is not null")} + + ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsLessThan) { return <> {props.rowFilter.columnSchema.column_name} < {props.rowFilter.value} ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsLessOrEqual) { + return <> + {props.rowFilter.columnSchema.column_name} + <= + {props.rowFilter.value} + ; } else if (props.rowFilter instanceof RowFilterDescriptorIsGreaterThan) { return <> {props.rowFilter.columnSchema.column_name} > {props.rowFilter.value} ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsGreaterOrEqual) { + return <> + {props.rowFilter.columnSchema.column_name} + >= + {props.rowFilter.value} + ; } else if (props.rowFilter instanceof RowFilterDescriptorIsEqualTo) { return <> {props.rowFilter.columnSchema.column_name} = {props.rowFilter.value} ; + } else if (props.rowFilter instanceof RowFilterDescriptorIsNotEqualTo) { + return <> + {props.rowFilter.columnSchema.column_name} + != + {props.rowFilter.value} + ; } else if (props.rowFilter instanceof RowFilterDescriptorIsBetween) { return <> {props.rowFilter.columnSchema.column_name} diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx index 54deaf0aa4f..a6b0f611836 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/rowFilterBar.tsx @@ -18,10 +18,24 @@ import { ContextMenuItem } from 'vs/workbench/browser/positronComponents/context import { ContextMenuSeparator } from 'vs/workbench/browser/positronComponents/contextMenu/contextMenuSeparator'; import { usePositronDataExplorerContext } from 'vs/workbench/browser/positronDataExplorer/positronDataExplorerContext'; import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer'; -import { CompareFilterParamsOp, RowFilter, RowFilterType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; +import { CompareFilterParamsOp, RowFilter, RowFilterCondition, RowFilterType } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; import { RowFilterWidget } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/rowFilterBar/components/rowFilterWidget'; import { AddEditRowFilterModalPopup } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/addEditRowFilterModalPopup'; -import { RowFilterDescriptor, RowFilterDescriptorIsEmpty, RowFilterDescriptorIsNotEmpty, RowFilterDescriptorIsLessThan, RowFilterDescriptorIsGreaterThan, RowFilterDescriptorIsEqualTo, RowFilterDescriptorIsBetween, RowFilterDescriptorIsNotBetween } from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; +import { + RowFilterDescriptor, + RowFilterDescriptorIsEmpty, + RowFilterDescriptorIsNotEmpty, + RowFilterDescriptorIsNull, + RowFilterDescriptorIsNotNull, + RowFilterDescriptorIsLessThan, + RowFilterDescriptorIsGreaterThan, + RowFilterDescriptorIsEqualTo, + RowFilterDescriptorIsBetween, + RowFilterDescriptorIsNotBetween, + RowFilterDescriptorIsLessOrEqual, + RowFilterDescriptorIsGreaterOrEqual, + RowFilterDescriptorIsNotEqualTo +} from 'vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/rowFilterDescriptor'; /** * Creates row filters from row filter descriptors. @@ -35,67 +49,72 @@ const createRowFilters = (rowFilterDescriptors: RowFilterDescriptor[]) => { rowFilterDescriptor ) => { // + const sharedParams = { + filter_id: rowFilterDescriptor.identifier, + column_index: rowFilterDescriptor.columnSchema.column_index, + condition: RowFilterCondition.And + }; + + const getCompareFilter = (value: string, op: CompareFilterParamsOp): RowFilter => { + return { + filter_type: RowFilterType.Compare, + compare_params: { + op, + value + }, + ...sharedParams + }; + }; + if (rowFilterDescriptor instanceof RowFilterDescriptorIsEmpty) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, - filter_type: RowFilterType.IsNull, - column_index: rowFilterDescriptor.columnSchema.column_index + filter_type: RowFilterType.IsEmpty, + ...sharedParams }); } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotEmpty) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, - filter_type: RowFilterType.NotNull, - column_index: rowFilterDescriptor.columnSchema.column_index + filter_type: RowFilterType.NotEmpty, + ...sharedParams }); - } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsLessThan) { + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNull) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, - filter_type: RowFilterType.Compare, - column_index: rowFilterDescriptor.columnSchema.column_index, - compare_params: { - op: CompareFilterParamsOp.Lt, - value: rowFilterDescriptor.value - } + filter_type: RowFilterType.IsNull, + ...sharedParams }); - } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsGreaterThan) { + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotNull) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, - filter_type: RowFilterType.Compare, - column_index: rowFilterDescriptor.columnSchema.column_index, - compare_params: { - op: CompareFilterParamsOp.Gt, - value: rowFilterDescriptor.value - } + filter_type: RowFilterType.NotNull, + ...sharedParams }); + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsLessThan) { + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.Lt)); + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsLessOrEqual) { + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.LtEq)); + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsGreaterThan) { + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.Gt)); + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsGreaterOrEqual) { + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.GtEq)); } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsEqualTo) { - rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, - filter_type: RowFilterType.Compare, - column_index: rowFilterDescriptor.columnSchema.column_index, - compare_params: { - op: CompareFilterParamsOp.Eq, - value: rowFilterDescriptor.value - } - }); + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.Eq)); + } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotEqualTo) { + rowFilters.push(getCompareFilter(rowFilterDescriptor.value, CompareFilterParamsOp.NotEq)); } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsBetween) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, filter_type: RowFilterType.Between, - column_index: rowFilterDescriptor.columnSchema.column_index, between_params: { left_value: rowFilterDescriptor.lowerLimit, right_value: rowFilterDescriptor.upperLimit - } + }, + ...sharedParams }); } else if (rowFilterDescriptor instanceof RowFilterDescriptorIsNotBetween) { rowFilters.push({ - filter_id: rowFilterDescriptor.identifier, filter_type: RowFilterType.NotBetween, - column_index: rowFilterDescriptor.columnSchema.column_index, between_params: { left_value: rowFilterDescriptor.lowerLimit, right_value: rowFilterDescriptor.upperLimit - } + }, + ...sharedParams }); } diff --git a/src/vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient.ts b/src/vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient.ts index 728fda8a6cd..2f7b25ec6f0 100644 --- a/src/vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient.ts +++ b/src/vs/workbench/services/languageRuntime/common/languageRuntimeDataExplorerClient.ts @@ -6,7 +6,19 @@ import { generateUuid } from 'vs/base/common/uuid'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IRuntimeClientInstance } from 'vs/workbench/services/languageRuntime/common/languageRuntimeClientInstance'; -import { ColumnProfileRequest, ColumnProfileResult, ColumnSchema, ColumnSortKey, FilterResult, PositronDataExplorerComm, RowFilter, SchemaUpdateEvent, TableData, TableSchema, TableState } from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; +import { + BackendState, + ColumnProfileRequest, + ColumnProfileResult, + ColumnSchema, + ColumnSortKey, + FilterResult, + PositronDataExplorerComm, + RowFilter, + SchemaUpdateEvent, + TableData, + TableSchema +} from 'vs/workbench/services/languageRuntime/common/positronDataExplorerComm'; /** * TableSchemaSearchResult interface. This is here temporarily until searching the tabe schema @@ -97,7 +109,7 @@ export class DataExplorerClientInstance extends Disposable { * Get the current active state of the data explorer backend. * @returns A promose that resolves to the current table state. */ - async getState(): Promise { + async getState(): Promise { return this._positronDataExplorerComm.getState(); } diff --git a/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts b/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts index 7253d51e01c..a5b609e6506 100644 --- a/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts +++ b/src/vs/workbench/services/languageRuntime/common/positronDataExplorerComm.ts @@ -54,9 +54,9 @@ export interface FilterResult { } /** - * The current backend table state + * The current backend state for the data explorer */ -export interface TableState { +export interface BackendState { /** * Provides number of rows and columns in table */ @@ -72,6 +72,11 @@ export interface TableState { */ sort_keys: Array; + /** + * The features currently supported by the backend instance + */ + supported_features: SupportedFeatures; + } /** @@ -90,27 +95,6 @@ export interface TableShape { } -/** - * For each field, returns flags indicating supported features - */ -export interface SupportedFeatures { - /** - * Support for 'search_schema' RPC and its features - */ - search_schema: SearchSchemaFeatures; - - /** - * Support for 'set_row_filters' RPC and its features - */ - set_row_filters: SetRowFiltersFeatures; - - /** - * Support for 'get_column_profiles' RPC and its features - */ - get_column_profiles: GetColumnProfilesFeatures; - -} - /** * Schema for a column in a table */ @@ -197,6 +181,17 @@ export interface RowFilter { */ column_index: number; + /** + * The binary condition to use to combine with preceding row filters + */ + condition: RowFilterCondition; + + /** + * Whether the filter is valid and supported by the backend, if undefined + * then true + */ + is_valid?: boolean; + /** * Parameters for the 'between' and 'not_between' filter types */ @@ -506,6 +501,27 @@ export interface ColumnSortKey { } +/** + * For each field, returns flags indicating supported features + */ +export interface SupportedFeatures { + /** + * Support for 'search_schema' RPC and its features + */ + search_schema: SearchSchemaFeatures; + + /** + * Support for 'set_row_filters' RPC and its features + */ + set_row_filters: SetRowFiltersFeatures; + + /** + * Support for 'get_column_profiles' RPC and its features + */ + get_column_profiles: GetColumnProfilesFeatures; + +} + /** * Feature flags for 'search_schema' RPC */ @@ -569,14 +585,24 @@ export enum ColumnDisplayType { Unknown = 'unknown' } +/** + * Possible values for Condition in RowFilter + */ +export enum RowFilterCondition { + And = 'and', + Or = 'or' +} + /** * Possible values for RowFilterType */ export enum RowFilterType { Between = 'between', Compare = 'compare', + IsEmpty = 'is_empty', IsNull = 'is_null', NotBetween = 'not_between', + NotEmpty = 'not_empty', NotNull = 'not_null', Search = 'search', SetMembership = 'set_membership' @@ -737,25 +763,12 @@ export class PositronDataExplorerComm extends PositronBaseComm { * Request the current table state (applied filters and sort columns) * * - * @returns The current backend table state + * @returns The current backend state for the data explorer */ - getState(): Promise { + getState(): Promise { return super.performRpc('get_state', [], []); } - /** - * Query the backend to determine supported features - * - * Query the backend to determine supported features, to enable feature - * toggling - * - * - * @returns For each field, returns flags indicating supported features - */ - getSupportedFeatures(): Promise { - return super.performRpc('get_supported_features', [], []); - } - /** * Reset after a schema change