From: Daniel Kutyła Date: Thu, 16 Dec 2021 16:11:03 +0000 (+0100) Subject: Merge branch '17579-Clear-table-filter-when-changing-the-project' into main X-Git-Tag: 2.4.0~23 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/fc84a3f3932af503d3afd04a58af52270c8fc3b6?hp=97622f7e87cccdd98dfc748913ed8791c862bd4a Merge branch '17579-Clear-table-filter-when-changing-the-project' into main closes #17579 Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js index bd211b1a..82a26cef 100644 --- a/cypress/integration/collection.spec.js +++ b/cypress/integration/collection.spec.js @@ -945,7 +945,8 @@ describe('Collection panel tests', function () { cy.get('[data-cy=form-submit-btn]').click(); - cy.get('button[aria-label=Remove]').click({ multiple: true }); + cy.get('button[aria-label=Remove]').should('exist'); + cy.get('button[aria-label=Remove]').click({ multiple: true, force: true }); cy.get('[data-cy=form-submit-btn]').should('not.exist'); }); diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js index 1c175952..b3d6bbed 100644 --- a/cypress/integration/project.spec.js +++ b/cypress/integration/project.spec.js @@ -194,4 +194,23 @@ describe('Project tests', function() { cy.contains(testRootProject.uuid).should('exist'); }); }); + + it('clears search input when changing project', () => { + cy.createGroup(activeUser.token, { + name: `Test root project ${Math.floor(Math.random() * 999999)}`, + group_class: 'project', + }).as('testProject1'); + + cy.getAll('@testProject1').then(function([testProject1]) { + cy.loginAs(activeUser); + + cy.get('[data-cy=side-panel-tree]').contains(testProject1.name).click(); + + cy.get('[data-cy=search-input] input').type('test123'); + + cy.get('[data-cy=side-panel-tree]').contains('Projects').click(); + + cy.get('[data-cy=search-input] input').should('not.have.value', 'test123'); + }); + }); }); \ No newline at end of file diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx index e33b7df0..1ef6b5c9 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -462,7 +462,7 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
1 ? classes.searchWrapper : classes.searchWrapperHidden}> - +
{ @@ -509,7 +509,7 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
- +
{ isWritable && diff --git a/src/components/collection-panel-files/collection-panel-files2.test.tsx b/src/components/collection-panel-files/collection-panel-files2.test.tsx deleted file mode 100644 index 4d8b8150..00000000 --- a/src/components/collection-panel-files/collection-panel-files2.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import React from "react"; -import { configure, shallow, mount } from "enzyme"; -import { WithStyles } from "@material-ui/core"; -import Adapter from "enzyme-adapter-react-16"; -import { TreeItem, TreeItemStatus } from '../tree/tree'; -import { FileTreeData } from '../file-tree/file-tree-data'; -import { CollectionFileType } from "../../models/collection-file"; -import { CollectionPanelFilesComponent, CollectionPanelFilesProps, CssRules } from './collection-panel-files2'; -import { SearchInput } from '../search-input/search-input'; - -configure({ adapter: new Adapter() }); - -jest.mock('components/file-tree/file-tree', () => ({ - FileTree: () => 'FileTree', -})); - -describe('', () => { - let props: CollectionPanelFilesProps & WithStyles; - - beforeEach(() => { - props = { - classes: {} as Record, - items: [], - isWritable: true, - isLoading: false, - tooManyFiles: false, - onUploadDataClick: jest.fn(), - onSearchChange: jest.fn(), - onItemMenuOpen: jest.fn(), - onOptionsMenuOpen: jest.fn(), - onSelectionToggle: jest.fn(), - onCollapseToggle: jest.fn(), - onFileClick: jest.fn(), - loadFilesFunc: jest.fn(), - currentItemUuid: '', - }; - }); - - it('renders properly', () => { - // when - const wrapper = shallow(); - - // then - expect(wrapper).not.toBeUndefined(); - }); - - it('filters out files', () => { - // given - const searchPhrase = 'test'; - const items: Array> = [ - { - data: { - url: '', - type: CollectionFileType.DIRECTORY, - name: 'test', - }, - id: '1', - open: true, - active: true, - status: TreeItemStatus.LOADED, - }, - { - data: { - url: '', - type: CollectionFileType.FILE, - name: 'test123', - }, - id: '2', - open: true, - active: true, - status: TreeItemStatus.LOADED, - }, - { - data: { - url: '', - type: CollectionFileType.FILE, - name: 'another-file', - }, - id: '3', - open: true, - active: true, - status: TreeItemStatus.LOADED, - } - ]; - - // setup - props.items = items; - const wrapper = mount(); - wrapper.find(SearchInput).simulate('change', { target: { value: searchPhrase } }); - - // when - setTimeout(() => { // we have to use set timeout because of the debounce - expect(wrapper.find('FileTree').prop('items')) - .toEqual([ - { - data: { url: '', type: 'directory', name: 'test' }, - id: '1', - open: true, - active: true, - status: 'loaded' - }, - { - data: { url: '', type: 'file', name: 'test123' }, - id: '2', - open: true, - active: true, - status: 'loaded' - } - ]); - }, 0); - }); -}); \ No newline at end of file diff --git a/src/components/collection-panel-files/collection-panel-files2.tsx b/src/components/collection-panel-files/collection-panel-files2.tsx deleted file mode 100644 index 41182482..00000000 --- a/src/components/collection-panel-files/collection-panel-files2.tsx +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import React from 'react'; -import { TreeItem, TreeItemStatus } from 'components/tree/tree'; -import { FileTreeData } from 'components/file-tree/file-tree-data'; -import { FileTree } from 'components/file-tree/file-tree'; -import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, Card, Button, Tooltip, CircularProgress } from '@material-ui/core'; -import { CustomizeTableIcon } from 'components/icon/icon'; -import { DownloadIcon } from 'components/icon/icon'; -import { SearchInput } from '../search-input/search-input'; - -export interface CollectionPanelFilesProps { - items: Array>; - isWritable: boolean; - isLoading: boolean; - tooManyFiles: boolean; - onUploadDataClick: () => void; - onSearchChange: (searchValue: string) => void; - onItemMenuOpen: (event: React.MouseEvent, item: TreeItem, isWritable: boolean) => void; - onOptionsMenuOpen: (event: React.MouseEvent, isWritable: boolean) => void; - onSelectionToggle: (event: React.MouseEvent, item: TreeItem) => void; - onCollapseToggle: (id: string, status: TreeItemStatus) => void; - onFileClick: (id: string) => void; - loadFilesFunc: () => void; - currentItemUuid?: string; -} - -export type CssRules = 'root' | 'cardSubheader' | 'nameHeader' | 'fileSizeHeader' | 'uploadIcon' | 'button' | 'centeredLabel' | 'cardHeaderContent' | 'cardHeaderContentTitle'; - -const styles: StyleRulesCallback = theme => ({ - root: { - paddingBottom: theme.spacing.unit, - height: '100%' - }, - cardSubheader: { - paddingTop: 0, - paddingBottom: 0, - minHeight: 8 * theme.spacing.unit, - }, - cardHeaderContent: { - display: 'flex', - paddingRight: 2 * theme.spacing.unit, - justifyContent: 'space-between', - }, - cardHeaderContentTitle: { - paddingLeft: theme.spacing.unit, - paddingTop: 2 * theme.spacing.unit, - paddingRight: 2 * theme.spacing.unit, - }, - nameHeader: { - marginLeft: '75px' - }, - fileSizeHeader: { - marginRight: '65px' - }, - uploadIcon: { - transform: 'rotate(180deg)' - }, - button: { - marginRight: -theme.spacing.unit, - marginTop: '8px' - }, - centeredLabel: { - fontSize: '0.875rem', - textAlign: 'center' - }, -}); - -export const CollectionPanelFilesComponent = ({ onItemMenuOpen, onSearchChange, onOptionsMenuOpen, onUploadDataClick, classes, - isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles) => { - const { useState, useEffect } = React; - const [searchValue, setSearchValue] = useState(''); - - useEffect(() => { - onSearchChange(searchValue); - }, [onSearchChange, searchValue]); - - return ( - - Files - -
- } - className={classes.cardSubheader} - classes={{ action: classes.button }} - action={<> - {isWritable && - } - {!tooManyFiles && - - onOptionsMenuOpen(ev, isWritable)}> - - - } - - } /> - {tooManyFiles - ?
- File listing may take some time, please click to browse: -
- : <> - - - Name - - - File size - - - {isLoading - ?
- :
- onItemMenuOpen(ev, item, isWritable)} - {...treeProps} />
} - - } - ); -}; - -export const CollectionPanelFiles = withStyles(styles)(CollectionPanelFilesComponent); diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index d3d26708..05125f12 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -93,11 +93,13 @@ type DataExplorerProps = DataExplorerDataProps & export const DataExplorer = withStyles(styles)( class DataExplorerGeneric extends React.Component> { + componentDidMount() { if (this.props.onSetColumns) { this.props.onSetColumns(this.props.columns); } } + render() { const { columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey, @@ -107,6 +109,7 @@ export const DataExplorer = withStyles(styles)( paperKey, fetchMode, currentItemUuid, title, doHidePanel, doMaximizePanel, panelName, panelMaximized } = this.props; + return {title && {title}} @@ -116,6 +119,7 @@ export const DataExplorer = withStyles(styles)( {!hideSearchInput && } {actions} diff --git a/src/components/search-input/search-input.test.tsx b/src/components/search-input/search-input.test.tsx index 90c52b76..c57d3608 100644 --- a/src/components/search-input/search-input.test.tsx +++ b/src/components/search-input/search-input.test.tsx @@ -21,20 +21,20 @@ describe("", () => { describe("on submit", () => { it("calls onSearch with initial value passed via props", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("form").simulate("submit"); expect(onSearch).toBeCalledWith("initial value"); }); it("calls onSearch with current value", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); searchInput.find("form").simulate("submit"); expect(onSearch).toBeCalledWith("current value"); }); it("calls onSearch with new value passed via props", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); searchInput.setProps({value: "new value"}); searchInput.find("form").simulate("submit"); @@ -42,7 +42,7 @@ describe("", () => { }); it("cancels timeout set on input value change", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); searchInput.find("form").simulate("submit"); jest.runTimersToTime(1000); @@ -54,7 +54,7 @@ describe("", () => { describe("on input value change", () => { it("calls onSearch after default timeout", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); expect(onSearch).not.toBeCalled(); jest.runTimersToTime(DEFAULT_SEARCH_DEBOUNCE); @@ -62,7 +62,7 @@ describe("", () => { }); it("calls onSearch after the time specified in props has passed", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); jest.runTimersToTime(1000); expect(onSearch).not.toBeCalled(); @@ -71,7 +71,7 @@ describe("", () => { }); it("calls onSearch only once after no change happened during the specified time", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); jest.runTimersToTime(500); searchInput.find("input").simulate("change", { target: { value: "changed value" } }); @@ -80,7 +80,7 @@ describe("", () => { }); it("calls onSearch again after the specified time has passed since previous call", () => { - const searchInput = mount(); + const searchInput = mount(); searchInput.find("input").simulate("change", { target: { value: "current value" } }); jest.runTimersToTime(500); searchInput.find("input").simulate("change", { target: { value: "intermediate value" } }); @@ -95,4 +95,14 @@ describe("", () => { }); + describe("on input target change", () => { + it("clears the input value on selfClearProp change", () => { + const searchInput = mount(); + searchInput.setProps({ selfClearProp: 'aaa' }); + jest.runTimersToTime(1000); + expect(onSearch).toBeCalledWith(""); + expect(onSearch).toHaveBeenCalledTimes(1); + }); + }); + }); diff --git a/src/components/search-input/search-input.tsx b/src/components/search-input/search-input.tsx index 5d5a9a22..50338f40 100644 --- a/src/components/search-input/search-input.tsx +++ b/src/components/search-input/search-input.tsx @@ -35,6 +35,7 @@ const styles: StyleRulesCallback = theme => { interface SearchInputDataProps { value: string; label?: string; + selfClearProp: string; } interface SearchInputActionProps { @@ -47,6 +48,7 @@ type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyl interface SearchInputState { value: string; label: string; + selfClearProp: string; } export const DEFAULT_SEARCH_DEBOUNCE = 1000; @@ -55,7 +57,8 @@ export const SearchInput = withStyles(styles)( class extends React.Component { state: SearchInputState = { value: "", - label: "" + label: "", + selfClearProp: "" }; timeout: number; @@ -66,6 +69,7 @@ export const SearchInput = withStyles(styles)( {this.state.label} - +