Skip to content

Add distinct / unique filter #920

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 9 commits into from
May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ let BrowserCell = ({ type, value, hidden, width, current, onSelect, onEditChange
content = <span>&nbsp;</span>;
classes.push(styles.empty);
} else if (type === 'Pointer') {
if (value && value.__type) {
const object = new Parse.Object(value.className);
object.id = value.objectId;
value = object;
}
content = (
<a href='javascript:;' onClick={onPointerClick.bind(undefined, value)}>
<Pill value={value.id} />
</a>
);
} else if (type === 'Date') {
if (typeof value === 'object' && value.__type) {
value = new Date(value.iso);
} else if (typeof value === 'string') {
value = new Date(value);
}
content = dateStringUTC(value);
} else if (type === 'Boolean') {
content = value ? 'True' : 'False';
Expand Down
12 changes: 11 additions & 1 deletion src/components/BrowserMenu/BrowserMenu.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,19 @@ export default class BrowserMenu extends React.Component {
</Popover>
);
}
const classes = [styles.entry];
if (this.props.disabled) {
classes.push(styles.disabled);
}
let onClick = null;
if (!this.props.disabled) {
onClick = () => {
this.setState({ open: true });
};
}
return (
<div className={styles.wrap}>
<div className={styles.entry} onClick={() => this.setState({ open: true })}>
<div className={classes.join(' ')} onClick={onClick}>
<Icon name={this.props.icon} width={14} height={14} />
<span>{this.props.title}</span>
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/components/BrowserMenu/BrowserMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
fill: white;
}
}

&.disabled {
cursor: not-allowed;
color: #66637A;

&:hover svg {
fill: #66637A;
}
}
}

.title {
Expand Down
35 changes: 32 additions & 3 deletions src/dashboard/Data/Browser/Browser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class Browser extends DashboardView {
lastNote: null,

relationCount: 0,

isUnique: false,
uniqueField: null,
};

this.prefetchData = this.prefetchData.bind(this);
Expand Down Expand Up @@ -333,7 +336,21 @@ class Browser extends DashboardView {
}

query.limit(200);
const data = await query.find({ useMasterKey: true });

let promise = query.find({ useMasterKey: true });
let isUnique = false;
let uniqueField = null;
filters.forEach(async (filter) => {
if (filter.get('constraint') == 'unique') {
const field = filter.get('field');
promise = query.distinct(field);
isUnique = true;
uniqueField = field;
}
});
await this.setState({ isUnique, uniqueField });

const data = await promise;
return data;
}

