Skip to content

Commit aa5c68d

Browse files
authored
feat: Add relational filter conditions in data browser (#2576)
1 parent 35eeb48 commit aa5c68d

15 files changed

+468
-102
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
6060
- [Configuring Localized Push Notifications](#configuring-localized-push-notifications)
6161
- [Run with Docker](#run-with-docker)
6262
- [Features](#features)
63+
- [Data Browser](#data-browser)
64+
- [Filters](#filters)
6365
- [Browse as User](#browse-as-user)
6466
- [Change Pointer Key](#change-pointer-key)
6567
- [Limitations](#limitations)
@@ -815,6 +817,26 @@ If you are not familiar with Docker, ``--port 8080`` will be passed in as argume
815817
# Features
816818
*(The following is not a complete list of features but a work in progress to build a comprehensive feature list.)*
817819

820+
## Data Browser
821+
822+
### Filters
823+
824+
▶️ *Core > Browser > Filter*
825+
826+
The filter dialog allows to add relational filter conditions based on other classes that have a pointer to the current class.
827+
828+
For example, users in the `_User` class may have:
829+
830+
- purchases in a `Purchase` class with a `_User` pointer field
831+
- transactions in a `Payment` class with a `_User` pointer field
832+
833+
A relational filter allows you filter all users who:
834+
835+
- purchased a specific item (in `Purchase` class)
836+
- payed with a specific payment method (in `Payment` class)
837+
838+
To apply such a filter, simply go to the `_User` class and add the two required filter conditions with the `Purchase` and `Payment` classes.
839+
818840
## Browse as User
819841

820842
▶️ *Core > Browser > Browse*

src/components/BrowserFilter/BrowserFilter.react.js

+60-40
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ export default class BrowserFilter extends React.Component {
4646
toggle() {
4747
let filters = this.props.filters;
4848
if (this.props.filters.size === 0) {
49-
const available = Filters.availableFilters(
50-
this.props.schema,
51-
null,
52-
this.state.blacklistedFilters
49+
const available = Filters.findRelatedClasses(
50+
this.props.className,
51+
this.props.allClassesSchema,
52+
this.state.blacklistedFilters,
53+
this.state.filters
5354
);
54-
const field = Object.keys(available)[0];
55-
filters = new List([new Map({ field: field, constraint: available[field][0] })]);
55+
const { filterClass, filterField, filterConstraint } = Filters.getFilterDetails(available);
56+
filters = new List([
57+
new Map({ class: filterClass, field: filterField, constraint: filterConstraint }),
58+
]);
5659
}
5760
this.setState(prevState => ({
5861
open: !prevState.open,
@@ -65,14 +68,17 @@ export default class BrowserFilter extends React.Component {
6568
}
6669

6770
addRow() {
68-
const available = Filters.availableFilters(
69-
this.props.schema,
70-
this.state.filters,
71-
this.state.blacklistedFilters
71+
const available = Filters.findRelatedClasses(
72+
this.props.className,
73+
this.props.allClassesSchema,
74+
this.state.blacklistedFilters,
75+
this.state.filters
7276
);
73-
const field = Object.keys(available)[0];
77+
const { filterClass, filterField, filterConstraint } = Filters.getFilterDetails(available);
7478
this.setState(({ filters }) => ({
75-
filters: filters.push(new Map({ field: field, constraint: available[field][0] })),
79+
filters: filters.push(
80+
new Map({ class: filterClass, field: filterField, constraint: filterConstraint })
81+
),
7682
editMode: true,
7783
}));
7884
}
@@ -125,7 +131,12 @@ export default class BrowserFilter extends React.Component {
125131
if (this.props.filters.size) {
126132
popoverStyle.push(styles.active);
127133
}
128-
const available = Filters.availableFilters(this.props.schema, this.state.filters);
134+
const available = Filters.findRelatedClasses(
135+
this.props.className,
136+
this.props.allClassesSchema,
137+
this.state.blacklistedFilters,
138+
this.state.filters
139+
);
129140
popover = (
130141
<Popover
131142
fixed={true}
@@ -154,6 +165,11 @@ export default class BrowserFilter extends React.Component {
154165
filters={this.state.filters}
155166
onChange={filters => this.setState({ filters: filters })}
156167
onSearch={this.apply.bind(this)}
168+
allClasses={this.props.allClassesSchema}
169+
allClassesSchema={Filters.findRelatedClasses(
170+
this.props.className,
171+
this.props.allClassesSchema
172+
)}
157173
renderRow={props => (
158174
<FilterRow
159175
{...props}
@@ -194,33 +210,37 @@ export default class BrowserFilter extends React.Component {
194210
)}
195211
{!this.state.confirmName && (
196212
<div className={styles.footer}>
197-
<Button
198-
color="white"
199-
value="Save"
200-
width="120px"
201-
onClick={() => this.setState({ confirmName: true })}
202-
/>
203-
<Button
204-
color="white"
205-
value="Clear"
206-
disabled={this.state.filters.size === 0}
207-
width="120px"
208-
onClick={() => this.clear()}
209-
/>
210-
<Button
211-
color="white"
212-
value="Add"
213-
disabled={Object.keys(available).length === 0}
214-
width="120px"
215-
onClick={() => this.addRow()}
216-
/>
217-
<Button
218-
color="white"
219-
primary={true}
220-
value="Apply"
221-
width="120px"
222-
onClick={() => this.apply()}
223-
/>
213+
<div className={styles.btnFlex}>
214+
<Button
215+
color="white"
216+
value="Save"
217+
width="120px"
218+
onClick={() => this.setState({ confirmName: true })}
219+
/>
220+
<Button
221+
color="white"
222+
value="Clear"
223+
disabled={this.state.filters.size === 0}
224+
width="120px"
225+
onClick={() => this.clear()}
226+
/>
227+
</div>
228+
<div className={styles.btnFlex}>
229+
<Button
230+
color="white"
231+
value="Add"
232+
disabled={Object.keys(available).length === 0}
233+
width="120px"
234+
onClick={() => this.addRow()}
235+
/>
236+
<Button
237+
color="white"
238+
primary={true}
239+
value="Apply"
240+
width="120px"
241+
onClick={() => this.apply()}
242+
/>
243+
</div>
224244
</div>
225245
)}
226246
</div>

src/components/BrowserFilter/BrowserFilter.scss

+18-3
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,24 @@
8585

8686
.body {
8787
position: absolute;
88+
display: flex;
89+
flex-direction: column;
90+
max-height: 500px;
91+
overflow: hidden;
8892
top: 30px;
8993
right: 0;
9094
border-radius: 5px 0 5px 5px;
9195
background: #797691;
92-
width: 535px;
96+
width: 685px;
9397
font-size: 14px;
9498
}
9599

96100
.footer {
97101
background: rgba(0,0,0,0.2);
98-
padding: 11px 0;
102+
padding: 11px 16px;
99103
text-align: center;
100-
104+
display: flex;
105+
justify-content: space-between;
101106
> button {
102107
margin-right: 10px;
103108

@@ -148,3 +153,13 @@
148153
display: inline-block;
149154
vertical-align: top;
150155
}
156+
157+
.flex{
158+
display: flex;
159+
align-items: center;
160+
}
161+
162+
.btnFlex{
163+
display: flex;
164+
gap: 12px;
165+
}

src/components/BrowserFilter/FilterRow.react.js

+45-3
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ function compareValue(
9898
}
9999

100100
const FilterRow = ({
101+
classes,
101102
fields,
102103
constraints,
103104
compareInfo,
105+
currentClass,
104106
currentField,
105107
currentConstraint,
106108
compareTo,
109+
onChangeClass,
107110
onChangeField,
108111
onChangeConstraint,
109112
onChangeCompareTo,
@@ -119,13 +122,52 @@ const FilterRow = ({
119122
}
120123
}, []);
121124

122-
const buildSuggestions = input => {
125+
const buildFieldSuggestions = input => {
123126
const regex = new RegExp(input.split('').join('.*?'), 'i');
124127
return fields.filter(f => regex.test(f));
125128
};
129+
const buildClassSuggestions = input => {
130+
const regex = new RegExp(input.split('').join('.*?'), 'i');
131+
return classes.filter(f => regex.test(f));
132+
};
126133

127134
return (
128-
<div className={styles.row}>
135+
<div className={`${styles.row} ${styles.flex}`}>
136+
<Autocomplete
137+
inputStyle={{
138+
transition: '0s background-color ease-in-out',
139+
}}
140+
suggestionsStyle={{
141+
width: '140px',
142+
maxHeight: '360px',
143+
overflowY: 'auto',
144+
fontSize: '14px',
145+
background: '#343445',
146+
borderBottomLeftRadius: '5px',
147+
borderBottomRightRadius: '5px',
148+
color: 'white',
149+
cursor: 'pointer',
150+
}}
151+
suggestionsItemStyle={{
152+
background: '#343445',
153+
color: 'white',
154+
height: '30px',
155+
lineHeight: '30px',
156+
borderBottom: '0px',
157+
}}
158+
containerStyle={{
159+
display: 'inline-block',
160+
width: '140px',
161+
verticalAlign: 'top',
162+
height: '30px',
163+
}}
164+
strict={true}
165+
value={currentClass}
166+
suggestions={classes}
167+
onChange={onChangeClass}
168+
buildSuggestions={buildClassSuggestions}
169+
buildLabel={() => ''}
170+
/>
129171
<Autocomplete
130172
inputStyle={{
131173
transition: '0s background-color ease-in-out',
@@ -158,7 +200,7 @@ const FilterRow = ({
158200
value={currentField}
159201
suggestions={fields}
160202
onChange={onChangeField}
161-
buildSuggestions={buildSuggestions}
203+
buildSuggestions={buildFieldSuggestions}
162204
buildLabel={() => ''}
163205
/>
164206
<ChromeDropdown

0 commit comments

Comments
 (0)