console: fix operations filters

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8097
GitOrigin-RevId: 33a72186a38094bbcdee85d87d0f0a05aadb1736
This commit is contained in:
Nicolas Inchauspe 2023-02-24 12:56:18 +01:00 committed by hasura-bot
parent fbab8cd755
commit 975d4ae91b
6 changed files with 249 additions and 182 deletions

View File

@ -711,50 +711,48 @@
vertical-align: middle; vertical-align: middle;
} }
.selectBox { .selectBox {
background-color: #ffffff;
font-family: 'Gudea'; font-family: 'Gudea';
font-size: 15px; font-size: 15px;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
padding: 16px 16px; padding: 16px 20px;
display: inline-block; display: flex;
flex-direction: row;
align-items: center; align-items: center;
gap: 12px;
.selectBoxRow { .selectBoxRow {
display: inline-block; display: inline-block;
} }
span { span {
min-width: 100px; display: block;
padding-right: 12px; flex-shrink: 1;
white-space: nowrap;
} }
} }
.dropDownBtn { .dropDownBtn {
min-width: 180px; min-width: 180px;
button { width: 100%;
width: 100%; border-radius: 2px;
border-radius: 2px; border: solid 1px #dddddd;
border: solid 1px #dddddd; background-color: #ffffff;
font-family: 'Gudea';
font-size: 15px;
font-weight: bold;
color: #505050;
padding: 7px 12px;
min-width: 140px;
display: flex;
align-items: center;
cursor: pointer;
position: relative;
&:focus {
outline: none;
border: solid 1px #f8c533;
}
&:hover {
border: solid 1px #f8c533;
background-color: #ffffff; background-color: #ffffff;
font-family: 'Gudea';
font-size: 15px;
font-weight: bold;
color: #505050;
padding: 7px 12px;
min-width: 140px;
display: flex;
align-items: center;
cursor: pointer;
position: relative;
img {
position: absolute;
right: 12px;
}
&:focus {
outline: none;
border: solid 1px #f8c533;
}
&:hover {
border: solid 1px #f8c533;
background-color: #ffffff;
}
} }
span { span {
padding-right: 0; padding-right: 0;
@ -787,6 +785,8 @@
/* common checkbox */ /* common checkbox */
.commonCheckBox, .commonCheckBox,
.defaultCheckBox { .defaultCheckBox {
min-width: 110px;
input[type='checkbox'] { input[type='checkbox'] {
position: absolute; // take it out of document flow position: absolute; // take it out of document flow
opacity: 0; // hide it opacity: 0; // hide it
@ -836,7 +836,7 @@
content: ''; content: '';
position: absolute; position: absolute;
left: 7px; left: 7px;
top: 3px; top: 5px;
width: 5px; width: 5px;
height: 10px; height: 10px;
border: solid white; border: solid white;

View File

@ -1,99 +0,0 @@
import React, { useState } from 'react';
import Dropdown from 'react-bootstrap/lib/Dropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import { SELECT_ALL_NAME_DISPLAY } from '../../constants';
import styles from '../../Metrics.module.scss';
import dropdown from '../../images/drop-down.svg';
const DropdownComponent = props => {
/* Accepts children which can be a single Menu Items
* or array of Menu Items
* TODO: How to validate whether the childrens are only of Menu item type?
* */
const [show, setShow] = useState(false);
const {
id,
displayValue,
options,
onChange,
selectedValues,
children,
selectAll,
} = props;
const selectedValuesList =
Object.keys(selectedValues).length === options.length - 1
? Object.assign({ ...selectedValues }, { 'Select All': true })
: selectedValues;
const handleToggle = (isOpen, event, metadata) => {
if (isOpen || metadata.source !== 'select') {
setShow(isOpen);
}
};
const handleChange = val => {
if (
val === SELECT_ALL_NAME_DISPLAY &&
Object.keys(selectedValuesList).length === options.length
) {
selectAll(id, options, true);
} else if (val === SELECT_ALL_NAME_DISPLAY) {
selectAll(id, options);
} else {
onChange(val);
}
};
const renderTitle = title => {
if (
title === 'Select All' &&
Object.keys(selectedValuesList).length === options.length
) {
return 'Unselect All';
}
return title;
};
return (
<Dropdown
open={show}
id="dropdown-custom-menu"
onToggle={handleToggle}
onSelect={e => {
handleChange(e);
}}
className={styles.dropDownBtn}
>
<Dropdown.Toggle noCaret>
{displayValue}
<img src={dropdown} alt={'Dropdown arrow'} />
</Dropdown.Toggle>
<Dropdown.Menu>
{options.map((list, index) => {
return (
<MenuItem key={displayValue + '_' + index} eventKey={list.title}>
<div className={styles.commonCheckBox}>
<input
id={id + '_' + index}
type="checkbox"
className="legacy-input-fix"
value={list.title}
onChange={() => null}
checked={selectedValuesList[list.title] ? true : false}
/>
<label
className={styles.eclipseText + ' ' + styles.fw_light}
htmlFor={id + '_' + index}
>
{renderTitle(list.title)}
</label>
</div>
</MenuItem>
);
})}
{children}
</Dropdown.Menu>
</Dropdown>
);
};
export default DropdownComponent;

View File

@ -0,0 +1,134 @@
import React, { useState, useEffect } from 'react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import clsx from 'clsx';
import { FaChevronDown } from 'react-icons/fa';
import { SELECT_ALL_NAME_DISPLAY } from '../../constants';
import styles from '../../Metrics.module.scss';
import { v4 as uuid } from 'uuid';
const DropdownComponent = (props: any) => {
/* Accepts children which can be a single Menu Items
* or array of Menu Items
* TODO: How to validate whether the childrens are only of Menu item type?
* */
const [componentId] = useState(uuid());
const [show, setShow] = useState(false);
const {
id,
displayValue,
options,
onChange,
selectedValues,
children,
selectAll,
} = props;
const selectedValuesList =
Object.keys(selectedValues).length === options.length - 1
? Object.assign({ ...selectedValues }, { 'Select All': true })
: selectedValues;
// Force dropdown content to be same width as trigger
useEffect(() => {
const component = document.getElementById(componentId);
document.documentElement.style.setProperty(
`--radix-dropdown-menu-trigger-width-${componentId}`,
component?.offsetWidth + 'px'
);
}, [displayValue, componentId]);
const handleToggle = (open: boolean) => {
setShow(open);
};
const handleChange = (val: string) => {
if (
val === SELECT_ALL_NAME_DISPLAY &&
Object.keys(selectedValuesList).length === options.length
) {
selectAll(id, options, true);
} else if (val === SELECT_ALL_NAME_DISPLAY) {
selectAll(id, options);
} else {
onChange(val);
}
};
const renderTitle = (title: string) => {
if (
title === 'Select All' &&
Object.keys(selectedValuesList).length === options.length
) {
return 'Unselect All';
}
return title;
};
return (
<DropdownMenu.Root open={show} onOpenChange={handleToggle}>
<DropdownMenu.Trigger
id={componentId}
className={clsx(
styles['dropDownBtn'],
'flex justify-between gap-3 px-3 py-2 cursor-pointer',
'font-normal block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-yellow-200 focus-visible:border-yellow-400 placeholder-gray-500'
)}
>
<div
className={
Object.keys(selectedValuesList).length > 0 ? '' : 'text-gray-500'
}
>
{displayValue}
</div>
<FaChevronDown className={clsx(show ? 'rotate-180' : 'rotate-0')} />
</DropdownMenu.Trigger>
<DropdownMenu.Content
className="border border-slate-300 rounded bg-white cursor-pointer z-50"
align="start"
style={{
minWidth: `var(--radix-dropdown-menu-trigger-width-${componentId})`,
}}
>
{options.map((list: any, index: string) => {
return (
<DropdownMenu.Item
key={displayValue + '_' + index}
className="bg-white hover:bg-slate-200 px-3 py-2 border-slate-300 border-b font-sans"
onSelect={() => handleChange(list.title as string)}
>
<div
className={clsx(
styles['commonCheckBox'],
'flex flex-row items-baseline'
)}
>
<input
id={id + '_' + index}
type="checkbox"
className="legacy-input-fix"
value={list.title}
onChange={() => {}}
checked={selectedValuesList[list?.title || ''] ? true : false}
/>
<label
className={clsx(styles['eclipseText'], styles['fw_light'])}
htmlFor={id + '_' + index}
>
{renderTitle(list.title as string)}
</label>
</div>
</DropdownMenu.Item>
);
})}
{children ? (
<div className="bg-white hover:bg-slate-200 px-3 py-2 border-slate-300 border-b list-none font-sans">
{children}
</div>
) : null}
</DropdownMenu.Content>
</DropdownMenu.Root>
);
};
export default DropdownComponent;

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import clsx from 'clsx';
/* calls the onChange configured when enter is pressed /* calls the onChange configured when enter is pressed
* and clears the current input * and clears the current input
@ -19,10 +20,14 @@ const FilterInputComponent = props => {
} }
}; };
return ( return (
<div className={styles.selectBox} id={id}> <div className={clsx(styles.selectBox)} id={id}>
<span>{filterTitle}</span> <span>{filterTitle}</span>
<input <input
className={styles.commonInput} className={clsx(
styles.commonInput,
'w-full',
'block font-normal w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-yellow-200 focus-visible:border-yellow-400 placeholder-gray-500'
)}
type="text" type="text"
placeholder={placeholder} placeholder={placeholder}
onKeyDown={handleEnterKeyPress} onKeyDown={handleEnterKeyPress}

View File

@ -1,11 +1,13 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import GenerateFilters from './GenerateFilters'; import GenerateFilters from './GenerateFilters';
import { Button } from '@hasura/console-legacy-ce'; import { Button } from '@hasura/console-legacy-ce';
import { FaChevronDown } from 'react-icons/fa';
import FilterSection from './FilterSection'; import FilterSection from './FilterSection';
import FilterBadge from './FilterBadge'; import FilterBadge from './FilterBadge';
import filter from '../../images/filter.svg'; import filter from '../../images/filter.svg';
import styles from '../../Metrics.module.scss'; import styles from '../../Metrics.module.scss';
import clsx from 'clsx';
const Filters = ({ const Filters = ({
projectId, projectId,
@ -19,7 +21,7 @@ const Filters = ({
retrieveDefaultDropdownOptions, retrieveDefaultDropdownOptions,
reset, reset,
selectAll, selectAll,
}) => { }: any) => {
const [displayFilters, setFiltersDisplay] = useState(false); const [displayFilters, setFiltersDisplay] = useState(false);
const renderSelectedFiltersCount = () => { const renderSelectedFiltersCount = () => {
return values.length > 0 ? `(${values.length})` : ''; return values.length > 0 ? `(${values.length})` : '';
@ -28,7 +30,7 @@ const Filters = ({
if (values.length > 0) { if (values.length > 0) {
return ( return (
<Button mode="destructive" onClick={reset}> <Button mode="destructive" onClick={reset}>
Remove all filters Reset all filters
</Button> </Button>
); );
} }
@ -36,8 +38,8 @@ const Filters = ({
}; };
const renderSelectedFilters = () => { const renderSelectedFilters = () => {
if (values.length > 0) { if (values.length > 0) {
return values.map((f, i) => { return values.map((f: { value: any; type: any }, i: any) => {
const composeFilterObj = o => { const composeFilterObj = (o: { [x: string]: any }) => {
const keyElements = Object.keys(o); const keyElements = Object.keys(o);
if (keyElements.length > 0) { if (keyElements.length > 0) {
return keyElements.map(k => `${k}: ${o[k]}`).join(', '); return keyElements.map(k => `${k}: ${o[k]}`).join(', ');
@ -53,7 +55,7 @@ const Filters = ({
return ( return (
<FilterBadge <FilterBadge
key={i} key={i}
text={`${getTitle(f.type)}: ${filterValue(f.value)}`} text={`${getTitle(f.type)}: ${filterValue()}`}
onClick={() => onChange(f.type, f.value)} onClick={() => onChange(f.type, f.value)}
/> />
); );
@ -69,8 +71,19 @@ const Filters = ({
return ( return (
<div className="filtersElementWrapper"> <div className="filtersElementWrapper">
<FilterSection> <FilterSection>
<div className={styles.filterBtnWrapper}> <div className={styles['filterBtnWrapper']}>
<div onClick={toggleFilters} className={styles.cursorPointer}> <button
className={clsx('cursor-pointer group mr-2')}
onClick={toggleFilters}
>
<FaChevronDown
className={clsx(
'w-8 h-8 p-2 group-hover:bg-slate-200 transition-all duration-150 rounded-full',
displayFilters ? 'rotate-180' : 'rotate-0'
)}
/>
</button>
<div onClick={toggleFilters} className={styles['cursorPointer']}>
<img <img
src={filter} src={filter}
alt="Filter" alt="Filter"
@ -81,7 +94,7 @@ const Filters = ({
height: '20px', height: '20px',
}} }}
/> />
<div className={styles.subHeader}> <div className={styles['subHeader']}>
Filters {renderSelectedFiltersCount()} Filters {renderSelectedFiltersCount()}
</div> </div>
</div> </div>

View File

@ -14,6 +14,7 @@ import FilterTypeInput from './FilterTypeInput';
import TimeRangeFilter from './TimeRangeFilter'; import TimeRangeFilter from './TimeRangeFilter';
import FilterTypeCheckbox from './FilterTypeCheckbox'; import FilterTypeCheckbox from './FilterTypeCheckbox';
import ComposeDropdownFilter from './ComposeDropdownFilter'; import ComposeDropdownFilter from './ComposeDropdownFilter';
import { DocumentNode } from 'graphql';
/* /*
* Dropdown filters are not rendered. The pattern ensures that data for * Dropdown filters are not rendered. The pattern ensures that data for
@ -40,7 +41,7 @@ const GenerateFilters = ({
filters, filters,
values, values,
selectAll, selectAll,
}) => { }: any) => {
const filtersHtml = []; const filtersHtml = [];
const [dropdownFilters, nonDropdownFilters] = splitByType( const [dropdownFilters, nonDropdownFilters] = splitByType(
filters, filters,
@ -50,47 +51,49 @@ const GenerateFilters = ({
return null; return null;
} }
const hOrderFn = compose(onChange); const hOrderFn = compose(onChange);
nonDropdownFilters.forEach((filter, i) => { nonDropdownFilters.forEach(
const { type, value } = filter; (filter: { type: any; value: any }, i: React.Key | null | undefined) => {
const onFilterChange = hOrderFn(value); const { type, value } = filter;
switch (type) { const onFilterChange = hOrderFn(value);
case FILTER_TYPE_INPUT: switch (type) {
filtersHtml.push( case FILTER_TYPE_INPUT:
<FilterTypeInput filtersHtml.push(
key={i} <FilterTypeInput
id={value} key={i}
title={getTitle(value)} id={value}
onChange={onFilterChange} title={getTitle(value)}
/> onChange={onFilterChange}
); />
break; );
case FILTER_TYPE_DROPDOWN_DEFAULT: break;
filtersHtml.push( case FILTER_TYPE_DROPDOWN_DEFAULT:
<TimeRangeFilter filtersHtml.push(
key={i} <TimeRangeFilter
id={value} key={i}
title={getTitle(value)} id={value}
onChange={onFilterChange} title={getTitle(value)}
filters={values} onChange={onFilterChange}
options={retrieveDefaultDropdownOptions(value)} filters={values}
/> options={retrieveDefaultDropdownOptions(value)}
); />
break; );
case FILTER_TYPE_CHECKBOX: break;
filtersHtml.push( case FILTER_TYPE_CHECKBOX:
<FilterTypeCheckbox filtersHtml.push(
key={i} <FilterTypeCheckbox
id={value} key={i}
title={getTitle(value)} id={value}
onChange={onFilterChange} title={getTitle(value)}
filters={values} onChange={onFilterChange}
/> filters={values}
); />
break; );
default: break;
console.error('Unsupported type'); default:
console.error('Unsupported type');
}
} }
}); );
if (dropdownFilters.length > 0) { if (dropdownFilters.length > 0) {
filtersHtml.push( filtersHtml.push(
@ -109,7 +112,18 @@ const GenerateFilters = ({
); );
} }
return filtersHtml; return (
<div
className="grid"
style={{
backgroundColor: '#f5f5f5',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gridGap: '2px',
}}
>
{filtersHtml}
</div>
);
}; };
export default GenerateFilters; export default GenerateFilters;