Expand All @@ -347,7 +364,11 @@ class Browser extends DashboardView {
const data = await this.fetchParseData(source, filters);
var filteredCounts = { ...this.state.filteredCounts };
if (filters.size > 0) {
filteredCounts[source] = await this.fetchParseDataCount(source,filters);
if (this.state.isUnique) {
filteredCounts[source] = data.length;
} else {
filteredCounts[source] = await this.fetchParseDataCount(source, filters);
}
} else {
delete filteredCounts[source];
}
Expand All @@ -372,7 +393,7 @@ class Browser extends DashboardView {
}

fetchNextPage() {
if (!this.state.data) {
if (!this.state.data || this.state.isUnique) {
return null;
}
let className = this.props.params.className;
Expand Down Expand Up @@ -889,11 +910,17 @@ class Browser extends DashboardView {
let columns = {
objectId: { type: 'String' }
};
if (this.state.isUnique) {
columns = {};
}
let userPointers = [];
classes.get(className).forEach((field, name) => {
if (name === 'objectId') {
return;
}
if (this.state.isUnique && name !== this.state.uniqueField) {
return;
}
let info = { type: field.type };
if (field.targetClass) {
info.targetClass = field.targetClass;
Expand All @@ -916,6 +943,8 @@ class Browser extends DashboardView {
}
browser = (
<DataBrowser
isUnique={this.state.isUnique}
uniqueField={this.state.uniqueField}
count={count}
perms={this.state.clp[className]}
schema={schema}
Expand Down
79 changes: 44 additions & 35 deletions src/dashboard/Data/Browser/BrowserTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@ export default class BrowserTable extends React.Component {
</span>
{this.props.order.map(({ name, width }, j) => {
let type = this.props.columns[name].type;
let attr = attributes[name];
if (name === 'objectId') {
attr = obj.id;
} else if (name === 'ACL' && this.props.className === '_User' && !attr) {
attr = new Parse.ACL({ '*': { read: true }, [obj.id]: { read: true, write: true }});
} else if (type === 'Relation' && !attr && obj.id) {
attr = new Parse.Relation(obj, name);
attr.targetClassName = this.props.columns[name].targetClass;
let attr = obj;
if (!this.props.isUnique) {
attr = attributes[name];
if (name === 'objectId') {
attr = obj.id;
} else if (name === 'ACL' && this.props.className === '_User' && !attr) {
attr = new Parse.ACL({ '*': { read: true }, [obj.id]: { read: true, write: true }});
} else if (type === 'Relation' && !attr && obj.id) {
attr = new Parse.Relation(obj, name);
attr.targetClassName = this.props.columns[name].targetClass;
}
}
let current = this.props.current && this.props.current.row === row && this.props.current.col === j;
let hidden = false;
Expand All @@ -118,7 +121,7 @@ export default class BrowserTable extends React.Component {
<BrowserCell
key={name}
type={type}
readonly={READ_ONLY.indexOf(name) > -1}
readonly={this.props.isUnique || READ_ONLY.indexOf(name) > -1}
width={width}
current={current}
onSelect={() => this.props.setCurrent({ row: row, col: j })}
Expand Down Expand Up @@ -187,16 +190,21 @@ export default class BrowserTable extends React.Component {
if (visible) {
let { name, width } = this.props.order[this.props.current.col];
let { type, targetClass } = this.props.columns[name];
let readonly = READ_ONLY.indexOf(name) > -1;
let readonly = this.props.isUnique || READ_ONLY.indexOf(name) > -1;
if (name === 'sessionToken') {
if (this.props.className === '_User' || this.props.className === '_Session') {
readonly = true;
}
}
let obj = this.props.current.row < 0 ? this.props.newObject : this.props.data[this.props.current.row];
let value = obj.get(name);
let value = obj;
if (!this.props.isUnique) {
value = obj.get(name);
}
if (name === 'objectId') {
value = obj.id;
if (!this.props.isUnique) {
value = obj.id;
}
} else if (name === 'ACL' && this.props.className === '_User' && !value) {
value = new Parse.ACL({ '*': { read: true }, [obj.id]: { read: true, write: true }});
} else if (name === 'password' && this.props.className === '_User') {
Expand All @@ -222,27 +230,28 @@ export default class BrowserTable extends React.Component {
for (let i = 0; i < this.props.current.col; i++) {
wrapLeft += this.props.order[i].width;
}

editor = (
<Editor
top={wrapTop}
left={wrapLeft}
type={type}
targetClass={targetClass}
value={value}
readonly={readonly}
width={width}
onCommit={(newValue) => {
if (newValue !== value) {
this.props.updateRow(
this.props.current.row,
name,
newValue
);
}
this.props.setEditing(false);
}} />
);
if (!this.props.isUnique) {
editor = (
<Editor
top={wrapTop}
left={wrapLeft}
type={type}
targetClass={targetClass}
value={value}
readonly={readonly}
width={width}
onCommit={(newValue) => {
if (newValue !== value) {
this.props.updateRow(
this.props.current.row,
name,
newValue
);
}
this.props.setEditing(false);
}} />
);
}
}
}

Expand All @@ -264,7 +273,7 @@ export default class BrowserTable extends React.Component {
/>
</div>
);
} else {
} else if (!this.props.isUnique) {
addRow = (
<div className={styles.addRow}>
<a title='Add Row' onClick={this.props.onAddRow}>
Expand Down Expand Up @@ -324,7 +333,7 @@ export default class BrowserTable extends React.Component {
selectAll={this.props.selectRow.bind(null, '*')}
headers={headers}
updateOrdering={this.props.updateOrdering}
readonly={!!this.props.relation}
readonly={!!this.props.relation || !!this.props.isUnique}
handleDragDrop={this.props.handleHeaderDragDrop}
onResize={this.props.handleResize}
onAddColumn={this.props.onAddColumn}
Expand Down
20 changes: 14 additions & 6 deletions src/dashboard/Data/Browser/BrowserToolbar.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ let BrowserToolbar = ({
onChangeCLP,
onRefresh,
hidePerms,
isUnique,

enableDeleteAllRows,
enableExportClass,
enableSecurityDialog,

enableColumnManipulation,
enableClassManipulation
enableClassManipulation,
}) => {
let selectionLength = Object.keys(selection).length;
let details = [];
Expand All @@ -58,7 +60,7 @@ let BrowserToolbar = ({
}
}

if (!relation) {
if (!relation && !isUnique) {
if (perms && !hidePerms) {
let read = perms.get && perms.find && perms.get['*'] && perms.find['*'];
let write = perms.create && perms.update && perms.delete && perms.create['*'] && perms.update['*'] && perms.delete['*'];
Expand Down Expand Up @@ -93,7 +95,7 @@ let BrowserToolbar = ({
);
} else {
menu = (
<BrowserMenu title='Edit' icon='edit-solid'>
<BrowserMenu title='Edit' icon='edit-solid' disabled={isUnique}>
<MenuItem text='Add a row' onClick={onAddRow} />
{enableColumnManipulation ? <MenuItem text='Add a column' onClick={onAddColumn} /> : <noscript />}
{enableClassManipulation ? <MenuItem text='Add a class' onClick={onAddClass} /> : <noscript />}
Expand Down Expand Up @@ -129,6 +131,12 @@ let BrowserToolbar = ({
} else if (subsection.length > 30) {
subsection = subsection.substr(0, 30) + '\u2026';
}
const classes = [styles.toolbarButton];
let onClick = onAddRow;
if (isUnique) {
classes.push(styles.toolbarButtonDisabled);
onClick = null;
}
return (
<Toolbar
relation={relation}
Expand All @@ -137,7 +145,7 @@ let BrowserToolbar = ({
subsection={subsection}
details={details.join(' \u2022 ')}
>
<a className={styles.toolbarButton} onClick={onAddRow}>
<a className={classes.join(' ')} onClick={onClick}>
<Icon name='plus-solid' width={14} height={14} />
<span>Add Row</span>
</a>
Expand All @@ -155,7 +163,7 @@ let BrowserToolbar = ({
<div className={styles.toolbarSeparator} />
{enableSecurityDialog ? <SecurityDialog
setCurrent={setCurrent}
disabled={!!relation}
disabled={!!relation || !!isUnique}
perms={perms}
className={classNameForPermissionsEditor}
onChangeCLP={onChangeCLP}
Expand All @@ -166,4 +174,4 @@ let BrowserToolbar = ({
);
};

export default BrowserToolbar;
export default BrowserToolbar;
3 changes: 2 additions & 1 deletion src/dashboard/Data/Browser/DataBrowser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export default class DataBrowser extends React.Component {
current: null,
editing: false,
});
} else if (Object.keys(props.columns).length !== Object.keys(this.props.columns).length) {
} else if (Object.keys(props.columns).length !== Object.keys(this.props.columns).length
|| (props.isUnique && props.uniqueField !== this.props.uniqueField)) {
let order = ColumnPreferences.getOrder(
props.columns,
context.currentApp.applicationId,
Expand Down
17 changes: 11 additions & 6 deletions src/lib/Filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,18 @@ export const Constraints = {
field: 'Object',
composable: true
},
unique: {
name: 'unique',
field: null
},
};

export const FieldConstraints = {
'Pointer': [ 'exists', 'dne', 'eq', 'neq'],
'Boolean': [ 'exists', 'dne', 'eq' ],
'Number': [ 'exists', 'dne', 'eq', 'neq', 'lt', 'lte', 'gt', 'gte' ],
'String': [ 'exists', 'dne', 'eq', 'neq', 'starts', 'ends', 'stringContainsString' ],
'Date': [ 'exists', 'dne', 'before', 'after' ],
'Pointer': [ 'exists', 'dne', 'eq', 'neq', 'unique' ],
'Boolean': [ 'exists', 'dne', 'eq', 'unique' ],
'Number': [ 'exists', 'dne', 'eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'unique' ],
'String': [ 'exists', 'dne', 'eq', 'neq', 'starts', 'ends', 'stringContainsString', 'unique' ],
'Date': [ 'exists', 'dne', 'before', 'after', 'unique' ],
'Object': [
'exists',
'dne',
Expand All @@ -149,7 +153,8 @@ export const FieldConstraints = {
'keyGt',
'keyGte',
'keyLt',
'keyLte'
'keyLte',
'unique',
],
'Array': [
'exists',
Expand Down