mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: add search for applications
This commit is contained in:
		
							parent
							
								
									d88435f0c5
								
							
						
					
					
						commit
						130110f5a4
					
				| @ -2,6 +2,7 @@ import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { ProgressBar, Card, CardText, Icon } from 'react-mdl'; | ||||
| import { AppsLinkList, styles as commonStyles } from '../common'; | ||||
| import SearchField from '../common/search-field'; | ||||
| 
 | ||||
| const Empty = () => ( | ||||
|     <React.Fragment> | ||||
| @ -22,6 +23,8 @@ class ClientStrategies extends Component { | ||||
|     static propTypes = { | ||||
|         applications: PropTypes.array, | ||||
|         fetchAll: PropTypes.func.isRequired, | ||||
|         settings: PropTypes.object.isRequired, | ||||
|         updateSetting: PropTypes.func.isRequired, | ||||
|     }; | ||||
| 
 | ||||
|     componentDidMount() { | ||||
| @ -35,9 +38,17 @@ class ClientStrategies extends Component { | ||||
|             return <ProgressBar indeterminate />; | ||||
|         } | ||||
|         return ( | ||||
|             <Card shadow={0} className={commonStyles.fullwidth}> | ||||
|                 {applications.length > 0 ? <AppsLinkList apps={applications} /> : <Empty />} | ||||
|             </Card> | ||||
|             <div> | ||||
|                 <div className={commonStyles.toolbar}> | ||||
|                     <SearchField | ||||
|                         value={this.props.settings.filter} | ||||
|                         updateValue={this.props.updateSetting.bind(this, 'filter')} | ||||
|                     /> | ||||
|                 </div> | ||||
|                 <Card shadow={0} className={commonStyles.fullwidth}> | ||||
|                     {applications.length > 0 ? <AppsLinkList apps={applications} /> : <Empty />} | ||||
|                 </Card> | ||||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,21 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import ApplicationList from './application-list-component'; | ||||
| import { fetchAll } from './../../store/application/actions'; | ||||
| import { updateSettingForGroup } from '../../store/settings/actions'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ applications: state.applications.get('list').toJS() }); | ||||
| const mapStateToProps = state => { | ||||
|     const applications = state.applications.get('list').toJS(); | ||||
|     const settings = state.settings.toJS().application || {}; | ||||
| 
 | ||||
| const Container = connect(mapStateToProps, { fetchAll })(ApplicationList); | ||||
|     const regex = new RegExp(settings.filter, 'i'); | ||||
| 
 | ||||
|     return { | ||||
|         applications: settings.filter ? applications.filter(a => regex.test(a.appName)) : applications, | ||||
|         settings, | ||||
|     }; | ||||
| }; | ||||
| const mapDispatchToProps = { fetchAll, updateSetting: updateSettingForGroup('application') }; | ||||
| 
 | ||||
| const Container = connect(mapStateToProps, mapDispatchToProps)(ApplicationList); | ||||
| 
 | ||||
| export default Container; | ||||
|  | ||||
| @ -85,4 +85,17 @@ | ||||
| .toggleName { | ||||
|     color: #37474f !important; | ||||
|     font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| .toolbar { | ||||
|     position: relative; | ||||
|     padding: 0 24px 16px 24px; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .toolbarButton { | ||||
|     position: absolute; | ||||
|     top: 56px; | ||||
|     right: 24px; | ||||
|     z-index: 2; | ||||
| } | ||||
| @ -11,14 +11,14 @@ export const shorten = (str, len = 50) => (str && str.length > len ? `${str.subs | ||||
| export const AppsLinkList = ({ apps }) => ( | ||||
|     <List> | ||||
|         {apps.length > 0 && | ||||
|             apps.map(({ appName, description = '-', icon }) => ( | ||||
|             apps.map(({ appName, description, icon }) => ( | ||||
|                 <ListItem twoLine key={appName}> | ||||
|                     <span className="mdl-list__item-primary-content" style={{ minWidth: 0 }}> | ||||
|                         <Icon name={icon || 'apps'} className="mdl-list__item-avatar" /> | ||||
|                         <Link to={`/applications/${appName}`} className={[styles.listLink, styles.truncate].join(' ')}> | ||||
|                             {appName} | ||||
|                             <span className={['mdl-list__item-sub-title', styles.truncate].join(' ')}> | ||||
|                                 {description} | ||||
|                                 {description || 'No descriptionn'} | ||||
|                             </span> | ||||
|                         </Link> | ||||
|                     </span> | ||||
|  | ||||
							
								
								
									
										50
									
								
								frontend/src/component/common/search-field.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								frontend/src/component/common/search-field.jsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { debounce } from 'debounce'; | ||||
| import { FABButton, Icon, Textfield } from 'react-mdl'; | ||||
| 
 | ||||
| function SearchField({ value, updateValue }) { | ||||
|     const [localValue, setLocalValue] = useState(value); | ||||
|     const debounceUpdateValue = debounce(updateValue, 500); | ||||
| 
 | ||||
|     const handleCange = e => { | ||||
|         e.preventDefault(); | ||||
|         const v = e.target.value || ''; | ||||
|         setLocalValue(v); | ||||
|         debounceUpdateValue(v); | ||||
|     }; | ||||
| 
 | ||||
|     const handleKeyPress = e => { | ||||
|         if (e.key === 'Enter') { | ||||
|             updateValue(localValue); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const updateNow = () => { | ||||
|         updateValue(localValue); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <Textfield | ||||
|                 floatingLabel | ||||
|                 value={localValue} | ||||
|                 onChange={handleCange} | ||||
|                 onBlur={updateNow} | ||||
|                 onKeyPress={handleKeyPress} | ||||
|                 label="Search" | ||||
|                 style={{ width: '500px', maxWidth: '80%' }} | ||||
|             /> | ||||
|             <FABButton mini className={'mdl-cell--hide-phone'}> | ||||
|                 <Icon name="search" /> | ||||
|             </FABButton> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| SearchField.propTypes = { | ||||
|     value: PropTypes.string.isRequired, | ||||
|     updateValue: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default SearchField; | ||||
| @ -5,16 +5,29 @@ exports[`renders correctly with one feature 1`] = ` | ||||
|   <div | ||||
|     className="toolbar" | ||||
|   > | ||||
|     <react-mdl-Textfield | ||||
|       floatingLabel={true} | ||||
|       label="Search" | ||||
|       onChange={[Function]} | ||||
|       style={ | ||||
|         Object { | ||||
|           "width": "100%", | ||||
|     <div> | ||||
|       <react-mdl-Textfield | ||||
|         floatingLabel={true} | ||||
|         label="Search" | ||||
|         onBlur={[Function]} | ||||
|         onChange={[Function]} | ||||
|         onKeyPress={[Function]} | ||||
|         style={ | ||||
|           Object { | ||||
|             "maxWidth": "80%", | ||||
|             "width": "500px", | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     /> | ||||
|       /> | ||||
|       <react-mdl-FABButton | ||||
|         className="mdl-cell--hide-phone" | ||||
|         mini={true} | ||||
|       > | ||||
|         <react-mdl-Icon | ||||
|           name="search" | ||||
|         /> | ||||
|       </react-mdl-FABButton> | ||||
|     </div> | ||||
|     <a | ||||
|       className="toolbarButton" | ||||
|       href="/features/create" | ||||
| @ -190,16 +203,29 @@ exports[`renders correctly with one feature without permissions 1`] = ` | ||||
|   <div | ||||
|     className="toolbar" | ||||
|   > | ||||
|     <react-mdl-Textfield | ||||
|       floatingLabel={true} | ||||
|       label="Search" | ||||
|       onChange={[Function]} | ||||
|       style={ | ||||
|         Object { | ||||
|           "width": "100%", | ||||
|     <div> | ||||
|       <react-mdl-Textfield | ||||
|         floatingLabel={true} | ||||
|         label="Search" | ||||
|         onBlur={[Function]} | ||||
|         onChange={[Function]} | ||||
|         onKeyPress={[Function]} | ||||
|         style={ | ||||
|           Object { | ||||
|             "maxWidth": "80%", | ||||
|             "width": "500px", | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     /> | ||||
|       /> | ||||
|       <react-mdl-FABButton | ||||
|         className="mdl-cell--hide-phone" | ||||
|         mini={true} | ||||
|       > | ||||
|         <react-mdl-Icon | ||||
|           name="search" | ||||
|         /> | ||||
|       </react-mdl-FABButton> | ||||
|     </div> | ||||
|      | ||||
|   </div> | ||||
|   <react-mdl-Card | ||||
|  | ||||
| @ -1,15 +1,3 @@ | ||||
| .toolbar { | ||||
|     position: relative; | ||||
|     padding: 0 104px 16px 24px; | ||||
| } | ||||
| 
 | ||||
| .toolbarButton { | ||||
|     position: absolute; | ||||
|     top: 56px; | ||||
|     right: 24px; | ||||
|     z-index: 2; | ||||
| } | ||||
| 
 | ||||
| .listItemMetric { | ||||
|     width: 40px; | ||||
|     flex-shrink: 0; | ||||
|  | ||||
| @ -2,9 +2,10 @@ import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { debounce } from 'debounce'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } from 'react-mdl'; | ||||
| import { Icon, FABButton, Menu, MenuItem, Card, CardActions, List } from 'react-mdl'; | ||||
| import Feature from './feature-list-item-component'; | ||||
| import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common'; | ||||
| import SearchField from '../common/search-field'; | ||||
| import styles from './feature.scss'; | ||||
| import { CREATE_FEATURE } from '../../permissions'; | ||||
| 
 | ||||
| @ -59,18 +60,13 @@ export default class FeatureListComponent extends React.Component { | ||||
|         }); | ||||
|         return ( | ||||
|             <div> | ||||
|                 <div className={styles.toolbar}> | ||||
|                     <Textfield | ||||
|                         floatingLabel | ||||
|                         value={this.state.filter} | ||||
|                         onChange={e => { | ||||
|                             this.setFilter(e.target.value); | ||||
|                         }} | ||||
|                         label="Search" | ||||
|                         style={{ width: '100%' }} | ||||
|                 <div className={commonStyles.toolbar}> | ||||
|                     <SearchField | ||||
|                         value={this.props.settings.filter} | ||||
|                         updateValue={this.props.updateSetting.bind(this, 'filter')} | ||||
|                     /> | ||||
|                     {hasPermission(CREATE_FEATURE) ? ( | ||||
|                         <Link to="/features/create" className={styles.toolbarButton}> | ||||
|                         <Link to="/features/create" className={commonStyles.toolbarButton}> | ||||
|                             <FABButton accent title="Create feature toggle"> | ||||
|                                 <Icon name="add" /> | ||||
|                             </FABButton> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user