From d29e4a907e4c7d423f17da9e435ae57cc644696e Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Mon, 30 Sep 2019 14:26:30 -0500 Subject: [PATCH 01/69] modify search styling --- web/src/app/util/Search.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/web/src/app/util/Search.js b/web/src/app/util/Search.js index 6fcf1129dd..9bf206b0a2 100644 --- a/web/src/app/util/Search.js +++ b/web/src/app/util/Search.js @@ -1,24 +1,28 @@ import React, { useState, useEffect } from 'react' +import { makeStyles } from '@material-ui/core' import AppBar from '@material-ui/core/AppBar' import Hidden from '@material-ui/core/Hidden' import IconButton from '@material-ui/core/IconButton' +import InputAdornment from '@material-ui/core/InputAdornment' import Slide from '@material-ui/core/Slide' import TextField from '@material-ui/core/TextField' import Toolbar from '@material-ui/core/Toolbar' import { Close as CloseIcon, Search as SearchIcon } from '@material-ui/icons' -import { styles } from '../styles/materialStyles' import { useDispatch, useSelector } from 'react-redux' import { searchSelector } from '../selectors/url' import { setURLParam } from '../actions/main' import { DEBOUNCE_DELAY } from '../config' -import { makeStyles } from '@material-ui/core' -const useStyles = makeStyles(theme => { - return { searchFieldBox: styles(theme).searchFieldBox } +const useStyles = makeStyles({ + textField: { + backgroundColor: 'white', + borderRadius: '4px', + }, }) /* - * Renders a search bar that will fix to the top right of the screen (in the app bar) + * Renders a search text field that utilizes the URL params to regulate + * what data to display * * On a mobile device the the search icon will be present, and when tapped * a new appbar will display that contains a search field to use. @@ -51,14 +55,19 @@ export default function Search() { return ( + + + ), }} placeholder='Search' + variant='outlined' + margin='dense' + hiddenLabel onChange={e => setSearch(e.target.value)} value={search} + className={classes.textField} {...extraProps} /> ) From 55868a01e9b631e6ae1665750aa286dd36ed894b Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Mon, 30 Sep 2019 14:26:49 -0500 Subject: [PATCH 02/69] remove unused style --- web/src/app/styles/materialStyles.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/src/app/styles/materialStyles.js b/web/src/app/styles/materialStyles.js index 7066ebfae8..51b26aed80 100644 --- a/web/src/app/styles/materialStyles.js +++ b/web/src/app/styles/materialStyles.js @@ -109,13 +109,6 @@ export const styles = theme => ({ top: '0.7em', right: '0.7em', }, - searchFieldBox: { - borderRadius: 4, - backgroundColor: theme.palette.common.white, - border: '1px solid #ced4da', - fontSize: 16, - padding: '10px 12px', - }, // use on grid items except the last one per page mobileGridSpacing: { marginBottom: '1em', From 8b7a478fc65d2343400f64c2ccc984f356bc8c2a Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Mon, 30 Sep 2019 14:27:21 -0500 Subject: [PATCH 03/69] allow changing icon props in filter container --- web/src/app/util/FilterContainer.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/app/util/FilterContainer.js b/web/src/app/util/FilterContainer.js index 9c825c1fd0..212e391e31 100644 --- a/web/src/app/util/FilterContainer.js +++ b/web/src/app/util/FilterContainer.js @@ -1,4 +1,5 @@ import React from 'react' +import p from 'prop-types' import { Hidden, Popover, @@ -37,6 +38,12 @@ export default class FilterContainer extends React.PureComponent { anchorEl: null, } + // todo: fill in rest + static propTypes = { + // https://material-ui.com/api/icon-button/ + iconButtonProps: p.object, + } + renderContent() { return ( @@ -67,7 +74,7 @@ export default class FilterContainer extends React.PureComponent { ) } render() { - const { classes } = this.props + const { classes, iconButtonProps } = this.props return ( From a7b211da93125a3f47e0c20518d29dc13cf071c7 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Mon, 30 Sep 2019 14:28:17 -0500 Subject: [PATCH 04/69] move search above list and add option to provide additional filters to the grid --- web/src/app/lists/PaginatedList.js | 143 +++++++++++++++++----------- web/src/app/lists/QueryList.js | 4 + web/src/app/lists/SimpleListPage.js | 19 ++-- 3 files changed, 99 insertions(+), 67 deletions(-) diff --git a/web/src/app/lists/PaginatedList.js b/web/src/app/lists/PaginatedList.js index 555b836537..90b7a97ad4 100644 --- a/web/src/app/lists/PaginatedList.js +++ b/web/src/app/lists/PaginatedList.js @@ -23,6 +23,7 @@ import { connect } from 'react-redux' import { ITEMS_PER_PAGE } from '../config' import { absURLSelector } from '../selectors/url' import ListItemIcon from '@material-ui/core/ListItemIcon' +import Search from '../util/Search' // gray boxes on load // disable overflow @@ -43,6 +44,7 @@ const styles = theme => ({ fontStyle: 'italic', }, controls: { + alignItems: 'center', [theme.breakpoints.down('sm')]: { '&:not(:first-child)': { marginBottom: '4.5em', @@ -58,49 +60,69 @@ class PaginationControls extends React.PureComponent { isLoading: p.bool, onNext: p.func, onBack: p.func, + searchFilters: p.node, + withSearch: p.bool, } render() { - const { classes, isLoading, onBack, onNext } = this.props + const { + classes, + isLoading, + onBack, + onNext, + searchFilters, + withSearch, + } = this.props return ( - - - - { - onBack() - window.scrollTo(0, 0) - }} - > - - - + + {withSearch && {searchFilters}} + {withSearch && ( - { - onNext() - window.scrollTo(0, 0) - }} - > - {isLoading && !onNext && ( - - )} - - + + )} + + { + onBack() + window.scrollTo(0, 0) + }} + > + + - + + { + onNext() + window.scrollTo(0, 0) + }} + > + {isLoading && !onNext && ( + + )} + + + + ) } } @@ -164,6 +186,8 @@ export class PaginatedList extends React.PureComponent { // provide a message to display if there are no results emptyMessage: p.string, + + searchFilters: p.node, } static defaultProps = { @@ -222,7 +246,7 @@ export class PaginatedList extends React.PureComponent { this.props.loadMore(ITEMS_PER_PAGE * 2) } - renderPaginationControls() { + renderPaginationControls(withSearch) { let onBack = null let onNext = null @@ -235,6 +259,8 @@ export class PaginatedList extends React.PureComponent { onBack={onBack} onNext={onNext} isLoading={this.isLoading()} + searchFilters={this.props.searchFilters} + withSearch={withSearch} /> ) } @@ -312,29 +338,34 @@ export class PaginatedList extends React.PureComponent { render() { const { headerNote, classes } = this.props + const withSearch = true + return ( - - - - {headerNote && ( - - - {headerNote} - - } - /> - - )} - {this.renderListItems()} - - + + {this.renderPaginationControls(withSearch)} + + + + {headerNote && ( + + + {headerNote} + + } + /> + + )} + {this.renderListItems()} + + + + {this.renderPaginationControls()} - {this.renderPaginationControls()} ) } diff --git a/web/src/app/lists/QueryList.js b/web/src/app/lists/QueryList.js index a074bb205b..aca91da395 100644 --- a/web/src/app/lists/QueryList.js +++ b/web/src/app/lists/QueryList.js @@ -46,6 +46,9 @@ export default class QueryList extends React.PureComponent { // provided by redux search: p.string, routeKey: p.string, + + // filters additional to search, to be rendered to the left of the search text field + searchFilters: p.node, } static defaultProps = { @@ -103,6 +106,7 @@ export default class QueryList extends React.PureComponent { items={items} loadMore={loadMore} isLoading={loading} + searchFilters={this.props.searchFilters} /> ) } diff --git a/web/src/app/lists/SimpleListPage.js b/web/src/app/lists/SimpleListPage.js index 6b43cc4881..5b410d8bb2 100644 --- a/web/src/app/lists/SimpleListPage.js +++ b/web/src/app/lists/SimpleListPage.js @@ -1,10 +1,6 @@ import React from 'react' -import QueryList from './QueryList' - -import PageActions from '../util/PageActions' import p from 'prop-types' - -import Search from '../util/Search' +import QueryList from './QueryList' import CreateFAB from './CreateFAB' export default class SimpleListPage extends React.PureComponent { @@ -16,17 +12,18 @@ export default class SimpleListPage extends React.PureComponent { createForm: p.element, createLabel: p.string, queryProps: p.object, + searchFilters: p.node, } render() { - const { createForm, createLabel, ...queryProps } = this.props + const { createForm, createLabel, searchFilters, ...queryProps } = this.props return ( - - - - - + {createForm && ( Date: Mon, 30 Sep 2019 14:30:03 -0500 Subject: [PATCH 05/69] rename filter icon --- web/src/app/util/FilterContainer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/util/FilterContainer.js b/web/src/app/util/FilterContainer.js index 212e391e31..24663c8a5b 100644 --- a/web/src/app/util/FilterContainer.js +++ b/web/src/app/util/FilterContainer.js @@ -9,7 +9,7 @@ import { Grid, Button, } from '@material-ui/core' -import { FilterList } from '@material-ui/icons' +import { FilterList as FilterIcon } from '@material-ui/icons' const style = theme => { return { @@ -88,7 +88,7 @@ export default class FilterContainer extends React.PureComponent { aria-expanded={Boolean(this.state.anchorEl)} {...iconButtonProps} > - + Date: Tue, 1 Oct 2019 10:25:41 -0500 Subject: [PATCH 06/69] update react-select --- web/src/package.json | 2 +- web/src/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/src/package.json b/web/src/package.json index a899afeabf..221e907159 100644 --- a/web/src/package.json +++ b/web/src/package.json @@ -99,7 +99,7 @@ "react-markdown": "4.2.2", "react-redux": "7.1.1", "react-router-dom": "5.0.1", - "react-select": "3.0.4", + "react-select": "3.0.7", "redux-thunk": "2.3.0", "reselect": "4.0.0" }, diff --git a/web/src/yarn.lock b/web/src/yarn.lock index 84d75fab63..528a1219be 100644 --- a/web/src/yarn.lock +++ b/web/src/yarn.lock @@ -9044,10 +9044,10 @@ react-infinite-scroll-component@4.5.3: resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-4.5.3.tgz#008c2ec358628b490117ffc4aa6ce6982b26f8be" integrity sha512-8O0PIeYZx0xFVS1ChLlLl/1obn64vylzXeheLsm+t0qUibmet7U6kDaKFg6jVRQJwDikWBTcyqEFFsxrbFCO5w== -react-input-autosize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" - integrity sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA== +react-input-autosize@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2" + integrity sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw== dependencies: prop-types "^15.5.8" @@ -9142,10 +9142,10 @@ react-router@5.0.1: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-select@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.0.4.tgz#16bde37c24fd4f6444914d4681e78f15ffbc86d3" - integrity sha512-fbVISKa/lSUlLsltuatfUiKcWCNvdLXxFFyrzVQCBUsjxJZH/m7UMPdw/ywmRixAmwXAP++MdbNNZypOsiDEfA== +react-select@3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.0.7.tgz#1d5b3d33e7c2bf3f8dcbd9de9c1424f27ec44fe7" + integrity sha512-m4DKFUGvjjghxovuo4NyGltq/qPwFV5LV1Bo8wl1tUt1UQZO0Lvru1yC62wkTsittf5A+k4gyM4c+MhfEiERSQ== dependencies: "@babel/runtime" "^7.4.4" "@emotion/cache" "^10.0.9" @@ -9155,7 +9155,7 @@ react-select@3.0.4: memoize-one "^5.0.0" prop-types "^15.6.0" raf "^3.4.0" - react-input-autosize "^2.2.1" + react-input-autosize "^2.2.2" react-transition-group "^2.2.1" react-transition-group@^2.2.1: From ebb39c683e5d0022ab232cd1cf5280f9dfc7f3cf Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 10:27:17 -0500 Subject: [PATCH 07/69] adjust width on filter container such that content is always within the paper --- web/src/app/util/FilterContainer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/app/util/FilterContainer.js b/web/src/app/util/FilterContainer.js index 24663c8a5b..716cc100d4 100644 --- a/web/src/app/util/FilterContainer.js +++ b/web/src/app/util/FilterContainer.js @@ -20,10 +20,12 @@ const style = theme => { overflow: { overflow: 'visible', }, - container: { padding: 8, - [theme.breakpoints.up('md')]: { width: '17em' }, + [theme.breakpoints.up('md')]: { + width: 'fit-content', + minWidth: '17em', + }, [theme.breakpoints.down('sm')]: { width: '100%' }, }, formContainer: { From b4c23bbb320ecb939dfd6221b3c64c899785f4d7 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 13:26:18 -0500 Subject: [PATCH 08/69] adjustments to make menu items word wrap instead of get cut off --- .../app/selection/MaterialSelectComponents.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/web/src/app/selection/MaterialSelectComponents.js b/web/src/app/selection/MaterialSelectComponents.js index dfa0763e06..99d7787661 100644 --- a/web/src/app/selection/MaterialSelectComponents.js +++ b/web/src/app/selection/MaterialSelectComponents.js @@ -34,6 +34,14 @@ export const styles = theme => ({ 0.08, ), }, + listItemIcon: { + position: 'absolute', + right: 0, + }, + menuItem: { + wordBreak: 'break-word', + whiteSpace: 'pre-wrap', + }, message: { float: 'left', padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, @@ -104,15 +112,14 @@ export const Option = props => ( {props.children} {props.data.icon && ( - + {props.data.icon} )} From 1039ae7d2e5dbe12b0184d9b60e2e5c64abc0d9b Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 14:11:29 -0500 Subject: [PATCH 09/69] get service router working with additional key select for search --- web/src/app/services/ServiceRouter.js | 159 ++++++++++++++++---------- 1 file changed, 100 insertions(+), 59 deletions(-) diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index 108f907b92..cf28a40271 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -1,18 +1,21 @@ import React from 'react' +import { useDispatch, useSelector } from 'react-redux' import gql from 'graphql-tag' import { Switch, Route } from 'react-router-dom' +import Grid from '@material-ui/core/Grid' import SimpleListPage from '../lists/SimpleListPage' import ServiceDetails from './ServiceDetails' import ServiceLabelList from './ServiceLabelList' import IntegrationKeyList from './IntegrationKeyList' - +import { LabelKeySelect } from '../selection/LabelKeySelect' import { PageNotFound } from '../error-pages/Errors' - import ServiceAlerts from './components/ServiceAlerts' - import ServiceCreateDialog from './ServiceCreateDialog' import HeartbeatMonitorList from './HeartbeatMonitorList' +import FilterContainer from '../util/FilterContainer' +import { searchSelector } from '../selectors' +import { setURLParam } from '../actions' const query = gql` query servicesQuery($input: ServiceSearchOptions) { @@ -31,66 +34,104 @@ const query = gql` } ` -export default class ServiceRouter extends React.PureComponent { - renderList = () => ( - ({ - title: n.name, - subText: n.description, - url: n.id, - isFavorite: n.isFavorite, - })} - createForm={} - createLabel='Service' - /> - ) +export default function ServiceRouter() { + const searchParam = useSelector(searchSelector) // current total search string on page load + const dispatch = useDispatch() + const setSearchParam = value => dispatch(setURLParam('search', value)) - renderDetails = ({ match }) => ( - - ) - renderAlerts = ({ match }) => ( - - ) - renderKeys = ({ match }) => ( - - ) - renderHeartbeatMonitors = ({ match }) => ( - - ) - renderLabels = ({ match }) => ( - - ) + // grab key and value from the search param, if at all + let key = null + // let value = null + if (searchParam.includes('=')) { + const searchSplit = searchParam.split(/(!=|=)/) + key = searchSplit[0] + // value = searchSplit[1] + } - render() { + function renderList() { return ( - - - - - - - + ({ + title: n.name, + subText: n.description, + url: n.id, + isFavorite: n.isFavorite, + })} + createForm={} + createLabel='Service' + searchFilters={renderSearchFilters()} + /> + ) + } - - + function renderSearchFilters() { + return ( + + + + + ) } + + // update key in state and update search parameters accordingly + function onKeyChange(newValue) { + if (newValue) { + setSearchParam(newValue + '=') + } else { + setSearchParam() // clear search if clearing key + } + } + + function renderDetails({ match }) { + return + } + + function renderAlerts({ match }) { + return + } + + function renderKeys({ match }) { + return + } + + function renderHeartbeatMonitors({ match }) { + return + } + + function renderLabels({ match }) { + return + } + + return ( + + + + + + + + + + + ) } From 8a5b7de276829099ace60ac3cf580ef3b2e7fcc6 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 14:14:57 -0500 Subject: [PATCH 10/69] match icon color with search bar on desktop --- web/src/app/util/Search.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/app/util/Search.js b/web/src/app/util/Search.js index 9bf206b0a2..d9d3a1ed59 100644 --- a/web/src/app/util/Search.js +++ b/web/src/app/util/Search.js @@ -78,7 +78,6 @@ export default function Search() { setShowMobile(true)} From 5d8a13b85cbe1f952f787095709e14f8683971de Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 14:18:48 -0500 Subject: [PATCH 11/69] fmt From 2b684e997ed584e2ac2018d298d840e0df026d05 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 14:21:16 -0500 Subject: [PATCH 12/69] add reset option to filter --- web/src/app/services/ServiceRouter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index cf28a40271..d7c0c485b5 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -73,6 +73,7 @@ export default function ServiceRouter() { color: 'default', 'aria-label': 'Show Label Filters', }} + onReset={() => setSearchParam()} > Date: Tue, 1 Oct 2019 14:37:17 -0500 Subject: [PATCH 13/69] add unique values query for label value select --- graphql2/generated.go | 7 +++++ graphql2/graphqlapp/label.go | 3 ++ graphql2/models_gen.go | 11 ++++--- graphql2/schema.graphql | 1 + label/search.go | 3 +- label/store.go | 37 +++++++++++++++++++++++ web/src/app/selection/LabelValueSelect.js | 0 7 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 web/src/app/selection/LabelValueSelect.js diff --git a/graphql2/generated.go b/graphql2/generated.go index f422e66bc8..4122b64bc2 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -2566,6 +2566,7 @@ input LabelSearchOptions { after: String = "" search: String = "" uniqueKeys: Boolean = false + uniqueValues: Boolean = false omit: [ID!] } @@ -11740,6 +11741,12 @@ func (ec *executionContext) unmarshalInputLabelSearchOptions(ctx context.Context if err != nil { return it, err } + case "uniqueValues": + var err error + it.UniqueValues, err = ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } case "omit": var err error it.Omit, err = ec.unmarshalOID2ᚕstring(ctx, v) diff --git a/graphql2/graphqlapp/label.go b/graphql2/graphqlapp/label.go index dc4ff630a6..e7e5d07073 100644 --- a/graphql2/graphqlapp/label.go +++ b/graphql2/graphqlapp/label.go @@ -23,6 +23,9 @@ func (q *Query) Labels(ctx context.Context, input *graphql2.LabelSearchOptions) if input.UniqueKeys != nil { searchOpts.UniqueKeys = *input.UniqueKeys } + if input.UniqueValues != nil { + searchOpts.UniqueValues = *input.UniqueValues + } if input.After != nil && *input.After != "" { err = search.ParseCursor(*input.After, &searchOpts) if err != nil { diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index d54a49bf14..6557cc4540 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -169,11 +169,12 @@ type LabelConnection struct { } type LabelSearchOptions struct { - First *int `json:"first"` - After *string `json:"after"` - Search *string `json:"search"` - UniqueKeys *bool `json:"uniqueKeys"` - Omit []string `json:"omit"` + First *int `json:"first"` + After *string `json:"after"` + Search *string `json:"search"` + UniqueKeys *bool `json:"uniqueKeys"` + UniqueValues *bool `json:"uniqueValues"` + Omit []string `json:"omit"` } type PageInfo struct { diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index df6c3fa96d..1896283873 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -149,6 +149,7 @@ input LabelSearchOptions { after: String = "" search: String = "" uniqueKeys: Boolean = false + uniqueValues: Boolean = false omit: [ID!] } diff --git a/label/search.go b/label/search.go index fdef504a78..a8d9aaff93 100644 --- a/label/search.go +++ b/label/search.go @@ -25,7 +25,8 @@ type SearchOptions struct { Limit int `json:"-"` - UniqueKeys bool `json:"u,omitempty"` + UniqueKeys bool `json:"k,omitempty"` + UniqueValues bool `json:"v,omitempty"` } // SearchCursor is used to indicate a position in a paginated list. diff --git a/label/store.go b/label/store.go index 8503d11db1..34fe2e799a 100644 --- a/label/store.go +++ b/label/store.go @@ -28,6 +28,7 @@ type DB struct { delete *sql.Stmt findAllByService *sql.Stmt uniqueKeys *sql.Stmt + uniqueValues *sql.Stmt } // NewDB will Set a DB backend from a sql.DB. An error will be returned if statements fail to prepare. @@ -57,6 +58,11 @@ func NewDB(ctx context.Context, db *sql.DB) (*DB, error) { FROM labels ORDER BY key ASC `), + uniqueValues: p.P(` + SELECT DISTINCT (value) + FROM labels + ORDER BY key ASC + `), }, p.Err } @@ -168,3 +174,34 @@ func (db *DB) UniqueKeysTx(ctx context.Context, tx *sql.Tx) ([]string, error) { func (db *DB) UniqueKeys(ctx context.Context) ([]string, error) { return db.UniqueKeysTx(ctx, nil) } + +func (db *DB) UniqueValuesTx(ctx context.Context, tx *sql.Tx) ([]string, error) { + err := permission.LimitCheckAny(ctx, permission.System, permission.User) + if err != nil { + return nil, err + } + + stmt := db.uniqueValues + if tx != nil { + stmt = tx.StmtContext(ctx, stmt) + } + + rows, err := stmt.QueryContext(ctx) + if err != nil { + return nil, err + } + defer rows.Close() + + var keys []string + + for rows.Next() { + var k string + err = rows.Scan(&k) + if err != nil { + return nil, errors.Wrap(err, "scan row") + } + + keys = append(keys, k) + } + return keys, nil +} diff --git a/web/src/app/selection/LabelValueSelect.js b/web/src/app/selection/LabelValueSelect.js new file mode 100644 index 0000000000..e69de29bb2 From 0483a757add9a75f4d6c51b2849fe478f8ef4f91 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 15:20:31 -0500 Subject: [PATCH 14/69] ok we actually don't need this stuff --- label/store.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/label/store.go b/label/store.go index 34fe2e799a..8503d11db1 100644 --- a/label/store.go +++ b/label/store.go @@ -28,7 +28,6 @@ type DB struct { delete *sql.Stmt findAllByService *sql.Stmt uniqueKeys *sql.Stmt - uniqueValues *sql.Stmt } // NewDB will Set a DB backend from a sql.DB. An error will be returned if statements fail to prepare. @@ -58,11 +57,6 @@ func NewDB(ctx context.Context, db *sql.DB) (*DB, error) { FROM labels ORDER BY key ASC `), - uniqueValues: p.P(` - SELECT DISTINCT (value) - FROM labels - ORDER BY key ASC - `), }, p.Err } @@ -174,34 +168,3 @@ func (db *DB) UniqueKeysTx(ctx context.Context, tx *sql.Tx) ([]string, error) { func (db *DB) UniqueKeys(ctx context.Context) ([]string, error) { return db.UniqueKeysTx(ctx, nil) } - -func (db *DB) UniqueValuesTx(ctx context.Context, tx *sql.Tx) ([]string, error) { - err := permission.LimitCheckAny(ctx, permission.System, permission.User) - if err != nil { - return nil, err - } - - stmt := db.uniqueValues - if tx != nil { - stmt = tx.StmtContext(ctx, stmt) - } - - rows, err := stmt.QueryContext(ctx) - if err != nil { - return nil, err - } - defer rows.Close() - - var keys []string - - for rows.Next() { - var k string - err = rows.Scan(&k) - if err != nil { - return nil, errors.Wrap(err, "scan row") - } - - keys = append(keys, k) - } - return keys, nil -} From b9e60be1be62a6c4cb71c23fc99c8f4b8ff9c848 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 15:20:51 -0500 Subject: [PATCH 15/69] get search working properly --- graphql2/graphqlapp/label.go | 3 +++ label/search.go | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/graphql2/graphqlapp/label.go b/graphql2/graphqlapp/label.go index e7e5d07073..7d9bf47c67 100644 --- a/graphql2/graphqlapp/label.go +++ b/graphql2/graphqlapp/label.go @@ -20,6 +20,9 @@ func (q *Query) Labels(ctx context.Context, input *graphql2.LabelSearchOptions) searchOpts.Search = *input.Search } searchOpts.Omit = input.Omit + if input.UniqueKeys != nil && input.UniqueValues != nil { + return nil, validation.NewFieldError("Unique", "Only one unique identifier may be toggled on at a time.") + } if input.UniqueKeys != nil { searchOpts.UniqueKeys = *input.UniqueKeys } diff --git a/label/search.go b/label/search.go index a8d9aaff93..e84549962a 100644 --- a/label/search.go +++ b/label/search.go @@ -37,8 +37,10 @@ type SearchCursor struct { } var searchTemplate = template.Must(template.New("search").Parse(` - SELECT{{if .UniqueKeys}} distinct on (lower(key)){{end}} - key, value, tgt_service_id + SELECT + {{if .UniqueKeys}} distinct on (lower(key)){{end}} + {{if .UniqueValues}} distinct on (lower(value)){{end}} + key, value, tgt_service_id FROM labels l WHERE true {{if .Omit}} @@ -53,7 +55,12 @@ var searchTemplate = template.Must(template.New("search").Parse(` {{if .After.Key}} AND (lower(l.key) > lower(:afterKey) AND l.tgt_service_id > :afterServiceID) {{end}} - ORDER BY lower(key), tgt_service_id + ORDER BY + {{if .UniqueValues}} + lower(value) + {{else}} + lower(key) + {{end}}, tgt_service_id LIMIT {{.Limit}} `)) From d58bbcc77626bb618c31f2ca5f710c7e913dd269 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 15:21:01 -0500 Subject: [PATCH 16/69] add label value select --- web/src/app/selection/LabelValueSelect.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/web/src/app/selection/LabelValueSelect.js b/web/src/app/selection/LabelValueSelect.js index e69de29bb2..fbadbaabd5 100644 --- a/web/src/app/selection/LabelValueSelect.js +++ b/web/src/app/selection/LabelValueSelect.js @@ -0,0 +1,19 @@ +import { makeQuerySelect } from './QuerySelect' +import gql from 'graphql-tag' + +const query = gql` + query($input: LabelSearchOptions) { + labels(input: $input) { + nodes { + value + } + } + } +` + +export const LabelValueSelect = makeQuerySelect('LabelValueSelect', { + variables: { uniqueValues: true }, + defaultQueryVariables: { uniqueValues: true }, + query, + mapDataNode: ({ value }) => ({ label: value, value: value }), +}) From 51254f5ead5455fa9105cfa0b5923b621ee8764b Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 15:27:23 -0500 Subject: [PATCH 17/69] add label value select to filter --- web/src/app/services/ServiceRouter.js | 31 +++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index d7c0c485b5..ead54b8bbc 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -9,6 +9,7 @@ import ServiceDetails from './ServiceDetails' import ServiceLabelList from './ServiceLabelList' import IntegrationKeyList from './IntegrationKeyList' import { LabelKeySelect } from '../selection/LabelKeySelect' +import { LabelValueSelect } from '../selection/LabelValueSelect' import { PageNotFound } from '../error-pages/Errors' import ServiceAlerts from './components/ServiceAlerts' import ServiceCreateDialog from './ServiceCreateDialog' @@ -41,11 +42,11 @@ export default function ServiceRouter() { // grab key and value from the search param, if at all let key = null - // let value = null + let value = null if (searchParam.includes('=')) { const searchSplit = searchParam.split(/(!=|=)/) key = searchSplit[0] - // value = searchSplit[1] + value = searchSplit[2] // [1] being != or = } function renderList() { @@ -82,19 +83,37 @@ export default function ServiceRouter() { onChange={onKeyChange} /> + + + ) } - // update key in state and update search parameters accordingly - function onKeyChange(newValue) { - if (newValue) { - setSearchParam(newValue + '=') + function onKeyChange(newKey) { + if (newKey) { + setSearchParam(newKey + '=') } else { setSearchParam() // clear search if clearing key } } + function onValueChange(newValue) { + // should be disabled if empty, but just in case :) + if (!key) return + + if (newValue) { + setSearchParam(key + '=' + newValue) + } else { + setSearchParam(key + '=') + } + } + function renderDetails({ match }) { return } From cdc05406e5b9f5bbff565b4900739916a76602d4 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Tue, 1 Oct 2019 19:18:53 -0500 Subject: [PATCH 18/69] use context to render pagination controls outside of query render chain --- web/src/app/lists/PaginatedList.js | 63 +++++++----- web/src/app/lists/PaginationActions.js | 132 +++++++++++++++++++++++++ web/src/app/lists/QueryList.js | 30 ++++-- 3 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 web/src/app/lists/PaginationActions.js diff --git a/web/src/app/lists/PaginatedList.js b/web/src/app/lists/PaginatedList.js index 90b7a97ad4..5b2c57145c 100644 --- a/web/src/app/lists/PaginatedList.js +++ b/web/src/app/lists/PaginatedList.js @@ -24,6 +24,7 @@ import { ITEMS_PER_PAGE } from '../config' import { absURLSelector } from '../selectors/url' import ListItemIcon from '@material-ui/core/ListItemIcon' import Search from '../util/Search' +import PaginationActions from './PaginationActions' // gray boxes on load // disable overflow @@ -76,9 +77,9 @@ class PaginationControls extends React.PureComponent { return ( 0) - onBack = () => this.setState({ page: this.state.page - 1 }) - if (this.hasNextPage()) onNext = this.onNextPage - - return ( - - ) - } - renderNoResults() { return ( @@ -337,13 +324,37 @@ export class PaginatedList extends React.PureComponent { } render() { - const { headerNote, classes } = this.props + const { classes, headerNote, withQuery } = this.props const withSearch = true + let onBack = null + let onNext = null + + if (this.state.page > 0) + onBack = () => this.setState({ page: this.state.page - 1 }) + if (this.hasNextPage()) onNext = this.onNextPage + + let topControls = ( + + ) + + // renders in a different context outside of the query's render chain. + // this allows search to keep its focus while querying, and allows + // any additional filters to also stay open + if (withQuery) { + topControls = {topControls} + } + return ( - {this.renderPaginationControls(withSearch)} + {topControls} @@ -364,7 +375,11 @@ export class PaginatedList extends React.PureComponent { - {this.renderPaginationControls()} + ) diff --git a/web/src/app/lists/PaginationActions.js b/web/src/app/lists/PaginationActions.js new file mode 100644 index 0000000000..cb371b4e54 --- /dev/null +++ b/web/src/app/lists/PaginationActions.js @@ -0,0 +1,132 @@ +import React from 'react' +import p from 'prop-types' +import { debounce } from 'lodash-es' + +const PaginationActionsContext = React.createContext({ + actions: null, + setActions: () => {}, +}) +PaginationActionsContext.displayName = 'PaginationActionsContext' + +export class PaginationActionsContainer extends React.PureComponent { + render() { + return ( + + {({ actions }) => actions} + + ) + } +} + +export class PaginationActionsProvider extends React.PureComponent { + state = { + actions: null, + } + + _mountCount = 0 + _mounted = false + + componentDidMount() { + this._mounted = true + if (this._pending) { + this.setActions(this._pending) + this._pending = null + } + } + + componentWillUnmount() { + this._mounted = false + this._pending = false + this.setActions.cancel() + } + + _setActions = actions => { + if (!this._mounted) { + this._pending = actions + return + } + + this.setState({ actions }) + } + setActions = debounce(this._setActions) + + updateMounted = mount => { + if (mount) { + this._mountCount++ + } else { + this._mountCount-- + } + + if (this._mountCount > 1 && global.console && console.error) { + console.error( + 'PaginationActions: Found more than one component mounted within the same provider.', + ) + } + } + + render() { + return ( + + {this.props.children} + + ) + } +} + +class PaginationActionsUpdater extends React.PureComponent { + static propTypes = { + setActions: p.func.isRequired, + } + + _mounted = false + + componentDidMount() { + this._mounted = true + this.props.trackMount(true) + this.props.setActions(this.props.children) + } + + componentWillUnmount() { + this._mounted = false + this.props.trackMount(false) + this.props.setActions(null) + } + + render() { + if (this._mounted) { + this.props.setActions(this.props.children) + } + + return null + } +} + +/* + * Usage: + * + * + * + * + * + */ +export default class PaginationActions extends React.PureComponent { + render() { + return ( + + {({ setActions, trackMount }) => ( + + )} + + ) + } +} diff --git a/web/src/app/lists/QueryList.js b/web/src/app/lists/QueryList.js index aca91da395..09a8e6f08b 100644 --- a/web/src/app/lists/QueryList.js +++ b/web/src/app/lists/QueryList.js @@ -1,6 +1,6 @@ import React from 'react' import p from 'prop-types' - +import Grid from '@material-ui/core/Grid' import { PaginatedList } from './PaginatedList' import { ITEMS_PER_PAGE } from '../config' import { once } from 'lodash-es' @@ -8,6 +8,10 @@ import { connect } from 'react-redux' import { searchSelector } from '../selectors' import Query from '../util/Query' import { fieldAlias } from '../util/graphql' +import { + PaginationActionsContainer, + PaginationActionsProvider, +} from './PaginationActions' const mapStateToProps = state => ({ search: searchSelector(state), @@ -107,6 +111,7 @@ export default class QueryList extends React.PureComponent { loadMore={loadMore} isLoading={loading} searchFilters={this.props.searchFilters} + withQuery /> ) } @@ -126,13 +131,22 @@ export default class QueryList extends React.PureComponent { delete variables.input.search } return ( - + + + {/* Such that filtering and searching isn't re-rendered with the page content */} + + + + + + + ) } } From fb3bc6d59d96029141f2182daa56e94ed6406545 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 10:28:53 -0500 Subject: [PATCH 19/69] convert to Component to prevent rerendering --- web/src/app/lists/PaginatedList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/lists/PaginatedList.js b/web/src/app/lists/PaginatedList.js index 5b2c57145c..8256063255 100644 --- a/web/src/app/lists/PaginatedList.js +++ b/web/src/app/lists/PaginatedList.js @@ -56,7 +56,7 @@ const styles = theme => ({ }) @withStyles(styles) -class PaginationControls extends React.PureComponent { +class PaginationControls extends React.Component { static propTypes = { isLoading: p.bool, onNext: p.func, From 094fb8e355ca91eeda195ea0268f45e0befba6c0 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 10:31:50 -0500 Subject: [PATCH 20/69] jk --- web/src/app/lists/PaginatedList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/lists/PaginatedList.js b/web/src/app/lists/PaginatedList.js index 8256063255..5b2c57145c 100644 --- a/web/src/app/lists/PaginatedList.js +++ b/web/src/app/lists/PaginatedList.js @@ -56,7 +56,7 @@ const styles = theme => ({ }) @withStyles(styles) -class PaginationControls extends React.Component { +class PaginationControls extends React.PureComponent { static propTypes = { isLoading: p.bool, onNext: p.func, From d262ffa0bedd64e8a21b2ad081005e9ec6c60baf Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 12:30:11 -0500 Subject: [PATCH 21/69] just render the search and filter in querylist --- web/src/app/lists/PaginatedList.js | 103 ++++++------------- web/src/app/lists/PaginationActions.js | 132 ------------------------- web/src/app/lists/QueryList.js | 47 +++++---- web/src/app/util/FilterContainer.js | 4 +- 4 files changed, 61 insertions(+), 225 deletions(-) delete mode 100644 web/src/app/lists/PaginationActions.js diff --git a/web/src/app/lists/PaginatedList.js b/web/src/app/lists/PaginatedList.js index 5b2c57145c..67c3d6e1ca 100644 --- a/web/src/app/lists/PaginatedList.js +++ b/web/src/app/lists/PaginatedList.js @@ -23,8 +23,6 @@ import { connect } from 'react-redux' import { ITEMS_PER_PAGE } from '../config' import { absURLSelector } from '../selectors/url' import ListItemIcon from '@material-ui/core/ListItemIcon' -import Search from '../util/Search' -import PaginationActions from './PaginationActions' // gray boxes on load // disable overflow @@ -45,7 +43,6 @@ const styles = theme => ({ fontStyle: 'italic', }, controls: { - alignItems: 'center', [theme.breakpoints.down('sm')]: { '&:not(:first-child)': { marginBottom: '4.5em', @@ -61,19 +58,10 @@ class PaginationControls extends React.PureComponent { isLoading: p.bool, onNext: p.func, onBack: p.func, - searchFilters: p.node, - withSearch: p.bool, } render() { - const { - classes, - isLoading, - onBack, - onNext, - searchFilters, - withSearch, - } = this.props + const { classes, isLoading, onBack, onNext } = this.props return ( - {withSearch && {searchFilters}} - {withSearch && ( - - - - )} this.setState({ page: this.state.page - 1 }) if (this.hasNextPage()) onNext = this.onNextPage - let topControls = ( - - ) - - // renders in a different context outside of the query's render chain. - // this allows search to keep its focus while querying, and allows - // any additional filters to also stay open - if (withQuery) { - topControls = {topControls} - } - return ( - - - {topControls} - - - - {headerNote && ( - - - {headerNote} - - } - /> - - )} - {this.renderListItems()} - - - - + + + + + {headerNote && ( + + + {headerNote} + + } + /> + + )} + {this.renderListItems()} + + - + + ) } } diff --git a/web/src/app/lists/PaginationActions.js b/web/src/app/lists/PaginationActions.js deleted file mode 100644 index cb371b4e54..0000000000 --- a/web/src/app/lists/PaginationActions.js +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react' -import p from 'prop-types' -import { debounce } from 'lodash-es' - -const PaginationActionsContext = React.createContext({ - actions: null, - setActions: () => {}, -}) -PaginationActionsContext.displayName = 'PaginationActionsContext' - -export class PaginationActionsContainer extends React.PureComponent { - render() { - return ( - - {({ actions }) => actions} - - ) - } -} - -export class PaginationActionsProvider extends React.PureComponent { - state = { - actions: null, - } - - _mountCount = 0 - _mounted = false - - componentDidMount() { - this._mounted = true - if (this._pending) { - this.setActions(this._pending) - this._pending = null - } - } - - componentWillUnmount() { - this._mounted = false - this._pending = false - this.setActions.cancel() - } - - _setActions = actions => { - if (!this._mounted) { - this._pending = actions - return - } - - this.setState({ actions }) - } - setActions = debounce(this._setActions) - - updateMounted = mount => { - if (mount) { - this._mountCount++ - } else { - this._mountCount-- - } - - if (this._mountCount > 1 && global.console && console.error) { - console.error( - 'PaginationActions: Found more than one component mounted within the same provider.', - ) - } - } - - render() { - return ( - - {this.props.children} - - ) - } -} - -class PaginationActionsUpdater extends React.PureComponent { - static propTypes = { - setActions: p.func.isRequired, - } - - _mounted = false - - componentDidMount() { - this._mounted = true - this.props.trackMount(true) - this.props.setActions(this.props.children) - } - - componentWillUnmount() { - this._mounted = false - this.props.trackMount(false) - this.props.setActions(null) - } - - render() { - if (this._mounted) { - this.props.setActions(this.props.children) - } - - return null - } -} - -/* - * Usage: - * - * - * - * - * - */ -export default class PaginationActions extends React.PureComponent { - render() { - return ( - - {({ setActions, trackMount }) => ( - - )} - - ) - } -} diff --git a/web/src/app/lists/QueryList.js b/web/src/app/lists/QueryList.js index 09a8e6f08b..f1590e042b 100644 --- a/web/src/app/lists/QueryList.js +++ b/web/src/app/lists/QueryList.js @@ -8,10 +8,7 @@ import { connect } from 'react-redux' import { searchSelector } from '../selectors' import Query from '../util/Query' import { fieldAlias } from '../util/graphql' -import { - PaginationActionsContainer, - PaginationActionsProvider, -} from './PaginationActions' +import Search from '../util/Search' const mapStateToProps = state => ({ search: searchSelector(state), @@ -117,6 +114,7 @@ export default class QueryList extends React.PureComponent { } render() { + const { searchFilters } = this.props const { input, ...vars } = this.props.variables const variables = { @@ -127,26 +125,39 @@ export default class QueryList extends React.PureComponent { ...input, }, } + if (this.props.noSearch) { delete variables.input.search } + return ( - - - {/* Such that filtering and searching isn't re-rendered with the page content */} - - - - + + {/* Such that filtering and searching isn't re-rendered with the page content */} + + {Boolean(searchFilters) && {searchFilters}} + + - + + + + + ) } } diff --git a/web/src/app/util/FilterContainer.js b/web/src/app/util/FilterContainer.js index 716cc100d4..a6ef492f89 100644 --- a/web/src/app/util/FilterContainer.js +++ b/web/src/app/util/FilterContainer.js @@ -23,8 +23,9 @@ const style = theme => { container: { padding: 8, [theme.breakpoints.up('md')]: { - width: 'fit-content', + width: 'min-content', minWidth: '17em', + maxWidth: '22em', }, [theme.breakpoints.down('sm')]: { width: '100%' }, }, @@ -75,6 +76,7 @@ export default class FilterContainer extends React.PureComponent { ) } + render() { const { classes, iconButtonProps } = this.props return ( From 4f7d78ff8f2f8f73390d329b8f6c77d6dea220ab Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 12:42:03 -0500 Subject: [PATCH 22/69] remove spinner so search and list render together --- web/src/app/lists/QueryList.js | 1 + web/src/app/lists/SimpleListPage.js | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/web/src/app/lists/QueryList.js b/web/src/app/lists/QueryList.js index f1590e042b..574d765a49 100644 --- a/web/src/app/lists/QueryList.js +++ b/web/src/app/lists/QueryList.js @@ -153,6 +153,7 @@ export default class QueryList extends React.PureComponent { query={fieldAlias(this.props.query, 'data')} variables={variables} noPoll + noSpin notifyOnNetworkStatusChange render={this.renderContent} /> diff --git a/web/src/app/lists/SimpleListPage.js b/web/src/app/lists/SimpleListPage.js index 5b410d8bb2..6f29eede2e 100644 --- a/web/src/app/lists/SimpleListPage.js +++ b/web/src/app/lists/SimpleListPage.js @@ -19,11 +19,7 @@ export default class SimpleListPage extends React.PureComponent { const { createForm, createLabel, searchFilters, ...queryProps } = this.props return ( - + {createForm && ( Date: Wed, 2 Oct 2019 13:40:41 -0500 Subject: [PATCH 23/69] cleanup --- web/src/app/lists/QueryList.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/app/lists/QueryList.js b/web/src/app/lists/QueryList.js index 574d765a49..150dde2627 100644 --- a/web/src/app/lists/QueryList.js +++ b/web/src/app/lists/QueryList.js @@ -107,14 +107,11 @@ export default class QueryList extends React.PureComponent { items={items} loadMore={loadMore} isLoading={loading} - searchFilters={this.props.searchFilters} - withQuery /> ) } render() { - const { searchFilters } = this.props const { input, ...vars } = this.props.variables const variables = { @@ -142,7 +139,9 @@ export default class QueryList extends React.PureComponent { alignItems='center' style={{ paddingRight: 0 }} > - {Boolean(searchFilters) && {searchFilters}} + {Boolean(this.props.searchFilters) && ( + {this.props.searchFilters} + )} From 8cdc8c499181fe535fe6119604e5cf118387aea8 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 14:25:52 -0500 Subject: [PATCH 24/69] only show label values pertinent to the key selected --- web/src/app/selection/LabelValueSelect.js | 28 ++++++++++++++++++----- web/src/app/services/ServiceRouter.js | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/web/src/app/selection/LabelValueSelect.js b/web/src/app/selection/LabelValueSelect.js index fbadbaabd5..c284e5002f 100644 --- a/web/src/app/selection/LabelValueSelect.js +++ b/web/src/app/selection/LabelValueSelect.js @@ -1,3 +1,5 @@ +import React from 'react' +import p from 'prop-types' import { makeQuerySelect } from './QuerySelect' import gql from 'graphql-tag' @@ -11,9 +13,23 @@ const query = gql` } ` -export const LabelValueSelect = makeQuerySelect('LabelValueSelect', { - variables: { uniqueValues: true }, - defaultQueryVariables: { uniqueValues: true }, - query, - mapDataNode: ({ value }) => ({ label: value, value: value }), -}) +export function LabelValueSelect(props) { + const { keyValue, ...selectProps } = props + const variables = { + search: props.keyValue + '=*', + uniqueValues: true, + } + + const LabelValueSelect = makeQuerySelect('LabelValueSelect', { + variables, + defaultQueryVariables: variables, + query, + mapDataNode: ({ value }) => ({ label: value, value: value }), + }) + + return +} + +LabelValueSelect.propTypes = { + keyValue: p.string, +} diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index ead54b8bbc..380bb56bef 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -86,6 +86,7 @@ export default function ServiceRouter() { Date: Wed, 2 Oct 2019 14:40:07 -0500 Subject: [PATCH 25/69] remove duplicate search on users list page --- web/src/app/users/UserRouter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/web/src/app/users/UserRouter.js b/web/src/app/users/UserRouter.js index f3067ac4d3..50c0f88b7c 100644 --- a/web/src/app/users/UserRouter.js +++ b/web/src/app/users/UserRouter.js @@ -2,8 +2,6 @@ import React, { Component } from 'react' import { Switch, Route } from 'react-router-dom' import gql from 'graphql-tag' import { UserAvatar } from '../util/avatar/types' -import PageActions from '../util/PageActions' -import Search from '../util/Search' import QueryList from '../lists/QueryList' import UserDetails from './UserDetails' import { PageNotFound } from '../error-pages/Errors' @@ -31,9 +29,6 @@ class UserList extends React.PureComponent { render() { return ( - - - ({ From f65df546cb229e9a0d9ffef2053d1f0955a3dcfc Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 14:52:31 -0500 Subject: [PATCH 26/69] adjust filter button's aria label --- web/src/app/services/ServiceRouter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index 380bb56bef..df17ba2605 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -72,7 +72,7 @@ export default function ServiceRouter() { setSearchParam()} > From dd7bd17746e70bfea2c9935c4bf48bfaedb80d18 Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Wed, 2 Oct 2019 14:59:37 -0500 Subject: [PATCH 27/69] add attributes for testing --- web/src/app/services/ServiceRouter.js | 5 ++++- web/src/app/util/FilterContainer.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/web/src/app/services/ServiceRouter.js b/web/src/app/services/ServiceRouter.js index df17ba2605..c675e9aebf 100644 --- a/web/src/app/services/ServiceRouter.js +++ b/web/src/app/services/ServiceRouter.js @@ -71,13 +71,15 @@ export default function ServiceRouter() { return ( setSearchParam()} > {this.props.onReset && ( - + )}