Skip to content

feat: Add relational filter conditions in data browser #2576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Configuring Localized Push Notifications](#configuring-localized-push-notifications)
- [Run with Docker](#run-with-docker)
- [Features](#features)
- [Data Browser](#data-browser)
- [Filters](#filters)
- [Browse as User](#browse-as-user)
- [Change Pointer Key](#change-pointer-key)
- [Limitations](#limitations)
Expand Down Expand Up @@ -815,6 +817,26 @@ If you are not familiar with Docker, ``--port 8080`` will be passed in as argume
# Features
*(The following is not a complete list of features but a work in progress to build a comprehensive feature list.)*

## Data Browser

### Filters

▶️ *Core > Browser > Filter*

The filter dialog allows to add relational filter conditions based on other classes that have a pointer to the current class.

For example, users in the `_User` class may have:

- purchases in a `Purchase` class with a `_User` pointer field
- transactions in a `Payment` class with a `_User` pointer field

A relational filter allows you filter all users who:

- purchased a specific item (in `Purchase` class)
- payed with a specific payment method (in `Payment` class)

To apply such a filter, simply go to the `_User` class and add the two required filter conditions with the `Purchase` and `Payment` classes.

## Browse as User

▶️ *Core > Browser > Browse*
Expand Down
100 changes: 60 additions & 40 deletions src/components/BrowserFilter/BrowserFilter.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ export default class BrowserFilter extends React.Component {
toggle() {
let filters = this.props.filters;
if (this.props.filters.size === 0) {
const available = Filters.availableFilters(
this.props.schema,
null,
this.state.blacklistedFilters
const available = Filters.findRelatedClasses(
this.props.className,
this.props.allClassesSchema,
this.state.blacklistedFilters,
this.state.filters
);
const field = Object.keys(available)[0];
filters = new List([new Map({ field: field, constraint: available[field][0] })]);
const { filterClass, filterField, filterConstraint } = Filters.getFilterDetails(available);
filters = new List([
new Map({ class: filterClass, field: filterField, constraint: filterConstraint }),
]);
}
this.setState(prevState => ({
open: !prevState.open,
Expand All @@ -65,14 +68,17 @@ export default class BrowserFilter extends React.Component {
}

addRow() {
const available = Filters.availableFilters(
this.props.schema,
this.state.filters,
this.state.blacklistedFilters
const available = Filters.findRelatedClasses(
this.props.className,
this.props.allClassesSchema,
this.state.blacklistedFilters,
this.state.filters
);
const field = Object.keys(available)[0];
const { filterClass, filterField, filterConstraint } = Filters.getFilterDetails(available);
this.setState(({ filters }) => ({
filters: filters.push(new Map({ field: field, constraint: available[field][0] })),
filters: filters.push(
new Map({ class: filterClass, field: filterField, constraint: filterConstraint })
),
editMode: true,
}));
}
Expand Down Expand Up @@ -125,7 +131,12 @@ export default class BrowserFilter extends React.Component {
if (this.props.filters.size) {
popoverStyle.push(styles.active);
}
const available = Filters.availableFilters(this.props.schema, this.state.filters);
const available = Filters.findRelatedClasses(
this.props.className,
this.props.allClassesSchema,
this.state.blacklistedFilters,
this.state.filters
);
popover = (
<Popover
fixed={true}
Expand Down Expand Up @@ -154,6 +165,11 @@ export default class BrowserFilter extends React.Component {
filters={this.state.filters}
onChange={filters => this.setState({ filters: filters })}
onSearch={this.apply.bind(this)}
allClasses={this.props.allClassesSchema}
allClassesSchema={Filters.findRelatedClasses(
this.props.className,
this.props.allClassesSchema
)}
renderRow={props => (
<FilterRow
{...props}
Expand Down Expand Up @@ -194,33 +210,37 @@ export default class BrowserFilter extends React.Component {
)}
{!this.state.confirmName && (
<div className={styles.footer}>
<Button
color="white"
value="Save"
width="120px"
onClick={() => this.setState({ confirmName: true })}
/>
<Button
color="white"
value="Clear"
disabled={this.state.filters.size === 0}
width="120px"
onClick={() => this.clear()}
/>
<Button
color="white"
value="Add"
disabled={Object.keys(available).length === 0}
width="120px"
onClick={() => this.addRow()}
/>
<Button
color="white"
primary={true}
value="Apply"
width="120px"
onClick={() => this.apply()}
/>
<div className={styles.btnFlex}>
<Button
color="white"
value="Save"
width="120px"
onClick={() => this.setState({ confirmName: true })}
/>
<Button
color="white"
value="Clear"
disabled={this.state.filters.size === 0}
width="120px"
onClick={() => this.clear()}
/>
</div>
<div className={styles.btnFlex}>
<Button
color="white"
value="Add"
disabled={Object.keys(available).length === 0}
width="120px"
onClick={() => this.addRow()}
/>
<Button
color="white"
primary={true}
value="Apply"
width="120px"
onClick={() => this.apply()}
/>
</div>
</div>
)}
</div>
Expand Down
21 changes: 18 additions & 3 deletions src/components/BrowserFilter/BrowserFilter.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,24 @@

.body {
position: absolute;
display: flex;
flex-direction: column;
max-height: 500px;
overflow: hidden;
top: 30px;
right: 0;
border-radius: 5px 0 5px 5px;
background: #797691;
width: 535px;
width: 685px;
font-size: 14px;
}

.footer {
background: rgba(0,0,0,0.2);
padding: 11px 0;
padding: 11px 16px;
text-align: center;

display: flex;
justify-content: space-between;
> button {
margin-right: 10px;

Expand Down Expand Up @@ -148,3 +153,13 @@
display: inline-block;
vertical-align: top;
}

.flex{
display: flex;
align-items: center;
}

.btnFlex{
display: flex;
gap: 12px;
}
48 changes: 45 additions & 3 deletions src/components/BrowserFilter/FilterRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,15 @@ function compareValue(
}

const FilterRow = ({
classes,
fields,
constraints,
compareInfo,
currentClass,
currentField,
currentConstraint,
compareTo,
onChangeClass,
onChangeField,
onChangeConstraint,
onChangeCompareTo,
Expand All @@ -119,13 +122,52 @@ const FilterRow = ({
}
}, []);

const buildSuggestions = input => {
const buildFieldSuggestions = input => {
const regex = new RegExp(input.split('').join('.*?'), 'i');
return fields.filter(f => regex.test(f));
};
const buildClassSuggestions = input => {
const regex = new RegExp(input.split('').join('.*?'), 'i');
return classes.filter(f => regex.test(f));
};

return (
<div className={styles.row}>
<div className={`${styles.row} ${styles.flex}`}>
<Autocomplete
inputStyle={{
transition: '0s background-color ease-in-out',
}}
suggestionsStyle={{
width: '140px',
maxHeight: '360px',
overflowY: 'auto',
fontSize: '14px',
background: '#343445',
borderBottomLeftRadius: '5px',
borderBottomRightRadius: '5px',
color: 'white',
cursor: 'pointer',
}}
suggestionsItemStyle={{
background: '#343445',
color: 'white',
height: '30px',
lineHeight: '30px',
borderBottom: '0px',
}}
containerStyle={{
display: 'inline-block',
width: '140px',
verticalAlign: 'top',
height: '30px',
}}
strict={true}
value={currentClass}
suggestions={classes}
onChange={onChangeClass}
buildSuggestions={buildClassSuggestions}
buildLabel={() => ''}
/>
<Autocomplete
inputStyle={{
transition: '0s background-color ease-in-out',
Expand Down Expand Up @@ -158,7 +200,7 @@ const FilterRow = ({
value={currentField}
suggestions={fields}
onChange={onChangeField}
buildSuggestions={buildSuggestions}
buildSuggestions={buildFieldSuggestions}
buildLabel={() => ''}
/>
<ChromeDropdown
Expand Down
Loading
Loading