diff --git a/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.tsx b/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.tsx index 02fa06f25f8..f683ebc4845 100644 --- a/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.tsx +++ b/src/vs/workbench/browser/positronComponents/positronModalPopup/positronModalPopup.tsx @@ -7,7 +7,7 @@ import './positronModalPopup.css'; // React. -import React, { PropsWithChildren, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import React, { PropsWithChildren, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; // Other dependencies. import * as DOM from '../../../../base/browser/dom.js'; @@ -19,8 +19,8 @@ import { PositronModalReactRenderer } from '../../positronModalReactRenderer/pos /** * Constants. */ -const LAYOUT_MARGIN = 4; -const MIN_SCROLLABLE_HEIGHT = 75; +const LAYOUT_OFFSET = 2; +const LAYOUT_MARGIN = 10; /** * Focusable element selectors. @@ -34,45 +34,6 @@ const focusableElementSelectors = 'input[type="checkbox"]:not([disabled]),' + 'select:not([disabled])'; -/** - * AnchorLayout class. - */ -class AnchorLayout { - /** - * The anchor x. - */ - readonly anchorX: number; - - /** - * The anchor y. - */ - readonly anchorY: number; - - /** - * The anchor width. - */ - readonly anchorWidth: number; - - /** - * The anchor height. - */ - readonly anchorHeight: number; - - /** - * Constructor. - * @param anchorX The anchor x. - * @param anchorY The anchor y. - * @param anchorWidth The anchor width. - * @param anchorHeight The anchor height. - */ - constructor(anchorX: number, anchorY: number, anchorWidth: number, anchorHeight: number) { - this.anchorX = anchorX; - this.anchorY = anchorY; - this.anchorWidth = anchorWidth; - this.anchorHeight = anchorHeight; - } -} - /** * PopupLayout class. */ @@ -81,8 +42,11 @@ class PopupLayout { right: number | 'auto' = 'auto'; bottom: number | 'auto' = 'auto'; left: number | 'auto' = 'auto'; - maxWidth: number | 'auto' = 'auto'; - maxHeight: number | 'auto' = 'auto'; + width: number | 'min-content' = 'min-content'; + height: number | 'min-content' = 'min-content'; + maxWidth: number | 'none' = 'none'; + maxHeight: number | 'none' = 'none'; + shadow: 'top' | 'bottom' = 'bottom'; } /** @@ -96,7 +60,7 @@ export interface AnchorPoint { /** * PopupPosition type. */ -export type PopupPosition = 'top' | 'bottom' | 'auto'; +export type PopupPosition = 'bottom' | 'top' | 'auto'; /** * PopupAlignment type. @@ -121,6 +85,8 @@ export interface PositronModalPopupProps { readonly minWidth?: number | 'auto'; readonly height: number | 'min-content'; readonly minHeight?: number | 'auto'; + readonly maxHeight?: number | 'none'; + readonly fixedHeight?: boolean; readonly focusableElementSelectors?: string; readonly keyboardNavigationStyle: KeyboardNavigationStyle; readonly onAccept?: () => void; @@ -138,33 +104,45 @@ export const PositronModalPopup = (props: PropsWithChildren(undefined!); // State hooks. - const [anchorLayout] = useState(() => { - if (props.anchorPoint) { - return new AnchorLayout(props.anchorPoint.clientX, props.anchorPoint.clientY, 0, 0); - } else { - const topLeftAnchorOffset = DOM.getTopLeftOffset(props.anchorElement); - return new AnchorLayout( - topLeftAnchorOffset.left, - topLeftAnchorOffset.top, - props.anchorElement.offsetWidth, - props.anchorElement.offsetHeight - ); - } - }); const [popupLayout, setPopupLayout] = useState(() => { // Initially, position the popup off screen. - const newPopupLayout = new PopupLayout(); - newPopupLayout.left = -10000; - newPopupLayout.top = -10000; - return newPopupLayout; + const popupLayout = new PopupLayout(); + popupLayout.left = -10000; + popupLayout.top = -10000; + return popupLayout; }); - // Layout. - useLayoutEffect(() => { + /** + * Updates the popup layout. + */ + const updatePopupLayout = useCallback(() => { // Get the document width and height. const { clientWidth: documentWidth, clientHeight: documentHeight } = DOM.getWindow(popupRef.current).document.documentElement; + // Calculate the anchor position and size. + let anchorX: number; + let anchorY: number; + let anchorWidth: number; + let anchorHeight: number; + if (props.anchorPoint) { + anchorX = props.anchorPoint.clientX; + anchorY = props.anchorPoint.clientY; + anchorWidth = 0; + anchorHeight = 0; + } else { + const topLeftAnchorOffset = DOM.getTopLeftOffset(props.anchorElement); + anchorX = topLeftAnchorOffset.left; + anchorY = topLeftAnchorOffset.top; + anchorWidth = props.anchorElement.offsetWidth; + anchorHeight = props.anchorElement.offsetHeight; + } + + // Calculate the left and right area widths. This is the space that is available for laying + // out the modal popup anchored to the left or right of the anchor point or element. + const leftAreaWidth = anchorX + anchorWidth - LAYOUT_MARGIN; + const rightAreaWidth = documentWidth - anchorX - LAYOUT_MARGIN; + // Create the popup layout. const popupLayout = new PopupLayout(); @@ -172,113 +150,137 @@ export const PositronModalPopup = (props: PropsWithChildren { - popupLayout.left = anchorLayout.anchorX; + popupLayout.left = anchorX; }; /** * Positions the popup aligned with the right edge of the anchor element. */ const positionRight = () => { - popupLayout.right = documentWidth - (anchorLayout.anchorX + anchorLayout.anchorWidth); + if (isNumber(props.width)) { + popupLayout.left = (anchorX + anchorWidth) - props.width; + } else { + popupLayout.right = documentWidth - (anchorX + anchorWidth); + } }; - // Adjust the popup layout for the popup alignment. + // Perform horizontal popup layout. if (props.popupAlignment === 'left') { positionLeft(); } else if (props.popupAlignment === 'right') { positionRight(); } else if (props.popupAlignment === 'auto') { - // Get the children width. - const childrenWidth = popupChildrenRef.current ? - popupChildrenRef.current.scrollWidth : - 0; - - // Calculate the ideal right. - const idealRight = anchorLayout.anchorX + - childrenWidth + - LAYOUT_MARGIN; - - // Try to position the popup fully at the bottom or fully at the top. If this this isn't - // possible, try to position the popup with scrolling at the bottom or at the top. If - // this isn't posssible, fallback to positioning the popup at the top of its container. - if (idealRight < documentWidth) { - positionLeft(); - } else if (childrenWidth < anchorLayout.anchorX - 1) { + if (leftAreaWidth > rightAreaWidth) { positionRight(); } else { - popupLayout.left = 0; + positionLeft(); } } - /** - * Positions the popup at the bottom of the anchor element. - */ - const positionBottom = () => { - popupLayout.top = anchorLayout.anchorY + anchorLayout.anchorHeight + 1; - popupLayout.maxHeight = documentHeight - LAYOUT_MARGIN - popupLayout.top; - }; + // Calculate the top and bottom area heights. This is the space that is available for laying + // out the modal popup anchored to the top or bottom of the anchor point or element. + const topAreaHeight = anchorY - LAYOUT_OFFSET - LAYOUT_MARGIN; + const bottomAreaHeight = documentHeight - + (anchorY + anchorHeight + LAYOUT_OFFSET + LAYOUT_MARGIN); + + // Perform vertical popup layout. + if (props.height === 'min-content') { + // Set the popup layout height. + popupLayout.height = props.height; + + // Calculate the layout height. (Adding 2 for the border.) + const layoutHeight = popupChildrenRef.current.offsetHeight + 2; + + // Position the popup at the bottom. + const positionBottom = () => { + popupLayout.top = anchorY + anchorHeight + LAYOUT_OFFSET; + if (props.fixedHeight) { + popupLayout.top = Math.min(popupLayout.top, documentHeight - layoutHeight - LAYOUT_MARGIN); + } else { + popupLayout.maxHeight = documentHeight - popupLayout.top - LAYOUT_MARGIN; + } + popupLayout.shadow = 'bottom'; + }; - /** - * Positions the popup at the top of the anchor element. - */ - const positionTop = () => { - popupLayout.bottom = documentHeight - (anchorLayout.anchorY - 1); - popupLayout.maxHeight = anchorLayout.anchorY - LAYOUT_MARGIN; - }; + // Position the popup at the top. + const positionTop = () => { + const drawHeight = Math.min(topAreaHeight, layoutHeight); + popupLayout.top = Math.max(anchorY - drawHeight - LAYOUT_OFFSET, LAYOUT_MARGIN); + popupLayout.maxHeight = drawHeight; + popupLayout.shadow = 'top'; + }; - // Adjust the popup layout for the popup position. - if (props.popupPosition === 'bottom') { - positionBottom(); - } else if (props.popupPosition === 'top') { - positionTop(); - } else if (props.popupPosition === 'auto') { - // Get the children height. - const childrenHeight = popupChildrenRef.current ? - popupChildrenRef.current.scrollHeight : - 0; - - // Calculate the ideal bottom. - const idealBottom = anchorLayout.anchorY + - anchorLayout.anchorHeight + - 1 + - childrenHeight + - LAYOUT_MARGIN; - - // Try to position the popup fully at the bottom or fully at the top. If this this - // isn't possible, try to position the popup with scrolling at the bottom or with - // scrolling at the top. If this isn't posssible, fallback to positioning the popup at - // the top of its container. - if (idealBottom < documentHeight - 1) { + // Adjust the popup layout for the popup position. + if (props.popupPosition === 'bottom') { positionBottom(); - } else if (childrenHeight < anchorLayout.anchorY - 1) { + } else if (props.popupPosition === 'top') { positionTop(); } else { - // Calculate the max bottom height. - const top = anchorLayout.anchorY + anchorLayout.anchorHeight + 1; - const maxBottomHeight = documentHeight - LAYOUT_MARGIN - top; - - // Position the popup on the bottom with scrolling, if we can. - if (maxBottomHeight > MIN_SCROLLABLE_HEIGHT) { + if (layoutHeight <= bottomAreaHeight) { positionBottom(); - } else if (anchorLayout.anchorY - LAYOUT_MARGIN > MIN_SCROLLABLE_HEIGHT) { + } else if (layoutHeight <= topAreaHeight) { positionTop(); } else { - // Position the popup at the top of its container. - popupLayout.top = LAYOUT_MARGIN; - popupLayout.maxHeight = documentHeight - (LAYOUT_MARGIN * 2); + if (bottomAreaHeight > topAreaHeight) { + positionBottom(); + } else { + positionTop(); + } + } + } + } else { + // Set the popup layout height. + popupLayout.height = props.height; + + // Position the popup at the bottom. + const positionBottom = () => { + popupLayout.top = anchorY + anchorHeight + LAYOUT_OFFSET; + popupLayout.maxHeight = bottomAreaHeight; + popupLayout.shadow = 'bottom'; + }; + + // Position the popup at the top. + const positionTop = (height: number) => { + const drawHeight = Math.min(topAreaHeight, height); + popupLayout.top = anchorY - drawHeight - LAYOUT_OFFSET; + popupLayout.maxHeight = drawHeight;//topAreaHeight; + popupLayout.shadow = 'top'; + }; + + // Adjust the popup layout for the popup position. + if (props.popupPosition === 'bottom') { + positionBottom(); + } else if (props.popupPosition === 'top') { + positionTop(props.height); + } else { + if (bottomAreaHeight > topAreaHeight) { + positionBottom(); + } else { + positionTop(props.height); } } } // Set the popup layout. setPopupLayout(popupLayout); - }, [anchorLayout.anchorHeight, anchorLayout.anchorWidth, anchorLayout.anchorX, anchorLayout.anchorY, props.popupAlignment, props.popupPosition]); + }, [props.anchorElement, props.anchorPoint, props.height, props.popupAlignment, props.popupPosition, props.width, props.fixedHeight]); + + // Layout. + useLayoutEffect(() => { + updatePopupLayout(); + }, [updatePopupLayout]); // Event handlers. useEffect(() => { // Create a disposable store for the event handlers we'll add. const disposableStore = new DisposableStore(); + // Add the onResize event handler. + disposableStore.add(props.renderer.onResize(e => { + // On resize, update the layout. + updatePopupLayout(); + })); + // Add the onKeyDown event handler. disposableStore.add(props.renderer.onKeyDown(e => { /** @@ -410,42 +412,9 @@ export const PositronModalPopup = (props: PropsWithChildren { - // Get the document width and height. - const { clientWidth: documentWidth, clientHeight: documentHeight } = - DOM.getWindow(popupRef.current).document.documentElement; - - // Get the popup right and bottom. - const { right: popupRight, bottom: popupBottom } = - popupRef.current.getBoundingClientRect(); - - // When resizing results in the popup being off screen, dispose of it. - if (popupRight >= documentWidth - LAYOUT_MARGIN || - popupBottom >= documentHeight - LAYOUT_MARGIN - ) { - props.renderer.dispose(); - } else if (isNumber(popupLayout.maxHeight)) { - // Increase the max height, if possible. - if (isNumber(popupLayout.top)) { - // Bottom alignment. - const maxHeight = documentHeight - LAYOUT_MARGIN - popupLayout.top; - if (maxHeight > popupLayout.maxHeight) { - setPopupLayout({ ...popupLayout, maxHeight }); - } - } else if (isNumber(popupLayout.bottom)) { - // Top alignment. - const maxHeight = anchorLayout.anchorY - LAYOUT_MARGIN; - if (maxHeight > popupLayout.maxHeight) { - setPopupLayout({ ...popupLayout, maxHeight }); - } - } - } - })); - // Return the clean up for our event handlers. return () => disposableStore.dispose(); - }, [anchorLayout.anchorY, popupLayout, props]); + }, [props, updatePopupLayout]); // Render. return ( @@ -459,20 +428,11 @@ export const PositronModalPopup = (props: PropsWithChildren { - // window.ts#registerListeners() discards wheel events to prevent back / forward - // gestures. Send wheel events to the div so they are not lost. - e.currentTarget.scrollBy(e.deltaX, e.deltaY); - e.preventDefault(); + width: props.width }} >
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 a2cc23369a9..6c4ee4ed419 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 @@ -753,6 +753,7 @@ export const AddEditRowFilterModalPopup = (props: AddEditRowFilterModalPopupProp return ( { try { // Get the backend state so that we can get the initial number of columns. - const backedState = await dataExplorerClientInstance.getBackendState(); + const backendState = await dataExplorerClientInstance.getBackendState(); // Return a new instance of the column selector data grid instance. return new ColumnSelectorDataGridInstance( - backedState.table_shape.num_columns, + backendState, dataExplorerClientInstance ); } catch { @@ -73,11 +78,11 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { /** * Constructor. - * @param initialColumns The initial number of columns. + * @param backendState The initial backend state. * @param _dataExplorerClientInstance The data explorer client instance. */ - protected constructor( - initialColumns: number, + private constructor( + backendState: BackendState, private readonly _dataExplorerClientInstance: DataExplorerClientInstance, ) { // Call the base class's constructor. @@ -101,26 +106,32 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { selection: false }); + // Set the backend state. + this._backendState = backendState; + // Create the column schema cache. this._register( this._columnSchemaCache = new ColumnSchemaCache(this._dataExplorerClientInstance) ); // Set the initial layout entries in the row layout manager. - this._rowLayoutManager.setLayoutEntries(initialColumns); + this._rowLayoutManager.setLayoutEntries(backendState.table_shape.num_columns); /** * Updates the data grid instance. - * @param state The state, if known; otherwise, undefined. + * @param backendState The backend state, if known; otherwise, undefined. */ - const updateDataGridInstance = async (state?: BackendState) => { + const updateDataGridInstance = async (backendState?: BackendState) => { // Get the backend state, if it was not supplied. - if (!state) { - state = await this._dataExplorerClientInstance.getBackendState(); + if (!backendState) { + backendState = await this._dataExplorerClientInstance.getBackendState(); } + // Update the backend state. + this._backendState = backendState; + // Set the layout entries in the row layout manager. - this._rowLayoutManager.setLayoutEntries(state.table_shape.num_columns); + this._rowLayoutManager.setLayoutEntries(backendState.table_shape.num_columns); // Scroll to the top. await this.setScrollOffsets(0, 0); @@ -139,10 +150,12 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { )); // Add the onDidUpdateBackendState event handler. - this._register(this._dataExplorerClientInstance.onDidUpdateBackendState(async state => - // Update the data grid instance. - updateDataGridInstance(state) - )); + this._register( + this._dataExplorerClientInstance.onDidUpdateBackendState(async backendState => + // Update the data grid instance. + updateDataGridInstance(backendState) + ) + ); // Add the onDidUpdateCache event handler. this._register(this._columnSchemaCache.onDidUpdateCache(() => @@ -166,7 +179,7 @@ export class ColumnSelectorDataGridInstance extends DataGridInstance { * Gets the number of rows. */ get rows() { - return this._columnSchemaCache.columns; + return this._backendState.table_shape.num_columns; } /** diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.css b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.css index db5a7387174..f5680158878 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.css +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.css @@ -7,16 +7,22 @@ width: 100%; height: 100%; display: grid; - grid-template-rows: [column-selector-search] 34px [column-selector-data-grid] 1fr [end-column-selector-data-grid]; + + /* Prevent scrolling the entire modal. */ + overflow: hidden; + grid-template-rows: [column-selector-search] min-content [column-selector-data-grid] 1fr [end-column-selector-data-grid]; } -.column-selector -.column-selector-search { +.column-selector .column-selector-search { + height: 34px; grid-row: column-selector-search / column-selector-data-grid; border-bottom: 1px solid var(--vscode-positronDataExplorer-border); } -.column-selector -.column-selector-data-grid { +.column-selector .column-selector-data-grid { + /* Allow the grid to shrink. */ + min-width: 0; + /* Allow the grid to shrink. */ + min-height: 0; grid-row: column-selector-data-grid / end-column-selector-data-grid; } diff --git a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx index 5addf6ca649..60e6a0f465a 100644 --- a/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx +++ b/src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.tsx @@ -10,14 +10,17 @@ import './columnSelectorModalPopup.css'; import React, { useEffect, useRef } from 'react'; // Other dependencies. +import { ColumnSearch } from './columnSearch.js'; import { DisposableStore } from '../../../../../../../../base/common/lifecycle.js'; -import { IConfigurationService } from '../../../../../../../../platform/configuration/common/configuration.js'; +import { ColumnSelectorDataGridInstance } from './columnSelectorDataGridInstance.js'; import { PositronDataGrid } from '../../../../../../positronDataGrid/positronDataGrid.js'; +import { IConfigurationService } from '../../../../../../../../platform/configuration/common/configuration.js'; import { ColumnSchema } from '../../../../../../../services/languageRuntime/common/positronDataExplorerComm.js'; import { PositronModalPopup } from '../../../../../../positronComponents/positronModalPopup/positronModalPopup.js'; import { PositronModalReactRenderer } from '../../../../../../positronModalReactRenderer/positronModalReactRenderer.js'; -import { ColumnSearch } from './columnSearch.js'; -import { ColumnSelectorDataGridInstance } from './columnSelectorDataGridInstance.js'; + +// Constants. +const SEARCH_AREA_HEIGHT = 34; /** * ColumnSelectorModalPopupProps interface. @@ -43,10 +46,14 @@ export const ColumnSelectorModalPopup = (props: ColumnSelectorModalPopupProps) = // Main useEffect. useEffect(() => { - if (props.focusInput) { return; } + if (props.focusInput) { + return; + } + // Drive focus into the data grid so the user can immediately navigate. props.columnSelectorDataGridInstance.setCursorPosition(0, 0); positronDataGridRef.current.focus(); + }, [props.columnSelectorDataGridInstance, props.focusInput]); useEffect(() => { @@ -70,45 +77,64 @@ export const ColumnSelectorModalPopup = (props: ColumnSelectorModalPopupProps) = } }; + // Calculate the max height. + const { defaultRowHeight, rows, rowsMargin } = props.columnSelectorDataGridInstance; + + // Enable search when there are more than 10 rows. + const enableSearch = rows > 10; + + // Calculate the base height. This is the height of the search UI plus the height of the top and + // bottom rows margin plus the height of the border of the popup. + const baseHeight = (enableSearch ? SEARCH_AREA_HEIGHT : 0) + (2 * rowsMargin) + 2; + + // Calculate the max height for all rows. + const maxHeight = baseHeight + (rows * defaultRowHeight); + + // Calculate the min height for two rows. + const minHeight = baseHeight + (2 * defaultRowHeight); + // Render. return ( -
-
- { - props.columnSelectorDataGridInstance.selectItem(props.columnSelectorDataGridInstance.cursorColumnIndex); - }} - onNavigateOut={() => { - positronDataGridRef.current.focus(); - props.columnSelectorDataGridInstance.showCursor(); - }} - onSearchTextChanged={async searchText => { - await props.columnSelectorDataGridInstance.setSearchText( - searchText !== '' ? searchText : undefined - ); - }} - /> -
-
+
+ {enableSearch && ( +
+ { + props.columnSelectorDataGridInstance.selectItem( + props.columnSelectorDataGridInstance.cursorColumnIndex + ); + }} + onNavigateOut={() => { + positronDataGridRef.current.focus(); + props.columnSelectorDataGridInstance.showCursor(); + }} + onSearchTextChanged={async searchText => { + await props.columnSelectorDataGridInstance.setSearchText( + searchText !== '' ? searchText : undefined + ); + }} + /> +
+ )} +
diff --git a/test/e2e/pages/dataExplorer.ts b/test/e2e/pages/dataExplorer.ts index e9047a4505d..b17c1b41bef 100644 --- a/test/e2e/pages/dataExplorer.ts +++ b/test/e2e/pages/dataExplorer.ts @@ -16,15 +16,7 @@ const CLOSE_DATA_EXPLORER = '.tab .codicon-close'; const IDLE_STATUS = '.status-bar-indicator .icon.idle'; const SCROLLBAR_LOWER_RIGHT_CORNER = '.data-grid-scrollbar-corner'; const DATA_GRID_TOP_LEFT = '.data-grid-corner-top-left'; -const ADD_FILTER_BUTTON = '.codicon-positron-add-filter'; -const COLUMN_SELECTOR = '.positron-modal-overlay .drop-down-column-selector'; -const COLUMN_INPUT = '.positron-modal-overlay .column-search-input .text-input'; -const COLUMN_SELECTOR_CELL = '.column-selector-cell'; -const FUNCTION_SELECTOR = '.positron-modal-overlay .drop-down-list-box'; -const FILTER_SELECTOR = '.positron-modal-overlay .row-filter-parameter-input .text-input'; -const APPLY_FILTER = '.positron-modal-overlay .button-apply-row-filter'; const STATUS_BAR = '.positron-data-explorer .status-bar'; -const OVERLAY_BUTTON = '.positron-modal-overlay .positron-button'; const CLEAR_SORTING_BUTTON = '.codicon-positron-clear-sorting'; const MISSING_PERCENT = (rowNumber: number) => `${DATA_GRID_ROW}:nth-child(${rowNumber}) .column-null-percent .text-percent`; const EXPAND_COLLAPSE_PROFILE = (rowNumber: number) => `${DATA_GRID_ROW}:nth-child(${rowNumber}) .expand-collapse-button`; @@ -48,9 +40,17 @@ export interface ColumnProfile { export class DataExplorer { clearSortingButton: Locator; + addFilterButton: Locator; + selectColumnButton: Locator; + selectConditionButton: Locator; + applyFilterButton: Locator; constructor(private code: Code, private workbench: Workbench) { this.clearSortingButton = this.code.driver.page.locator(CLEAR_SORTING_BUTTON); + this.addFilterButton = this.code.driver.page.getByRole('button', { name: 'Add Filter' }); + this.selectColumnButton = this.code.driver.page.getByRole('button', { name: 'Select Column' }); + this.selectConditionButton = this.code.driver.page.getByRole('button', { name: 'Select Condition' }); + this.applyFilterButton = this.code.driver.page.getByRole('button', { name: 'Apply Filter' }); } /* @@ -103,35 +103,19 @@ export class DataExplorer { * Add a filter to the data explorer. Only works for a single filter at the moment. */ async addFilter(columnName: string, functionText: string, filterValue: string) { + await this.addFilterButton.click(); - await this.code.driver.page.locator(ADD_FILTER_BUTTON).click(); - - // worakaround for column being set incorrectly - await expect(async () => { - try { - await this.code.driver.page.locator(COLUMN_SELECTOR).click(); - const columnText = `${columnName}\n`; - await this.code.driver.page.locator(COLUMN_INPUT).fill(columnText); - await this.code.driver.page.locator(COLUMN_SELECTOR_CELL).click(); - const checkValue = await this.code.driver.page.locator(COLUMN_SELECTOR).textContent(); - expect(checkValue).toBe(columnName); - } catch (e) { - await this.code.driver.page.keyboard.press('Escape'); - throw e; - } - }).toPass({ timeout: 30000 }); - - - await this.code.driver.page.locator(FUNCTION_SELECTOR).click(); - - // note that base Microsoft funtionality does not work with "has text" type selection - const equalTo = this.code.driver.page.locator(`${OVERLAY_BUTTON} div:has-text("${functionText}")`); - await equalTo.click(); + // select column + await this.selectColumnButton.click(); + await this.code.driver.page.getByRole('button', { name: columnName }).click(); - const filterValueText = `${filterValue}\n`; - await this.code.driver.page.locator(FILTER_SELECTOR).fill(filterValueText); + // select condition + await this.selectConditionButton.click(); + await this.code.driver.page.getByRole('button', { name: functionText, exact: true }).click(); - await this.code.driver.page.locator(APPLY_FILTER).click(); + // enter value + await this.code.driver.page.getByRole('textbox', { name: 'value' }).fill(filterValue); + await this.applyFilterButton.click(); } async getDataExplorerStatusBarText(): Promise {