- {this.props.options.map((o) => {
+
this.setState({ open: false })}
+ >
+
+ {this.props.options.map(o => {
let key = o;
let value = o;
if (o instanceof Object) {
key = o.key;
value = o.value;
}
- return
{value}
+ return (
+
+ {value}
+
+ );
})}
@@ -98,25 +115,13 @@ export default class ChromeDropdown extends React.Component {
}
ChromeDropdown.propTypes = {
- color: PropTypes.oneOf(['blue', 'purple']).describe(
- 'Determines the color of the dropdown.'
- ),
- value: PropTypes.string.isRequired.describe(
- 'The current value of the dropdown.'
- ),
+ color: PropTypes.oneOf(['blue', 'purple']).describe('Determines the color of the dropdown.'),
+ value: PropTypes.string.isRequired.describe('The current value of the dropdown.'),
options: PropTypes.array.isRequired.describe(
'An array of options available in the dropdown. Can be an array of string or array of { key, value }'
),
- onChange: PropTypes.func.isRequired.describe(
- 'A function called when the dropdown is changed.'
- ),
- width: PropTypes.string.describe(
- 'An optional width override.'
- ),
- placeholder: PropTypes.string.describe(
- 'Placeholder text used in place of default selection.'
- ),
- styles: PropTypes.object.describe(
- 'Styles override used to provide dropdown with differnt skin.'
- ),
+ onChange: PropTypes.func.isRequired.describe('A function called when the dropdown is changed.'),
+ width: PropTypes.string.describe('An optional width override.'),
+ placeholder: PropTypes.string.describe('Placeholder text used in place of default selection.'),
+ styles: PropTypes.object.describe('Styles override used to provide dropdown with differnt skin.'),
};
diff --git a/src/components/CodeEditor/CodeEditor.example.js b/src/components/CodeEditor/CodeEditor.example.js
index 87ba16cc77..4b9cb25b4b 100644
--- a/src/components/CodeEditor/CodeEditor.example.js
+++ b/src/components/CodeEditor/CodeEditor.example.js
@@ -13,8 +13,6 @@ export const component = CodeEditor;
export const demos = [
{
name: 'Simple code editor (only JS support)',
- render: () => (
-
- )
- }
+ render: () =>
,
+ },
];
diff --git a/src/components/CodeEditor/CodeEditor.react.js b/src/components/CodeEditor/CodeEditor.react.js
index a3231bf9a8..bf865041a9 100644
--- a/src/components/CodeEditor/CodeEditor.react.js
+++ b/src/components/CodeEditor/CodeEditor.react.js
@@ -56,5 +56,5 @@ export default class CodeEditor extends React.Component {
CodeEditor.propTypes = {
fontSize: PropTypes.number.describe('Font size of the editor'),
- placeHolder: PropTypes.string.describe('Code place holder')
+ placeHolder: PropTypes.string.describe('Code place holder'),
};
diff --git a/src/components/CodeSnippet/CodeSnippet.example.js b/src/components/CodeSnippet/CodeSnippet.example.js
index 699be4da7a..0822076280 100644
--- a/src/components/CodeSnippet/CodeSnippet.example.js
+++ b/src/components/CodeSnippet/CodeSnippet.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
export const component = CodeSnippet;
@@ -13,28 +13,24 @@ export const component = CodeSnippet;
export const demos = [
{
render() {
- let source = `// Some comment here
+ const source = `// Some comment here
Parse.Cloud.define('hello', function(req, resp) {
let someVariable = "
";
let otherVariable = "
";
-});`
+});`;
- return (
-
- )
- }
+ return
;
+ },
},
{
name: 'Print JSON',
render() {
- let obj = {
+ const obj = {
this: 'is awesome',
- awesome: true
+ awesome: true,
};
- return (
-
- )
- }
- }
+ return
;
+ },
+ },
];
diff --git a/src/components/CodeSnippet/CodeSnippet.react.js b/src/components/CodeSnippet/CodeSnippet.react.js
index 85b57bbe89..521d09d5a4 100644
--- a/src/components/CodeSnippet/CodeSnippet.react.js
+++ b/src/components/CodeSnippet/CodeSnippet.react.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import Prism from 'prismjs';
+import React from 'react';
+import Prism from 'prismjs';
import './CodeSnippet.css';
import 'prismjs/plugins/line-numbers/prism-line-numbers';
@@ -31,15 +31,17 @@ export default class CodeSnippet extends React.Component {
}
render() {
- let { fullPage = true, lineNumbers = true } = this.props;
- let classes = ['language-' + this.props.language];
+ const { fullPage = true, lineNumbers = true } = this.props;
+ const classes = ['language-' + this.props.language];
if (lineNumbers) {
classes.push('line-numbers');
}
- let pageStyle = fullPage ? { minHeight: 'calc(100vh - 96px)'} : {};
+ const pageStyle = fullPage ? { minHeight: 'calc(100vh - 96px)' } : {};
return (
-
- {this.props.source}
+
+
+ {this.props.source}
+
);
}
@@ -49,13 +51,11 @@ CodeSnippet.propTypes = {
source: PropTypes.string.isRequired.describe(
'The source code to be rendered with syntax-highlighting.'
),
- language: PropTypes.string.describe(
- 'The programming language of the snippet.'
- ),
+ language: PropTypes.string.describe('The programming language of the snippet.'),
fullPage: PropTypes.bool.describe(
'Pass false if this component doesn\'t need to fill the whole page.'
),
lineNumbers: PropTypes.bool.describe(
'Pass false if this component doesn\'t need to print line numbers.'
- )
+ ),
};
diff --git a/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js
index 279beb1926..d4b0f5d70a 100644
--- a/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js
+++ b/src/components/ColumnsConfiguration/ColumnConfigurationItem.react.js
@@ -7,37 +7,47 @@ import styles from 'components/ColumnsConfiguration/ColumnConfigurationItem.scss
const DND_TYPE = 'ColumnConfigurationItem';
export default ({ name, handleColumnDragDrop, index, onChangeVisible, visible }) => {
- const [ { isDragging}, drag ] = useDrag({
+ const [{ isDragging }, drag] = useDrag({
item: { type: DND_TYPE, index },
- collect: monitor => ({ isDragging: !!monitor.isDragging() })
+ collect: monitor => ({ isDragging: !!monitor.isDragging() }),
});
- const [ { canDrop, isOver }, drop ] = useDrop({
+ const [{ canDrop, isOver }, drop] = useDrop({
accept: DND_TYPE,
drop: item => handleColumnDragDrop(item.index, index),
canDrop: item => item.index !== index,
collect: monitor => ({
isOver: !!monitor.isOver(),
- canDrop: !!monitor.canDrop()
- })
+ canDrop: !!monitor.canDrop(),
+ }),
});
- return drag(drop(
- onChangeVisible(!visible)}>
-
-
-
- {name}
-
-
-
-
- ));
+ return drag(
+ drop(
+ onChangeVisible(!visible)}
+ >
+
+
+
+
+ {name}
+
+
+
+
+
+ )
+ );
};
diff --git a/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js
index 45c9389087..7328db1afb 100644
--- a/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js
+++ b/src/components/ColumnsConfiguration/ColumnsConfiguration.react.js
@@ -1,6 +1,6 @@
import React from 'react';
-import { DndProvider } from 'react-dnd'
-import HTML5Backend from 'react-dnd-html5-backend'
+import { DndProvider } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
import Button from 'components/Button/Button.react';
import ColumnConfigurationItem from 'components/ColumnsConfiguration/ColumnConfigurationItem.react';
@@ -16,7 +16,7 @@ export default class ColumnsConfiguration extends React.Component {
super();
this.state = {
- open: false
+ open: false,
};
this.entryRef = React.createRef();
@@ -25,24 +25,24 @@ export default class ColumnsConfiguration extends React.Component {
componentWillReceiveProps(props) {
if (props.schema !== this.props.schema) {
this.setState({
- open: false
+ open: false,
});
}
}
toggle() {
this.setState({
- open: !this.state.open
- })
+ open: !this.state.open,
+ });
}
showAll() {
let shouldReload = false;
- let updatedOrder = this.props.order.map(field => {
+ const updatedOrder = this.props.order.map(field => {
if (!shouldReload && !field.cached) {
shouldReload = true;
}
- return { ...field, visible: true }
+ return { ...field, visible: true };
});
this.props.handleColumnsOrder(updatedOrder, shouldReload);
}
@@ -52,94 +52,107 @@ export default class ColumnsConfiguration extends React.Component {
}
autoSort() {
- const defaultOrder = ['objectId', 'createdAt', 'updatedAt', 'ACL']
+ const defaultOrder = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
const order = {
default: [],
- other: []
+ other: [],
};
for (const column of this.props.order) {
const index = defaultOrder.indexOf(column.name);
if (index !== -1) {
order.default[index] = column;
} else {
- order.other.push(column)
+ order.other.push(column);
}
}
- this.props.handleColumnsOrder([...order.default.filter(column => column), ...order.other.sort((a,b) => a.name.localeCompare(b.name))]);
+ this.props.handleColumnsOrder([
+ ...order.default.filter(column => column),
+ ...order.other.sort((a, b) => a.name.localeCompare(b.name)),
+ ]);
}
render() {
const { handleColumnDragDrop, handleColumnsOrder, order, disabled } = this.props;
- const title =
-
- Manage Columns
-
+ const title = (
+
+
+ Manage Columns
+
+ );
- let entry =
-
- Manage Columns
-
+ let entry = (
+
+
+ Manage Columns
+
+ );
if (disabled) {
- entry =
-
- Manage Columns
-
;
+ entry = (
+
+
+ Manage Columns
+
+ );
}
let popover = null;
if (this.state.open) {
popover = (
-
+
{title}
{order.map(({ name, visible, ...rest }, index) => {
- return {
- const updatedOrder = [...order];
- updatedOrder[index] = {
- ...rest,
- name,
- visible
- };
- let shouldReload = visible;
- // these fields are always cached as they are never excluded from server
- // therefore no need to make another request.
- if (name === 'objectId' || name === 'createdAt' || name === 'updatedAt' || name === 'ACL') {
- shouldReload = false;
- }
- if (this.props.className === '_User' && name === 'password') {
- shouldReload = false;
- }
- if (updatedOrder[index].cached) {
- shouldReload = false;
- }
- handleColumnsOrder(updatedOrder, shouldReload);
- }}
- handleColumnDragDrop={handleColumnDragDrop} />
+ return (
+ {
+ const updatedOrder = [...order];
+ updatedOrder[index] = {
+ ...rest,
+ name,
+ visible,
+ };
+ let shouldReload = visible;
+ // these fields are always cached as they are never excluded from server
+ // therefore no need to make another request.
+ if (
+ name === 'objectId' ||
+ name === 'createdAt' ||
+ name === 'updatedAt' ||
+ name === 'ACL'
+ ) {
+ shouldReload = false;
+ }
+ if (this.props.className === '_User' && name === 'password') {
+ shouldReload = false;
+ }
+ if (updatedOrder[index].cached) {
+ shouldReload = false;
+ }
+ handleColumnsOrder(updatedOrder, shouldReload);
+ }}
+ handleColumnDragDrop={handleColumnDragDrop}
+ />
+ );
})}
-
-
-
+
+
+
diff --git a/src/components/ContextMenu/ContextMenu.example.js b/src/components/ContextMenu/ContextMenu.example.js
index 10afe9c1c8..f3932767b3 100644
--- a/src/components/ContextMenu/ContextMenu.example.js
+++ b/src/components/ContextMenu/ContextMenu.example.js
@@ -14,35 +14,70 @@ export const demos = [
{
name: 'Context menu',
render: () => (
-
+
{ alert('C1 Item 1 clicked!') } },
- { text: 'C1 Item 2', callback: () => { alert('C1 Item 2 clicked!') } },
+ text: 'Category 1',
+ items: [
{
- text: 'Sub Category 1', items: [
- { text: 'SC1 Item 1', callback: () => { alert('SC1 Item 1 clicked!') } },
- { text: 'SC1 Item 2', callback: () => { alert('SC1 Item 2 clicked!') } },
- ]
- }
- ]
+ text: 'C1 Item 1',
+ callback: () => {
+ alert('C1 Item 1 clicked!');
+ },
+ },
+ {
+ text: 'C1 Item 2',
+ callback: () => {
+ alert('C1 Item 2 clicked!');
+ },
+ },
+ {
+ text: 'Sub Category 1',
+ items: [
+ {
+ text: 'SC1 Item 1',
+ callback: () => {
+ alert('SC1 Item 1 clicked!');
+ },
+ },
+ {
+ text: 'SC1 Item 2',
+ callback: () => {
+ alert('SC1 Item 2 clicked!');
+ },
+ },
+ ],
+ },
+ ],
},
{
- text: 'Category 2', items: [
- { text: 'C2 Item 1', callback: () => { alert('C2 Item 1 clicked!') } },
- { text: 'C2 Item 2', callback: () => { alert('C2 Item 2 clicked!') } }
- ]
- }
+ text: 'Category 2',
+ items: [
+ {
+ text: 'C2 Item 1',
+ callback: () => {
+ alert('C2 Item 1 clicked!');
+ },
+ },
+ {
+ text: 'C2 Item 2',
+ callback: () => {
+ alert('C2 Item 2 clicked!');
+ },
+ },
+ ],
+ },
]}
/>
- )
- }
+ ),
+ },
];
diff --git a/src/components/ContextMenu/ContextMenu.react.js b/src/components/ContextMenu/ContextMenu.react.js
index 611e1e0a99..04d973b7d9 100644
--- a/src/components/ContextMenu/ContextMenu.react.js
+++ b/src/components/ContextMenu/ContextMenu.react.js
@@ -9,22 +9,20 @@ import PropTypes from 'lib/PropTypes';
import React, { useState, useEffect, useRef } from 'react';
import styles from 'components/ContextMenu/ContextMenu.scss';
-const getPositionToFitVisibleScreen = (ref) => {
+const getPositionToFitVisibleScreen = ref => {
if (ref.current) {
-
const elBox = ref.current.getBoundingClientRect();
- const y = (elBox.y + elBox.height) < window.innerHeight ?
- 0 : (0 - elBox.y + 100);
+ const y = elBox.y + elBox.height < window.innerHeight ? 0 : 0 - elBox.y + 100;
// If there's a previous element show current next to it.
// Try on right side first, then on left if there's no place.
const prevEl = ref.current.previousSibling;
if (prevEl) {
const prevElBox = prevEl.getBoundingClientRect();
- const showOnRight = (prevElBox.x + prevElBox.width + elBox.width) < window.innerWidth;
+ const showOnRight = prevElBox.x + prevElBox.width + elBox.width < window.innerWidth;
return {
x: showOnRight ? prevElBox.width : -elBox.width,
- y
+ y,
};
}
@@ -33,7 +31,6 @@ const getPositionToFitVisibleScreen = (ref) => {
};
const MenuSection = ({ level, items, path, setPath, hide }) => {
-
const sectionRef = useRef(null);
const [position, setPosition] = useState();
@@ -42,18 +39,21 @@ const MenuSection = ({ level, items, path, setPath, hide }) => {
newPosition && setPosition(newPosition);
}, [sectionRef]);
- const style = position ? {
- left: position.x,
- top: position.y,
- maxHeight: '80vh',
- overflowY: 'scroll',
- opacity: 1
- } : {};
-
- return (
- {items.map((item, index) => {
- if (item.items) {
- return (
+ const style = position
+ ? {
+ left: position.x,
+ top: position.y,
+ maxHeight: '80vh',
+ overflowY: 'scroll',
+ opacity: 1,
+ }
+ : {};
+
+ return (
+
+ {items.map((item, index) => {
+ if (item.items) {
+ return (
{
{item.text}
);
- }
- return (
+ }
+ return (
{
{item.subtext && - {item.subtext} }
);
- })}
- );
-}
-
-let ContextMenu = ({ x, y, items }) => {
+ })}
+
+ );
+};
+const ContextMenu = ({ x, y, items }) => {
const [path, setPath] = useState([0]);
const [visible, setVisible] = useState(true);
- useEffect(() => { setVisible(true); }, [items]);
+ useEffect(() => {
+ setVisible(true);
+ }, [items]);
const hide = () => {
setVisible(false);
setPath([0]);
- }
+ };
//#region Closing menu after clicking outside it
@@ -114,38 +116,49 @@ let ContextMenu = ({ x, y, items }) => {
//#endregion
- if (!visible) { return null; }
+ if (!visible) {
+ return null;
+ }
- const getItemsFromLevel = (level) => {
+ const getItemsFromLevel = level => {
let result = items;
for (let index = 1; index <= level; index++) {
result = result[path[index]].items;
}
return result;
- }
+ };
return (
-
+
{path.map((position, level) => {
- return
+ return (
+
+ );
})}
);
-}
+};
ContextMenu.propTypes = {
x: PropTypes.number.isRequired.describe('X context menu position.'),
y: PropTypes.number.isRequired.describe('Y context menu position.'),
- items: PropTypes.array.isRequired.describe('Array with tree representation of context menu items.'),
-}
+ items: PropTypes.array.isRequired.describe(
+ 'Array with tree representation of context menu items.'
+ ),
+};
export default ContextMenu;
diff --git a/src/components/CreditCardInput/CreditCardInput.example.js b/src/components/CreditCardInput/CreditCardInput.example.js
index 0d5cc55c9d..2d8585c8b1 100644
--- a/src/components/CreditCardInput/CreditCardInput.example.js
+++ b/src/components/CreditCardInput/CreditCardInput.example.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import CreditCardInput from 'components/CreditCardInput/CreditCardInput.react';
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
class Demo extends React.Component {
constructor() {
@@ -21,7 +21,8 @@ class Demo extends React.Component {
this.setState({ value })} />
+ onChange={value => this.setState({ value })}
+ />
);
}
}
@@ -32,15 +33,15 @@ export const demos = [
{
render: () => (
- } input={ } />
+ } input={ } />
- )
+ ),
},
{
render: () => (
- } input={ } />
+ } input={ } />
- )
- }
+ ),
+ },
];
diff --git a/src/components/CreditCardInput/CreditCardInput.react.js b/src/components/CreditCardInput/CreditCardInput.react.js
index c9eeb524cb..6376e40f9d 100644
--- a/src/components/CreditCardInput/CreditCardInput.react.js
+++ b/src/components/CreditCardInput/CreditCardInput.react.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/CreditCardInput/CreditCardInput.scss';
+import React from 'react';
+import styles from 'components/CreditCardInput/CreditCardInput.scss';
const VALID_REGEX = /^[\d ]*$/;
@@ -26,7 +26,8 @@ class CreditCardInput extends React.Component {
}
render() {
- let { value, lastFour, onChange } = this.props
+ let { value } = this.props;
+ const { lastFour, onChange } = this.props;
let prefilled = false;
if (value == null && lastFour) {
prefilled = true;
@@ -35,24 +36,25 @@ class CreditCardInput extends React.Component {
return (
{
+ onFocus={() => {
if (prefilled) {
onChange('');
}
}}
onChange={e => {
- let newValue = e.target.value;
+ const newValue = e.target.value;
if (VALID_REGEX.test(newValue)) {
onChange(newValue.replace(/\s/g, ''));
- this.setState({cursorPosition: e.target.selectionStart});
+ this.setState({ cursorPosition: e.target.selectionStart });
} else {
//If they try to type a non-digit, don't move the cursor.
- this.setState({cursorPosition: e.target.selectionStart - 1});
+ this.setState({ cursorPosition: e.target.selectionStart - 1 });
}
- }} />
+ }}
+ />
);
}
}
@@ -60,13 +62,9 @@ class CreditCardInput extends React.Component {
export default CreditCardInput;
CreditCardInput.propTypes = {
- value: PropTypes.string.describe(
- 'The current value of the controlled input.'
- ),
+ value: PropTypes.string.describe('The current value of the controlled input.'),
lastFour: PropTypes.string.describe(
'If provided, and the current value is falsy, the input will render as "•••• •••• •••• {lastFour}"'
),
- onChange: PropTypes.func.describe(
- 'A function called when the input is changed.'
- )
+ onChange: PropTypes.func.describe('A function called when the input is changed.'),
};
diff --git a/src/components/DataBrowserHeader/DataBrowserHeader.example.js b/src/components/DataBrowserHeader/DataBrowserHeader.example.js
index 83d9e2a05c..c62e8beeb4 100644
--- a/src/components/DataBrowserHeader/DataBrowserHeader.example.js
+++ b/src/components/DataBrowserHeader/DataBrowserHeader.example.js
@@ -5,14 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
-import HTML5Backend from 'react-dnd-html5-backend';
-import React from 'react';
-import { DndProvider } from 'react-dnd'
+import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
+import HTML5Backend from 'react-dnd-html5-backend';
+import React from 'react';
+import { DndProvider } from 'react-dnd';
export const component = DataBrowserHeader;
-let lightBg = { background: 'rgba(224,224,234,0.10)' };
+const lightBg = { background: 'rgba(224,224,234,0.10)' };
class HeadersDemo extends React.Component {
render() {
@@ -20,22 +20,26 @@ class HeadersDemo extends React.Component {
@@ -45,8 +49,6 @@ class HeadersDemo extends React.Component {
export const demos = [
{
- render: () => (
-
- )
- }
+ render: () => ,
+ },
];
diff --git a/src/components/DataBrowserHeader/DataBrowserHeader.react.js b/src/components/DataBrowserHeader/DataBrowserHeader.react.js
index 2e17b297d5..04557dc9a4 100644
--- a/src/components/DataBrowserHeader/DataBrowserHeader.react.js
+++ b/src/components/DataBrowserHeader/DataBrowserHeader.react.js
@@ -5,14 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/DataBrowserHeader/DataBrowserHeader.scss';
-import baseStyles from 'stylesheets/base.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/DataBrowserHeader/DataBrowserHeader.scss';
+import baseStyles from 'stylesheets/base.scss';
import { DragSource, DropTarget } from 'react-dnd';
const Types = {
- DATA_BROWSER_HEADER: 'dataBrowserHeader'
+ DATA_BROWSER_HEADER: 'dataBrowserHeader',
};
const dataBrowserHeaderTarget = {
@@ -33,7 +33,7 @@ const dataBrowserHeaderTarget = {
props.moveDataBrowserHeader(dragIndex, hoverIndex);
},
-}
+};
const dataBrowserHeaderSource = {
beginDrag(props) {
@@ -41,21 +41,31 @@ const dataBrowserHeaderSource = {
name: props.name,
index: props.index,
};
- }
+ },
};
@DropTarget(Types.DATA_BROWSER_HEADER, dataBrowserHeaderTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver()
+ isOver: monitor.isOver(),
}))
@DragSource(Types.DATA_BROWSER_HEADER, dataBrowserHeaderSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
- isDragging: monitor.isDragging()
+ isDragging: monitor.isDragging(),
}))
class DataBrowserHeader extends React.Component {
render() {
- let { connectDragSource, connectDropTarget, name, type, targetClass, order, style, isDragging, isOver } = this.props;
- let classes = [styles.header, baseStyles.unselectable];
+ const {
+ connectDragSource,
+ connectDropTarget,
+ name,
+ type,
+ targetClass,
+ order,
+ style,
+ isDragging,
+ isOver,
+ } = this.props;
+ const classes = [styles.header, baseStyles.unselectable];
if (order) {
classes.push(styles[order]);
}
@@ -65,27 +75,23 @@ class DataBrowserHeader extends React.Component {
if (isDragging) {
classes.push(styles.dragging);
}
- return connectDragSource(connectDropTarget(
-
-
{name}
-
{targetClass ? `${type} <${targetClass}>` : type}
-
- ));
+ return connectDragSource(
+ connectDropTarget(
+
+
{name}
+
{targetClass ? `${type} <${targetClass}>` : type}
+
+ )
+ );
}
}
export default DataBrowserHeader;
DataBrowserHeader.propTypes = {
- name: PropTypes.string.isRequired.describe(
- 'The name of the column.'
- ),
- type: PropTypes.string.describe(
- 'The type of the column.'
- ),
- targetClass: PropTypes.string.describe(
- 'The target class for a Pointer or Relation.'
- ),
+ name: PropTypes.string.isRequired.describe('The name of the column.'),
+ type: PropTypes.string.describe('The type of the column.'),
+ targetClass: PropTypes.string.describe('The target class for a Pointer or Relation.'),
order: PropTypes.oneOf(['ascending', 'descending']).describe(
'A sort ordering that displays as an arrow in the header.'
),
diff --git a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
index d021924386..e714759376 100644
--- a/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
+++ b/src/components/DataBrowserHeaderBar/DataBrowserHeaderBar.react.js
@@ -5,38 +5,49 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
-import DragHandle from 'components/DragHandle/DragHandle.react';
-import HTML5Backend from 'react-dnd-html5-backend';
-import React from 'react';
-import styles from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss';
-import { DndProvider } from 'react-dnd'
+import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
+import DragHandle from 'components/DragHandle/DragHandle.react';
+import HTML5Backend from 'react-dnd-html5-backend';
+import React from 'react';
+import styles from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.scss';
+import { DndProvider } from 'react-dnd';
export default class DataBrowserHeaderBar extends React.Component {
render() {
- let { headers, onResize, selectAll, onAddColumn, updateOrdering, readonly, preventSchemaEdits, selected, isDataLoaded } = this.props;
- let elements = [
-
- {readonly
- ? null
- : selectAll(e.target.checked)} />
- }
-
+ const {
+ headers,
+ onResize,
+ selectAll,
+ onAddColumn,
+ updateOrdering,
+ readonly,
+ preventSchemaEdits,
+ selected,
+ isDataLoaded,
+ } = this.props;
+ const elements = [
+
+ {readonly ? null : (
+ selectAll(e.target.checked)} />
+ )}
+
,
];
headers.forEach(({ width, name, type, targetClass, order, visible, preventSort }, i) => {
- if (!visible) return;
- let wrapStyle = { width };
+ if (!visible) {
+ return;
+ }
+ const wrapStyle = { width };
if (i % 2) {
wrapStyle.background = '#726F85';
} else {
wrapStyle.background = '#66637A';
}
let onClick = null;
- if (!preventSort && (type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean')) {
+ if (
+ !preventSort &&
+ (type === 'String' || type === 'Number' || type === 'Date' || type === 'Boolean')
+ ) {
onClick = () => updateOrdering((order === 'descending' ? '' : '-') + name);
}
@@ -46,18 +57,15 @@ export default class DataBrowserHeaderBar extends React.Component {
}
elements.push(
-
+
+ moveDataBrowserHeader={this.props.handleDragDrop}
+ />
);
elements.push(
@@ -66,18 +74,15 @@ export default class DataBrowserHeaderBar extends React.Component {
});
if (onAddColumn) {
- let finalStyle = {};
+ const finalStyle = {};
if (headers.length % 2) {
finalStyle.background = 'rgba(224,224,234,0.10)';
}
elements.push(
readonly || preventSchemaEdits ? null : (
-
-
+
+
Add a new column
@@ -86,8 +91,10 @@ export default class DataBrowserHeaderBar extends React.Component {
}
function renderSkeleton() {
- if (isDataLoaded) return null;
- var skeletons = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1];
+ if (isDataLoaded) {
+ return null;
+ }
+ const skeletons = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1];
return (
{skeletons.map(function (opacity, index) {
@@ -112,6 +119,6 @@ export default class DataBrowserHeaderBar extends React.Component {
{renderSkeleton()}
- )
+ );
}
}
diff --git a/src/components/DatePicker/DatePicker.example.js b/src/components/DatePicker/DatePicker.example.js
index d7e3f55728..d5a57b5920 100644
--- a/src/components/DatePicker/DatePicker.example.js
+++ b/src/components/DatePicker/DatePicker.example.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import DatePicker from 'components/DatePicker/DatePicker.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import React from 'react';
export const component = DatePicker;
@@ -23,9 +23,7 @@ class DatePickerDemo extends React.Component {
}
render() {
- return (
-
- );
+ return ;
}
}
@@ -34,9 +32,10 @@ export const demos = [
render: () => (
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/DatePicker/DatePicker.react.js b/src/components/DatePicker/DatePicker.react.js
index 6ae363adf9..f6718abcdb 100644
--- a/src/components/DatePicker/DatePicker.react.js
+++ b/src/components/DatePicker/DatePicker.react.js
@@ -5,23 +5,23 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Calendar from 'components/Calendar/Calendar.react';
+import Calendar from 'components/Calendar/Calendar.react';
import { Directions } from 'lib/Constants';
-import { MONTHS } from 'lib/DateUtils';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-import styles from 'components/DatePicker/DatePicker.scss';
+import { MONTHS } from 'lib/DateUtils';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import styles from 'components/DatePicker/DatePicker.scss';
export default class DatePicker extends React.Component {
constructor() {
super();
this.state = {
open: false,
- position: null
- }
- this.inputRef = React.createRef()
+ position: null,
+ };
+ this.inputRef = React.createRef();
}
toggle() {
@@ -31,32 +31,35 @@ export default class DatePicker extends React.Component {
}
return {
open: true,
- position: Position.inDocument(this.inputRef.current)
+ position: Position.inDocument(this.inputRef.current),
};
});
}
close() {
this.setState({
- open: false
+ open: false,
});
}
render() {
let popover = null;
if (this.state.open) {
- let width = this.inputRef.current.clientWidth;
+ const width = this.inputRef.current.clientWidth;
popover = (
- {
- this.setState({ open: false }, this.props.onChange.bind(null, newValue));
- }} />
+ {
+ this.setState({ open: false }, this.props.onChange.bind(null, newValue));
+ }}
+ />
- )
+ );
}
let content = null;
@@ -65,11 +68,14 @@ export default class DatePicker extends React.Component {
} else {
content = (
- {`${MONTHS[this.props.value.getMonth()].substr(0, 3)} ${this.props.value.getDate()}, ${this.props.value.getFullYear()}`}
+ {`${MONTHS[this.props.value.getMonth()].substr(
+ 0,
+ 3
+ )} ${this.props.value.getDate()}, ${this.props.value.getFullYear()}`}
);
}
-
+
return (
{content}
diff --git a/src/components/DateRange/DateRange.example.js b/src/components/DateRange/DateRange.example.js
index b9b169ffaf..43d4a3c093 100644
--- a/src/components/DateRange/DateRange.example.js
+++ b/src/components/DateRange/DateRange.example.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DateRange from 'components/DateRange/DateRange.react';
+import DateRange from 'components/DateRange/DateRange.react';
import { Directions } from 'lib/Constants';
-import React from 'react';
+import React from 'react';
export const component = DateRange;
@@ -23,7 +23,11 @@ class Demo extends React.Component {
render() {
return (
-
+
);
}
}
@@ -34,13 +38,13 @@ export const demos = [
- )
+ ),
},
{
render: () => (
- )
- }
+ ),
+ },
];
diff --git a/src/components/DateRange/DateRange.react.js b/src/components/DateRange/DateRange.react.js
index 555b7eb6c8..c6439aa3bb 100644
--- a/src/components/DateRange/DateRange.react.js
+++ b/src/components/DateRange/DateRange.react.js
@@ -5,31 +5,27 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Calendar from 'components/Calendar/Calendar.react';
-import { Directions } from 'lib/Constants';
-import Icon from 'components/Icon/Icon.react';
-import {
- monthDayStringUTC,
- monthsFrom,
- daysFrom
-} from 'lib/DateUtils';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/DateRange/DateRange.scss';
+import Calendar from 'components/Calendar/Calendar.react';
+import { Directions } from 'lib/Constants';
+import Icon from 'components/Icon/Icon.react';
+import { monthDayStringUTC, monthsFrom, daysFrom } from 'lib/DateUtils';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/DateRange/DateRange.scss';
export default class DateRange extends React.Component {
constructor(props) {
super();
- let val = props.value || {};
+ const val = props.value || {};
this.state = {
open: false,
position: null,
start: val.start || monthsFrom(new Date(), -1),
- end: val.end || new Date()
+ end: val.end || new Date(),
};
this.wrapRef = React.createRef();
@@ -40,13 +36,13 @@ export default class DateRange extends React.Component {
if (this.state.open) {
return { open: false };
}
- let pos = Position.inWindow(this.wrapRef.current);
+ const pos = Position.inWindow(this.wrapRef.current);
if (this.props.align === Directions.RIGHT) {
pos.x += this.wrapRef.current.clientWidth;
}
return {
open: true,
- position: pos
+ position: pos,
};
});
}
@@ -69,45 +65,48 @@ export default class DateRange extends React.Component {
close() {
this.setState({
- open: false
+ open: false,
});
this.props.onChange({ start: this.state.start, end: this.state.end });
}
rangeString() {
- return (
- `${monthDayStringUTC(this.state.start)} - ${monthDayStringUTC(this.state.end)}`
- );
+ return `${monthDayStringUTC(this.state.start)} - ${monthDayStringUTC(this.state.end)}`;
}
render() {
let popover = null;
let content = null;
if (this.state.open) {
- let classes = [styles.open];
+ const classes = [styles.open];
if (this.props.align === Directions.RIGHT) {
classes.push(styles.right);
}
- let renderShade = (
+ const renderShade =
this.state.start.getFullYear() < this.state.end.getFullYear() ||
- this.state.start.getMonth() !== this.state.end.getMonth()
- );
+ this.state.start.getMonth() !== this.state.end.getMonth();
popover = (
-
+
this.setStart(start)}
- shadeAfter={renderShade} />
+ onChange={start => this.setStart(start)}
+ shadeAfter={renderShade}
+ />
this.setEnd(end)}
- shadeBefore={renderShade} />
+ onChange={end => this.setEnd(end)}
+ shadeBefore={renderShade}
+ />
{this.rangeString()}
-
+
@@ -116,7 +115,7 @@ export default class DateRange extends React.Component {
content = (
{this.rangeString()}
-
+
);
}
diff --git a/src/components/DateTimeEditor/DateTimeEditor.react.js b/src/components/DateTimeEditor/DateTimeEditor.react.js
index f2af5dc61c..3799d845e0 100644
--- a/src/components/DateTimeEditor/DateTimeEditor.react.js
+++ b/src/components/DateTimeEditor/DateTimeEditor.react.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react';
-import hasAncestor from 'lib/hasAncestor';
-import React from 'react';
-import styles from 'components/DateTimeEditor/DateTimeEditor.scss';
+import hasAncestor from 'lib/hasAncestor';
+import React from 'react';
+import styles from 'components/DateTimeEditor/DateTimeEditor.scss';
export default class DateTimeEditor extends React.Component {
constructor(props) {
@@ -18,7 +18,7 @@ export default class DateTimeEditor extends React.Component {
open: false,
position: null,
value: props.value,
- text: props.value.toISOString()
+ text: props.value.toISOString(),
};
this.checkExternalClick = this.checkExternalClick.bind(this);
@@ -55,7 +55,7 @@ export default class DateTimeEditor extends React.Component {
}
toggle() {
- this.setState((state) => ({ open: !state.open }));
+ this.setState(state => ({ open: !state.open }));
}
inputDate(e) {
@@ -66,22 +66,27 @@ export default class DateTimeEditor extends React.Component {
if (this.state.text === this.props.value.toISOString()) {
return;
}
- let date = new Date(this.state.text);
+ const date = new Date(this.state.text);
if (isNaN(date.getTime())) {
- this.setState({ value: this.props.value, text: this.props.value.toISOString() });
+ this.setState({
+ value: this.props.value,
+ text: this.props.value.toISOString(),
+ });
} else {
if (this.state.text.endsWith('Z')) {
this.setState({ value: date });
} else {
- let utc = new Date(Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds()
- ));
+ const utc = new Date(
+ Date.UTC(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate(),
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds(),
+ date.getMilliseconds()
+ )
+ );
this.setState({ value: utc });
}
}
@@ -95,8 +100,11 @@ export default class DateTimeEditor extends React.Component {
this.setState({ value: value, text: value.toISOString() })}
- close={() => this.setState({ open: false }, () => this.props.onCommit(this.state.value))} />
+ onChange={value => this.setState({ value: value, text: value.toISOString() })}
+ close={() =>
+ this.setState({ open: false }, () => this.props.onCommit(this.state.value))
+ }
+ />
);
}
@@ -105,13 +113,14 @@ export default class DateTimeEditor extends React.Component {
e.target.select()}
onClick={this.toggle.bind(this)}
onChange={this.inputDate.bind(this)}
- onBlur={this.commitDate.bind(this)} />
+ onBlur={this.commitDate.bind(this)}
+ />
{popover}
);
diff --git a/src/components/DateTimeEntry/DateTimeEntry.react.js b/src/components/DateTimeEntry/DateTimeEntry.react.js
index 2da49d5f62..ee49f84e92 100644
--- a/src/components/DateTimeEntry/DateTimeEntry.react.js
+++ b/src/components/DateTimeEntry/DateTimeEntry.react.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
export default class DateTimeEntry extends React.Component {
constructor(props) {
@@ -17,8 +17,8 @@ export default class DateTimeEntry extends React.Component {
this.state = {
open: false,
position: null,
- value: props.value.toISOString ? props.value.toISOString() : props.value
- }
+ value: props.value.toISOString ? props.value.toISOString() : props.value,
+ };
this.rootRef = React.createRef();
this.inputRef = React.createRef();
@@ -26,7 +26,7 @@ export default class DateTimeEntry extends React.Component {
componentWillReceiveProps(props) {
this.setState({
- value: props.value.toISOString ? props.value.toISOString() : props.value
+ value: props.value.toISOString ? props.value.toISOString() : props.value,
});
}
@@ -39,23 +39,23 @@ export default class DateTimeEntry extends React.Component {
}
open() {
- let node = this.rootRef.current;
- let pos = Position.inDocument(node);
+ const node = this.rootRef.current;
+ const pos = Position.inDocument(node);
pos.y += node.clientHeight;
- let height = 230 + node.clientWidth * 0.14;
+ const height = 230 + node.clientWidth * 0.14;
if (window.innerHeight - pos.y - height < 40) {
pos.y = window.innerHeight - height - 40;
}
this.setState({
open: true,
- position: pos
+ position: pos,
});
}
close() {
this.setState({
- open: false
+ open: false,
});
}
@@ -67,19 +67,21 @@ export default class DateTimeEntry extends React.Component {
if (this.state.value === this.props.value.toISOString()) {
return;
}
- let date = new Date(this.state.value);
+ const date = new Date(this.state.value);
if (isNaN(date.getTime())) {
this.setState({ value: this.props.value.toISOString() });
} else if (!this.state.value.toLowerCase().endsWith('z')) {
- let utc = new Date(Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds()
- ));
+ const utc = new Date(
+ Date.UTC(
+ date.getFullYear(),
+ date.getMonth(),
+ date.getDate(),
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds(),
+ date.getMilliseconds()
+ )
+ );
this.props.onChange(utc);
} else {
this.props.onChange(date);
@@ -94,24 +96,31 @@ export default class DateTimeEntry extends React.Component {
let popover = null;
if (this.state.open) {
popover = (
-
+
this.setState({ open: false })} />
+ close={() => this.setState({ open: false })}
+ />
);
}
-
+
return (
+ ref={this.inputRef}
+ />
{popover}
);
diff --git a/src/components/DateTimeInput/DateTimeInput.example.js b/src/components/DateTimeInput/DateTimeInput.example.js
index cb7cf53e90..9c293a0b18 100644
--- a/src/components/DateTimeInput/DateTimeInput.example.js
+++ b/src/components/DateTimeInput/DateTimeInput.example.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import React from 'react';
export const component = DateTimeInput;
@@ -24,7 +24,11 @@ class DateTimeInputDemo extends React.Component {
render() {
return (
-
+
);
}
}
@@ -34,18 +38,20 @@ export const demos = [
render: () => (
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
+ ),
},
{
render: () => (
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/DateTimeInput/DateTimeInput.react.js b/src/components/DateTimeInput/DateTimeInput.react.js
index 5c66e9c177..cd9f7a82d0 100644
--- a/src/components/DateTimeInput/DateTimeInput.react.js
+++ b/src/components/DateTimeInput/DateTimeInput.react.js
@@ -5,12 +5,12 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react';
-import { MONTHS, getDateMethod } from 'lib/DateUtils';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
-import styles from 'components/DateTimeInput/DateTimeInput.scss';
+import DateTimePicker from 'components/DateTimePicker/DateTimePicker.react';
+import { MONTHS, getDateMethod } from 'lib/DateUtils';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
+import styles from 'components/DateTimeInput/DateTimeInput.scss';
export default class DateTimeInput extends React.Component {
constructor() {
@@ -19,7 +19,7 @@ export default class DateTimeInput extends React.Component {
this.state = {
open: false,
position: null,
- }
+ };
this.inputRef = React.createRef();
}
@@ -29,9 +29,9 @@ export default class DateTimeInput extends React.Component {
if (this.state.open) {
return { open: false };
}
- let node = this.inputRef.current;
+ const node = this.inputRef.current;
let pos = Position.inDocument(node);
- let height = 230 + node.clientWidth * 0.14;
+ const height = 230 + node.clientWidth * 0.14;
if (this.props.fixed) {
pos = Position.inWindow(node);
if (window.innerHeight - pos.y - height < 40) {
@@ -44,14 +44,14 @@ export default class DateTimeInput extends React.Component {
}
return {
open: true,
- position: pos
+ position: pos,
};
});
}
close() {
this.setState({
- open: false
+ open: false,
});
}
@@ -59,13 +59,18 @@ export default class DateTimeInput extends React.Component {
let popover = null;
if (this.state.open) {
popover = (
-
+
this.setState({ open: false })} />
+ close={() => this.setState({ open: false })}
+ />
);
}
@@ -76,18 +81,28 @@ export default class DateTimeInput extends React.Component {
} else {
content = (
- {MONTHS[this.props.value[getDateMethod(this.props.local, 'getMonth')]()].substr(0, 3) + ' ' + this.props.value[getDateMethod(this.props.local, 'getDate')]()}
+
+ {MONTHS[this.props.value[getDateMethod(this.props.local, 'getMonth')]()].substr(0, 3) +
+ ' ' +
+ this.props.value[getDateMethod(this.props.local, 'getDate')]()}
+
at
- {this.props.value[getDateMethod(this.props.local, 'getHours')]()}:{(this.props.value[getDateMethod(this.props.local, 'getMinutes')]() < 10 ? '0' : '') + this.props.value[getDateMethod(this.props.local, 'getMinutes')]()}
+ {this.props.value[getDateMethod(this.props.local, 'getHours')]()}:
+ {(this.props.value[getDateMethod(this.props.local, 'getMinutes')]() < 10 ? '0' : '') +
+ this.props.value[getDateMethod(this.props.local, 'getMinutes')]()}
{!this.props.local ? UTC : null}
);
}
-
+
return (
-
+
{content}
{popover}
diff --git a/src/components/DateTimePicker/DateTimePicker.react.js b/src/components/DateTimePicker/DateTimePicker.react.js
index a4fdabe5b2..2c8f582df5 100644
--- a/src/components/DateTimePicker/DateTimePicker.react.js
+++ b/src/components/DateTimePicker/DateTimePicker.react.js
@@ -5,33 +5,37 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Calendar from 'components/Calendar/Calendar.react';
+import Button from 'components/Button/Button.react';
+import Calendar from 'components/Calendar/Calendar.react';
import { hoursFrom, getDateMethod } from 'lib/DateUtils';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/DateTimePicker/DateTimePicker.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/DateTimePicker/DateTimePicker.scss';
export default class DateTimePicker extends React.Component {
constructor(props) {
super();
- let timeRef = props.value || hoursFrom(new Date(), 1);
+ const timeRef = props.value || hoursFrom(new Date(), 1);
this.state = {
hours: String(timeRef[getDateMethod(props.local, 'getHours')]()),
- minutes: (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + String(timeRef[getDateMethod(props.local, 'getMinutes')]()),
- }
+ minutes:
+ (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') +
+ String(timeRef[getDateMethod(props.local, 'getMinutes')]()),
+ };
}
componentWillReceiveProps(props) {
- let timeRef = props.value || hoursFrom(new Date(), 1);
+ const timeRef = props.value || hoursFrom(new Date(), 1);
this.setState({
hours: String(timeRef[getDateMethod(props.local, 'getHours')]()),
- minutes: (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') + String(timeRef[getDateMethod(props.local, 'getMinutes')]()),
+ minutes:
+ (timeRef[getDateMethod(props.local, 'getMinutes')]() < 10 ? '0' : '') +
+ String(timeRef[getDateMethod(props.local, 'getMinutes')]()),
});
}
changeHours(e) {
- let hoursString = e.target.value;
+ const hoursString = e.target.value;
if (hoursString === '') {
return this.setState({ hours: '' });
}
@@ -49,7 +53,7 @@ export default class DateTimePicker extends React.Component {
}
changeMinutes(e) {
- let minutesString = e.target.value;
+ const minutesString = e.target.value;
if (minutesString === '') {
return this.setState({ minutes: '' });
}
@@ -67,21 +71,24 @@ export default class DateTimePicker extends React.Component {
}
commitTime() {
- let dateRef = this.props.value || new Date();
- let newDate = this.props.local ? new Date(
- dateRef.getFullYear(),
- dateRef.getMonth(),
- dateRef.getDate(),
- parseInt(this.state.hours, 10),
- parseInt(this.state.minutes, 10)
- ) :
- new Date(Date.UTC(
- dateRef.getUTCFullYear(),
- dateRef.getUTCMonth(),
- dateRef.getUTCDate(),
- parseInt(this.state.hours, 10),
- parseInt(this.state.minutes, 10)
- ));
+ const dateRef = this.props.value || new Date();
+ const newDate = this.props.local
+ ? new Date(
+ dateRef.getFullYear(),
+ dateRef.getMonth(),
+ dateRef.getDate(),
+ parseInt(this.state.hours, 10),
+ parseInt(this.state.minutes, 10)
+ )
+ : new Date(
+ Date.UTC(
+ dateRef.getUTCFullYear(),
+ dateRef.getUTCMonth(),
+ dateRef.getUTCDate(),
+ parseInt(this.state.hours, 10),
+ parseInt(this.state.minutes, 10)
+ )
+ );
this.props.onChange(newDate);
if (this.props.close) {
this.props.close();
@@ -90,32 +97,47 @@ export default class DateTimePicker extends React.Component {
render() {
return (
-
e.stopPropagation()} >
-
{
- let timeRef = this.props.value || hoursFrom(new Date(), 1);
- let newDate = this.props.local ? new Date(
- newValue.getFullYear(),
- newValue.getMonth(),
- newValue.getDate(),
- timeRef.getHours(),
- timeRef.getMinutes()
- ) :
- new Date(Date.UTC(
- newValue.getUTCFullYear(),
- newValue.getUTCMonth(),
- newValue.getUTCDate(),
- timeRef.getUTCHours(),
- timeRef.getUTCMinutes()
- ));
- this.props.onChange(newDate);
- }} />
+ e.stopPropagation()}
+ >
+
{
+ const timeRef = this.props.value || hoursFrom(new Date(), 1);
+ const newDate = this.props.local
+ ? new Date(
+ newValue.getFullYear(),
+ newValue.getMonth(),
+ newValue.getDate(),
+ timeRef.getHours(),
+ timeRef.getMinutes()
+ )
+ : new Date(
+ Date.UTC(
+ newValue.getUTCFullYear(),
+ newValue.getUTCMonth(),
+ newValue.getUTCDate(),
+ timeRef.getUTCHours(),
+ timeRef.getUTCMinutes()
+ )
+ );
+ this.props.onChange(newDate);
+ }}
+ />
);
@@ -123,19 +145,9 @@ export default class DateTimePicker extends React.Component {
}
DateTimePicker.propTypes = {
- value: PropTypes.instanceOf(Date).describe(
- 'The current date of the picker.'
- ),
- width: PropTypes.number.isRequired.describe(
- 'The width of the calendar.'
- ),
- onChange: PropTypes.func.isRequired.describe(
- 'A function to call when a new date is selected.'
- ),
- close: PropTypes.func.describe(
- 'An optional function to call to close the calendar.'
- ),
- local: PropTypes.bool.describe(
- 'An option flag to set when using a local DateTimeInput.'
- ),
+ value: PropTypes.instanceOf(Date).describe('The current date of the picker.'),
+ width: PropTypes.number.isRequired.describe('The width of the calendar.'),
+ onChange: PropTypes.func.isRequired.describe('A function to call when a new date is selected.'),
+ close: PropTypes.func.describe('An optional function to call to close the calendar.'),
+ local: PropTypes.bool.describe('An option flag to set when using a local DateTimeInput.'),
};
diff --git a/src/components/DonutChart/DonutChart.example.js b/src/components/DonutChart/DonutChart.example.js
index 3f2e2ea9ea..22b72e4aae 100644
--- a/src/components/DonutChart/DonutChart.example.js
+++ b/src/components/DonutChart/DonutChart.example.js
@@ -6,31 +6,28 @@
* the root directory of this source tree.
*/
import DonutChart from 'components/DonutChart/DonutChart.react';
-import React from 'react';
+import React from 'react';
export const component = DonutChart;
export const demos = [
{
name: 'Simple DonutChart',
- render: () => (
-
- )
- }, {
+ render: () => ,
+ },
+ {
name: 'DonutChart without Dominant Value',
- render: () => (
-
- )
- }, {
- name: 'Progress Bar with DonutChart',
+ render: () => ,
+ },
+ {
+ name: 'Progress Bar with DonutChart',
render: () => (
- )
- }
+ label="20/120GB"
+ />
+ ),
+ },
];
diff --git a/src/components/DonutChart/DonutChart.react.js b/src/components/DonutChart/DonutChart.react.js
index a55c39d597..c7f1dfd9af 100644
--- a/src/components/DonutChart/DonutChart.react.js
+++ b/src/components/DonutChart/DonutChart.react.js
@@ -6,26 +6,23 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/DonutChart/DonutChart.scss';
+import React from 'react';
+import styles from 'components/DonutChart/DonutChart.scss';
-const CHART_COLORS = [
- '#b9e88b',
- '#fac786',
- '#80eeef',
- '#dfb3eb',
- '#fd9fb0'
-];
+const CHART_COLORS = ['#b9e88b', '#fac786', '#80eeef', '#dfb3eb', '#fd9fb0'];
-const MONOCHROME_COLORS = [
- '#3b2c48',
- '#e0e0ea'
-];
+const MONOCHROME_COLORS = ['#3b2c48', '#e0e0ea'];
-let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, printPercentage=false }) => {
- let centerX = diameter / 2;
- let centerY = centerX;
- let radius = centerX * 0.9;
+const DonutChart = ({
+ segments = [],
+ diameter = 200,
+ label = '',
+ isMonochrome = false,
+ printPercentage = false,
+}) => {
+ const centerX = diameter / 2;
+ const centerY = centerX;
+ const radius = centerX * 0.9;
let lastX = centerX;
let lastY = centerY - radius;
@@ -36,18 +33,14 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri
sum += segments[i];
}
- let paths = [];
+ const paths = [];
for (let i = 0; i < segments.length; ++i) {
- let arc = segments[i] / sum * 2 * Math.PI;
+ const arc = (segments[i] / sum) * 2 * Math.PI;
let angle = alpha - Math.min(arc, Math.PI);
let endX = radius * Math.cos(angle) + centerX;
let endY = -radius * Math.sin(angle) + centerY;
- let path = [
- 'M', centerY, centerY,
- 'L', lastX, lastY,
- 'A', radius, radius, 0, 0, 1, endX, endY
- ];
+ let path = ['M', centerY, centerY, 'L', lastX, lastY, 'A', radius, radius, 0, 0, 1, endX, endY];
if (arc > Math.PI) {
angle = alpha - arc;
endX = radius * Math.cos(angle) + centerX;
@@ -60,8 +53,12 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri
+ style={{
+ fill: isMonochrome ? MONOCHROME_COLORS[i % 2] : CHART_COLORS[i],
+ transformOrigin: `${centerY}px ${centerX}px 0px`,
+ }}
+ key={`segment${i}`}
+ />
);
lastX = endX;
@@ -74,11 +71,19 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri
{paths}
{segments.map((segment, i) => (
-
- {printPercentage ? (segment / sum * 100).toFixed(2) + '%' : segment}
+
+ {printPercentage ? ((segment / sum) * 100).toFixed(2) + '%' : segment}
))}
- {label}
+
+ {label}
+
);
};
@@ -86,19 +91,15 @@ let DonutChart = ({ segments=[], diameter=200, label='', isMonochrome=false, pri
export default DonutChart;
DonutChart.propTypes = {
- 'segments': PropTypes.arrayOf(PropTypes.number).isRequired.describe(
- 'Values of the DonutChart.'
- ),
- 'diameter': PropTypes.number.describe(
- 'Width and height of the DonutChart.'
- ),
- 'label': PropTypes.string.describe(
+ segments: PropTypes.arrayOf(PropTypes.number).isRequired.describe('Values of the DonutChart.'),
+ diameter: PropTypes.number.describe('Width and height of the DonutChart.'),
+ label: PropTypes.string.describe(
'Additional string to be appended after each rendered value in DonutChart.'
),
- 'isMonochrome': PropTypes.bool.describe(
+ isMonochrome: PropTypes.bool.describe(
'Whether the DonutChart is monochrome/bicolor (usually used for progress bar).'
),
- 'printPercentage': PropTypes.bool.describe(
+ printPercentage: PropTypes.bool.describe(
'Whether the DonutChart should render percentage of each segment instead of the actual value.'
- )
+ ),
};
diff --git a/src/components/DragHandle/DragHandle.example.js b/src/components/DragHandle/DragHandle.example.js
index cb0571f2b6..4fbf2fa1a7 100644
--- a/src/components/DragHandle/DragHandle.example.js
+++ b/src/components/DragHandle/DragHandle.example.js
@@ -5,11 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
-import DragHandle from 'components/DragHandle/DragHandle.react';
-import HTML5Backend from 'react-dnd-html5-backend';
-import React from 'react';
-import { DndProvider } from 'react-dnd'
+import DataBrowserHeader from 'components/DataBrowserHeader/DataBrowserHeader.react';
+import DragHandle from 'components/DragHandle/DragHandle.react';
+import HTML5Backend from 'react-dnd-html5-backend';
+import React from 'react';
+import { DndProvider } from 'react-dnd';
export const component = DragHandle;
@@ -22,14 +22,14 @@ class DragDemo extends React.Component {
handleDrag(dx, dy) {
this.setState(({ x, y }) => {
- let newX = Math.max(0, Math.min(x + dx, 480));
- let newY = Math.max(0, Math.min(y + dy, 480));
+ const newX = Math.max(0, Math.min(x + dx, 480));
+ const newY = Math.max(0, Math.min(y + dy, 480));
return { x: newX, y: newY };
});
}
render() {
- let style = {
+ const style = {
width: 20,
height: 20,
background: '#5298fc',
@@ -37,31 +37,33 @@ class DragDemo extends React.Component {
cursor: 'move',
position: 'absolute',
left: this.state.x,
- top: this.state.y
+ top: this.state.y,
};
return (
-
+
);
}
}
-let lightBg = { background: 'rgba(224,224,234,0.10)' };
-let handleStyle = {
+const lightBg = { background: 'rgba(224,224,234,0.10)' };
+const handleStyle = {
position: 'relative',
display: 'inline-block',
width: 4,
height: 30,
marginLeft: -2,
marginRight: -2,
- cursor: 'ew-resize'
+ cursor: 'ew-resize',
};
class HeadersDemo extends React.Component {
@@ -69,14 +71,7 @@ class HeadersDemo extends React.Component {
super();
this.state = {
- widths: [
- 140,
- 140,
- 140,
- 140,
- 140,
- 140
- ]
+ widths: [140, 140, 140, 140, 140, 140],
};
}
@@ -92,27 +87,31 @@ class HeadersDemo extends React.Component {
@@ -124,13 +123,10 @@ class HeadersDemo extends React.Component {
export const demos = [
{
name: 'Drag the ball',
- render: () => (
-
- )
- }, {
+ render: () => ,
+ },
+ {
name: 'Data Browser Headers',
- render: () => (
-
- )
- }
+ render: () => ,
+ },
];
diff --git a/src/components/DragHandle/DragHandle.react.js b/src/components/DragHandle/DragHandle.react.js
index 20304789a6..54741c0bc2 100644
--- a/src/components/DragHandle/DragHandle.react.js
+++ b/src/components/DragHandle/DragHandle.react.js
@@ -6,7 +6,7 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import React from 'react';
export default class DragHandle extends React.Component {
constructor() {
@@ -22,7 +22,7 @@ export default class DragHandle extends React.Component {
this.y = 0;
this.state = {
- dragging: false
+ dragging: false,
};
}
@@ -77,5 +77,5 @@ export default class DragHandle extends React.Component {
DragHandle.propTypes = {
onDrag: PropTypes.func.isRequired.describe(
'A function called when the handle is dragged. It takes deltas for X and Y as the two parameters.'
- )
-}
+ ),
+};
diff --git a/src/components/Dropdown/Dropdown.example.js b/src/components/Dropdown/Dropdown.example.js
index 15a76dfff0..ed091cc28c 100644
--- a/src/components/Dropdown/Dropdown.example.js
+++ b/src/components/Dropdown/Dropdown.example.js
@@ -5,11 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
import Option from 'components/Dropdown/Option.react';
-import React from 'react';
+import React from 'react';
export const component = Dropdown;
@@ -26,14 +26,14 @@ class DropdownDemo extends React.Component {
render() {
return (
- Red
- Orange
- Yellow
- Green
- Blue
- Purple
- Rainbow
- No preference. Instead, I like really long strings
+ Red
+ Orange
+ Yellow
+ Green
+ Blue
+ Purple
+ Rainbow
+ No preference. Instead, I like really long strings
);
}
@@ -42,14 +42,10 @@ class DropdownDemo extends React.Component {
export const demos = [
{
render: () => (
-
-
}
- input={
} />
-
}
- input={
} />
+
+ } input={ } />
+ } input={ } />
- )
- }
+ ),
+ },
];
diff --git a/src/components/Dropdown/Dropdown.react.js b/src/components/Dropdown/Dropdown.react.js
index 3f444767f9..471cf1d31e 100644
--- a/src/components/Dropdown/Dropdown.react.js
+++ b/src/components/Dropdown/Dropdown.react.js
@@ -6,20 +6,20 @@
* the root directory of this source tree.
*/
import { Directions } from 'lib/Constants';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-import styles from 'components/Dropdown/Dropdown.scss';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import styles from 'components/Dropdown/Dropdown.scss';
export default class Dropdown extends React.Component {
constructor() {
super();
this.state = {
open: false,
- position: null
- }
+ position: null,
+ };
this.dropdownRef = React.createRef();
}
@@ -35,14 +35,14 @@ export default class Dropdown extends React.Component {
}
return {
open: true,
- position: pos
+ position: pos,
};
});
}
close() {
this.setState({
- open: false
+ open: false,
});
}
@@ -50,30 +50,40 @@ export default class Dropdown extends React.Component {
if (value === this.props.value) {
return this.setState({ open: false });
}
- this.setState({
- open: false
- }, () => {
- this.props.onChange(value);
- });
+ this.setState(
+ {
+ open: false,
+ },
+ () => {
+ this.props.onChange(value);
+ }
+ );
}
render() {
let popover = null;
if (this.state.open && !this.props.disabled) {
- let width = this.dropdownRef.current.clientWidth;
- let popoverChildren = (
+ const width = this.dropdownRef.current.clientWidth;
+ const popoverChildren = (
{React.Children.map(this.props.children, c => (
- {c}
+
+ {c}
+
))}
);
- popover =
-
+ popover = (
+
{popoverChildren}
- ;
+
+ );
}
let content = null;
React.Children.forEach(this.props.children, c => {
@@ -82,26 +92,25 @@ export default class Dropdown extends React.Component {
}
});
if (!content) {
- content = (
-
- {this.props.placeHolder}
-
- );
+ content =
{this.props.placeHolder}
;
}
let dropdownStyle = {};
if (this.props.width) {
dropdownStyle = {
width: this.props.width,
- float: 'left'
+ float: 'left',
};
}
- let dropdownClasses = [styles.dropdown];
+ const dropdownClasses = [styles.dropdown];
if (this.props.disabled) {
dropdownClasses.push(styles.disabled);
}
return (
-
+
{content}
{popover}
@@ -114,9 +123,7 @@ Dropdown.propTypes = {
onChange: PropTypes.func.isRequired.describe(
'A function called when the dropdown is changed. It receives the new value as the only parameter.'
),
- value: PropTypes.string.describe(
- 'The currently-selected value of this controlled input.'
- ),
+ value: PropTypes.string.describe('The currently-selected value of this controlled input.'),
disabled: PropTypes.bool.describe('Set to true to disable the dropdown.'),
children: PropTypes.node.isRequired.describe(
'The children of Dropdown should only be
components.'
@@ -124,10 +131,6 @@ Dropdown.propTypes = {
fixed: PropTypes.bool.describe(
'Fixes the dropdown in place. Set to true in modals or other places where you don\u2019t want the dropdown to move when you scroll.'
),
- placeHolder: PropTypes.string.describe(
- 'Placeholder text used in place of default selection.'
- ),
- hideArrow: PropTypes.bool.describe(
- 'Flag to hide the dropdown arrow.'
- ),
-}
+ placeHolder: PropTypes.string.describe('Placeholder text used in place of default selection.'),
+ hideArrow: PropTypes.bool.describe('Flag to hide the dropdown arrow.'),
+};
diff --git a/src/components/Dropdown/Option.react.js b/src/components/Dropdown/Option.react.js
index d9726537d2..4995b2d4d5 100644
--- a/src/components/Dropdown/Option.react.js
+++ b/src/components/Dropdown/Option.react.js
@@ -8,6 +8,6 @@
import React from 'react';
import styles from 'components/Dropdown/Dropdown.scss';
-let Option = props =>
;
+const Option = props =>
;
export default Option;
diff --git a/src/components/EmptyState/EmptyState.example.js b/src/components/EmptyState/EmptyState.example.js
index 2c16c46331..8b1e014e6f 100644
--- a/src/components/EmptyState/EmptyState.example.js
+++ b/src/components/EmptyState/EmptyState.example.js
@@ -14,27 +14,29 @@ export const demos = [
{
name: 'Example with Action callback',
render: () => (
-
+
alert('CTA was clicked')}>
+ action={() => alert('CTA was clicked')}
+ >
- )
+ ),
},
{
name: 'Example with Action href',
render: () => (
-
+
+ action={'someLink'}
+ >
- )
- }
-]
+ ),
+ },
+];
diff --git a/src/components/EmptyState/EmptyState.react.js b/src/components/EmptyState/EmptyState.react.js
index 22be2eb529..4e27072db7 100644
--- a/src/components/EmptyState/EmptyState.react.js
+++ b/src/components/EmptyState/EmptyState.react.js
@@ -5,52 +5,46 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Icon from 'components/Icon/Icon.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/EmptyState/EmptyState.scss';
+import Button from 'components/Button/Button.react';
+import Icon from 'components/Icon/Icon.react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/EmptyState/EmptyState.scss';
import stylesButton from 'components/Button/Button.scss';
-import baseStyles from 'stylesheets/base.scss';
+import baseStyles from 'stylesheets/base.scss';
-let ctaButton = (cta, action) => {
+const ctaButton = (cta, action) => {
if (cta) {
if (action.constructor === String) {
return (
-
+
{cta}
);
} else {
- return (
-
- );
+ return
;
}
} else {
return null;
}
-}
+};
-let EmptyState = ({
- icon='',
- title='',
- description='',
- cta='',
- action=() => {},
- secondaryCta='',
- secondaryAction=() => {},
+const EmptyState = ({
+ icon = '',
+ title = '',
+ description = '',
+ cta = '',
+ action = () => {},
+ secondaryCta = '',
+ secondaryAction = () => {},
}) => (
-
+
{title}
{description}
@@ -61,26 +55,20 @@ let EmptyState = ({
);
EmptyState.propTypes = {
- icon: PropTypes.string.describe(
- 'The name of the large icon that appears in the empty state.'
- ),
+ icon: PropTypes.string.describe('The name of the large icon that appears in the empty state.'),
title: PropTypes.string.describe(
'Help text that explains why this is an empty state; ' +
- 'usually because you haven\u2019t created any data here.'
+ 'usually because you haven\u2019t created any data here.'
),
description: PropTypes.node.describe(
'Help text that indicates how to leave the empty state, ' +
- 'usually by visiting docs or using the CTA.'
- ),
- cta: PropTypes.string.describe(
- 'The text that appears in the CTA button.'
+ 'usually by visiting docs or using the CTA.'
),
+ cta: PropTypes.string.describe('The text that appears in the CTA button.'),
action: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).describe(
'An href link or a click handler that is forwarded to the CTA button.'
),
- secondaryCta: PropTypes.string.describe(
- 'The text that appears in the secondary CTA button.'
- ),
+ secondaryCta: PropTypes.string.describe('The text that appears in the secondary CTA button.'),
secondaryAction: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).describe(
'An href link or a click handler that is forwarded to the secondary CTA button.'
),
diff --git a/src/components/ExpirationDateInput/ExpirationDateInput.example.js b/src/components/ExpirationDateInput/ExpirationDateInput.example.js
index ae1c72efdf..987fd329a0 100644
--- a/src/components/ExpirationDateInput/ExpirationDateInput.example.js
+++ b/src/components/ExpirationDateInput/ExpirationDateInput.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
+import Field from 'components/Field/Field.react';
import ExpirationDateInput from 'components/ExpirationDateInput/ExpirationDateInput.react';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import Label from 'components/Label/Label.react';
+import React from 'react';
export const component = ExpirationDateInput;
@@ -23,7 +23,8 @@ class Demo extends React.Component {
this.setState(change)} />
+ onChange={change => this.setState(change)}
+ />
);
}
}
@@ -31,11 +32,9 @@ class Demo extends React.Component {
export const demos = [
{
render: () => (
-
-
}
- input={
} />
+
+ } input={ } />
- )
- }
+ ),
+ },
];
diff --git a/src/components/ExpirationDateInput/ExpirationDateInput.react.js b/src/components/ExpirationDateInput/ExpirationDateInput.react.js
index 0027b5b362..4993b269e7 100644
--- a/src/components/ExpirationDateInput/ExpirationDateInput.react.js
+++ b/src/components/ExpirationDateInput/ExpirationDateInput.react.js
@@ -10,31 +10,41 @@ import Option from 'components/Dropdown/Option.react';
import PropTypes from 'lib/PropTypes';
import React from 'react';
-let months = [];
+const months = [];
for (let i = 1; i <= 12; i++) {
- let value = (i < 10 ? '0' : '') + String(i);
- months.push(
{value} );
+ const value = (i < 10 ? '0' : '') + String(i);
+ months.push(
+
+ {value}
+
+ );
}
-let years = [];
-let startYear = new Date().getFullYear();
+const years = [];
+const startYear = new Date().getFullYear();
for (let i = 0; i < 10; i++) {
- let value = String(startYear + i);
- years.push(
{value} );
+ const value = String(startYear + i);
+ years.push(
+
+ {value}
+
+ );
}
-let ExpirationDateInput = ({ month, year, onChange }) => {
+const ExpirationDateInput = ({ month, year, onChange }) => {
return (
onChange({ month: parseInt(value, 10), year: year })}>
+ onChange={value => onChange({ month: parseInt(value, 10), year: year })}
+ >
{months}
onChange({ month: month, year: parseInt(value, 10) })}>
+ onChange={value => onChange({ month: month, year: parseInt(value, 10) })}
+ >
{years}
@@ -44,13 +54,9 @@ let ExpirationDateInput = ({ month, year, onChange }) => {
export default ExpirationDateInput;
ExpirationDateInput.propTypes = {
- month: PropTypes.number.describe(
- 'The expiration month.'
- ),
- year: PropTypes.number.describe(
- 'The expiration year, in four-digit form.'
- ),
+ month: PropTypes.number.describe('The expiration month.'),
+ year: PropTypes.number.describe('The expiration year, in four-digit form.'),
onChange: PropTypes.func.isRequired.describe(
'A function called when the value changes. It receives an object with two parameters: the month and the year.'
- )
+ ),
};
diff --git a/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.example.js b/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.example.js
index 41e3b0da39..bdb1bb0b94 100644
--- a/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.example.js
+++ b/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.example.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import ExplorerActiveChartButton from 'components/ExplorerActiveChartButton/ExplorerActiveChartButton.react';
-import React from 'react';
-import { ChartColorSchemes } from 'lib/Constants';
+import React from 'react';
+import { ChartColorSchemes } from 'lib/Constants';
export const component = ExplorerActiveChartButton;
@@ -17,103 +17,107 @@ const QUERIES = [
children: [
{
name: 'Daily Active Installations',
- query: { },
+ query: {},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Daily Active Users',
- query: { },
+ query: {},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Monthly Active Installations',
- query: { },
+ query: {},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Monthly Active Users',
- query: { },
+ query: {},
preset: true,
- nonComposable: true
- }
- ]
- }, {
+ nonComposable: true,
+ },
+ ],
+ },
+ {
name: 'Core',
children: [
{
name: 'Gogo Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'User Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Installation Count',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Events',
children: [
{
name: 'API Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Analytics Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'File Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Notifications',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'App Opens',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Opens',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Recent Queries',
children: [
{
name: 'User Count Aggregate',
- query: { }
- }
- ]
- }, {
+ query: {},
+ },
+ ],
+ },
+ {
name: 'Saved Queries',
children: [
{
name: 'Gogo Queries',
- query: { }
+ query: {},
},
{
name: 'Saved Queries',
- query: { }
- }
- ]
- }
+ query: {},
+ },
+ ],
+ },
];
export const demos = [
@@ -124,43 +128,61 @@ export const demos = [
{/* Do nothing */}}
- onToggle={() => {/* Do nothing */} } />
+ onSave={() => {
+ /* Do nothing */
+ }}
+ onToggle={() => {
+ /* Do nothing */
+ }}
+ />
);
- }
- }, {
+ },
+ },
+ {
name: 'With Custom Color',
render: () => {
return (
{/* Do nothing */}}
- onToggle={() => {/* Do nothing */} } />
+ onSave={() => {
+ /* Do nothing */
+ }}
+ onToggle={() => {
+ /* Do nothing */
+ }}
+ />
);
- }
- }, {
+ },
+ },
+ {
name: 'Without Dropdown',
render: () => {
return (
{/* Do nothing */}}
- onToggle={() => {/* Do nothing */} }
- disableDropdown={true} />
+ onSave={() => {
+ /* Do nothing */
+ }}
+ onToggle={() => {
+ /* Do nothing */
+ }}
+ disableDropdown={true}
+ />
);
- }
- }, {
+ },
+ },
+ {
name: 'With Non-Composable Query',
render: () => {
return (
@@ -168,12 +190,17 @@ export const demos = [
queries={QUERIES}
query={{
name: 'User Count Aggregate',
- nonComposable: true
+ nonComposable: true,
}}
color={ChartColorSchemes[1]}
- onSave={() => {/* Do nothing */}}
- onToggle={() => {/* Do nothing */} } />
+ onSave={() => {
+ /* Do nothing */
+ }}
+ onToggle={() => {
+ /* Do nothing */
+ }}
+ />
);
- }
- }
-]
+ },
+ },
+];
diff --git a/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.react.js b/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.react.js
index 51930881be..d3d65957d3 100644
--- a/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.react.js
+++ b/src/components/ExplorerActiveChartButton/ExplorerActiveChartButton.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { Directions } from 'lib/Constants';
+import { Directions } from 'lib/Constants';
import ExplorerQueryComposer from 'components/ExplorerQueryComposer/ExplorerQueryComposer.react';
-import Icon from 'components/Icon/Icon.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/ExplorerActiveChartButton/ExplorerActiveChartButton.scss';
-import baseStyles from 'stylesheets/base.scss';
+import Icon from 'components/Icon/Icon.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/ExplorerActiveChartButton/ExplorerActiveChartButton.scss';
+import baseStyles from 'stylesheets/base.scss';
export default class ExplorerActiveChartButton extends React.Component {
constructor() {
@@ -23,17 +23,17 @@ export default class ExplorerActiveChartButton extends React.Component {
position: null,
open: false,
active: true,
- align: Directions.LEFT
- }
+ align: Directions.LEFT,
+ };
this.wrapRef = React.createRef();
}
handleCheckbox() {
- let nextActiveState = !this.state.active;
+ const nextActiveState = !this.state.active;
this.props.onToggle(nextActiveState);
this.setState({
- active: nextActiveState
+ active: nextActiveState,
});
}
@@ -52,7 +52,7 @@ export default class ExplorerActiveChartButton extends React.Component {
let color = '#343445';
if (this.state.active) {
// TODO (hallucinogen): a11y the checkbox
- checkMark = ;
+ checkMark = ;
color = this.props.color;
}
let dropdown = null;
@@ -61,7 +61,7 @@ export default class ExplorerActiveChartButton extends React.Component {
{
- let position = Position.inDocument(this.wrapRef.current);
+ const position = Position.inDocument(this.wrapRef.current);
let align = Directions.LEFT;
if (position.x > 700) {
position.x += this.wrapRef.current.clientWidth;
@@ -70,9 +70,10 @@ export default class ExplorerActiveChartButton extends React.Component {
this.setState({
open: !this.state.open,
position,
- align
+ align,
});
- }} />
+ }}
+ />
);
}
@@ -83,8 +84,9 @@ export default class ExplorerActiveChartButton extends React.Component {
onClick={this.handleCheckbox.bind(this)}
style={{
backgroundColor: this.state.active ? this.props.color : null,
- border: `1px solid ${color}`
- }}>
+ border: `1px solid ${color}`,
+ }}
+ >
{checkMark}
{this.props.query.name}
@@ -95,10 +97,10 @@ export default class ExplorerActiveChartButton extends React.Component {
render() {
let popover = null;
- let content = this.renderButton();
+ const content = this.renderButton();
if (this.state.open) {
- let classes = [styles.composerContainer];
+ const classes = [styles.composerContainer];
let calloutStyle = { marginLeft: '10px' };
if (this.state.align === Directions.RIGHT) {
classes.push(styles.right);
@@ -106,9 +108,7 @@ export default class ExplorerActiveChartButton extends React.Component {
}
popover = (
-
+
{content}
@@ -120,7 +120,8 @@ export default class ExplorerActiveChartButton extends React.Component {
onDismiss={() => {
this.setState({ open: false });
this.props.onDismiss();
- }} />
+ }}
+ />
);
@@ -136,13 +137,11 @@ export default class ExplorerActiveChartButton extends React.Component {
}
ExplorerActiveChartButton.propTypes = {
- query: PropTypes.object.describe(
- 'Current query being rendered.'
- ),
+ query: PropTypes.object.describe('Current query being rendered.'),
queries: PropTypes.arrayOf(PropTypes.object).describe(
'An array of queryGroups. Each querygroup should include the following fields: name, children. ' +
- 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
- 'name, query, (optional)preset.'
+ 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
+ 'name, query, (optional)preset.'
),
onSave: PropTypes.func.describe(
'Function to be called when an analytics query is sucessfully composed.'
@@ -153,14 +152,12 @@ ExplorerActiveChartButton.propTypes = {
onDismiss: PropTypes.func.describe(
'Function to be called when current chart is being dismissed from list of active charts.'
),
- color: PropTypes.string.describe(
- 'The color of the checkbox and the chart to be rendered.'
- ),
+ color: PropTypes.string.describe('The color of the checkbox and the chart to be rendered.'),
disableDropdown: PropTypes.bool.describe(
'If set to true, disable dropdown to pick/compose the query.'
),
isTimeSeries: PropTypes.bool.describe(
'If set to true, add default grouping (day, hour) and aggregate to the composer. ' +
- 'Otherwise, render limit inside the composer.'
- )
-}
+ 'Otherwise, render limit inside the composer.'
+ ),
+};
diff --git a/src/components/ExplorerMenuButton/ExplorerMenuButton.example.js b/src/components/ExplorerMenuButton/ExplorerMenuButton.example.js
index 1246cfb74b..b72b753bb5 100644
--- a/src/components/ExplorerMenuButton/ExplorerMenuButton.example.js
+++ b/src/components/ExplorerMenuButton/ExplorerMenuButton.example.js
@@ -6,119 +6,126 @@
* the root directory of this source tree.
*/
import ExplorerMenuButton from 'components/ExplorerMenuButton/ExplorerMenuButton.react';
-import React from 'react';
+import React from 'react';
export const component = ExplorerMenuButton;
export const demos = [
{
render: () => {
- let queries = [
+ const queries = [
{
name: 'Audience',
children: [
{
name: 'Daily Active Installations',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Daily Active Users',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Monthly Active Installations',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Monthly Active Users',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Core',
children: [
{
name: 'Gogo Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'User Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Installation Count',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Events',
children: [
{
name: 'API Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Analytics Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'File Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Notifications',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'App Opens',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Opens',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Recent Queries',
children: [
{
name: 'User Count Aggregate',
- query: { }
- }
- ]
- }, {
+ query: {},
+ },
+ ],
+ },
+ {
name: 'Saved Queries',
children: [
{
name: 'Gogo Queries',
- query: { }
+ query: {},
},
{
name: 'Saved Queries',
- query: { }
- }
- ]
- }
+ query: {},
+ },
+ ],
+ },
];
return (
{/* Do nothing */}} />
+ value="Add query"
+ onSave={() => {
+ /* Do nothing */
+ }}
+ />
);
- }
- }
-]
+ },
+ },
+];
diff --git a/src/components/ExplorerMenuButton/ExplorerMenuButton.react.js b/src/components/ExplorerMenuButton/ExplorerMenuButton.react.js
index 254bb5bdb2..b5526c55b5 100644
--- a/src/components/ExplorerMenuButton/ExplorerMenuButton.react.js
+++ b/src/components/ExplorerMenuButton/ExplorerMenuButton.react.js
@@ -5,14 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { Directions } from 'lib/Constants';
+import { Directions } from 'lib/Constants';
import ExplorerQueryComposer from 'components/ExplorerQueryComposer/ExplorerQueryComposer.react';
-import ExplorerQueryPicker from 'components/ExplorerQueryPicker/ExplorerQueryPicker.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/ExplorerMenuButton/ExplorerMenuButton.scss';
+import ExplorerQueryPicker from 'components/ExplorerQueryPicker/ExplorerQueryPicker.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/ExplorerMenuButton/ExplorerMenuButton.scss';
export default class ExplorerMenuButton extends React.Component {
constructor() {
@@ -21,7 +21,7 @@ export default class ExplorerMenuButton extends React.Component {
// can be null, 'picker', or 'composer'
currentView: null,
position: null,
- align: Directions.LEFT
+ align: Directions.LEFT,
};
this.wrapRef = React.createRef();
@@ -32,7 +32,7 @@ export default class ExplorerMenuButton extends React.Component {
if (this.state.currentView) {
return { currentView: null };
}
- let position = Position.inDocument(this.wrapRef.current);
+ const position = Position.inDocument(this.wrapRef.current);
let align = Directions.LEFT;
if (position.x > 700) {
position.x += this.wrapRef.current.clientWidth;
@@ -41,7 +41,7 @@ export default class ExplorerMenuButton extends React.Component {
return {
currentView: 'picker',
position,
- align
+ align,
};
});
}
@@ -71,11 +71,11 @@ export default class ExplorerMenuButton extends React.Component {
render() {
let popover = null;
- let content = this.renderButton();
+ const content = this.renderButton();
if (this.state.currentView) {
let queryMenu = null;
- let classes = [styles.queryMenuContainer];
+ const classes = [styles.queryMenuContainer];
let calloutStyle = { marginLeft: '10px' };
if (this.state.align === Directions.RIGHT) {
classes.push(styles.right);
@@ -88,10 +88,11 @@ export default class ExplorerMenuButton extends React.Component {
{
- this.setState({ currentView: 'composer' })
+ this.setState({ currentView: 'composer' });
}}
onSelect={this.handleSelect.bind(this)}
- onDelete={this.handleDelete.bind(this)} />
+ onDelete={this.handleDelete.bind(this)}
+ />
);
break;
case 'composer':
@@ -99,15 +100,14 @@ export default class ExplorerMenuButton extends React.Component {
+ onSave={this.handleSave.bind(this)}
+ />
);
break;
}
popover = (
-
+
{content}
@@ -130,20 +130,16 @@ ExplorerMenuButton.propTypes = {
value: PropTypes.string.describe('The label of the button.'),
queries: PropTypes.arrayOf(PropTypes.object).describe(
'An array of queryGroups. Each querygroup should include the following fields: name, children. ' +
- 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
- 'name, query, (optional)preset.'
+ 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
+ 'name, query, (optional)preset.'
),
onSave: PropTypes.func.describe(
'Function to be called when an analytics query is sucessfully composed.'
),
- onSelect: PropTypes.func.describe(
- 'Function to be called when a query is being selected.'
- ),
- onDelete: PropTypes.func.describe(
- 'Function to be called when a query is being deleted.'
- ),
+ onSelect: PropTypes.func.describe('Function to be called when a query is being selected.'),
+ onDelete: PropTypes.func.describe('Function to be called when a query is being deleted.'),
isTimeSeries: PropTypes.bool.describe(
'If set to true, add default grouping (day, hour) and aggregate to the composer. ' +
- 'Otherwise, render limit inside the composer.'
- )
+ 'Otherwise, render limit inside the composer.'
+ ),
};
diff --git a/src/components/ExplorerQueryComposer/ExplorerFilter.js b/src/components/ExplorerQueryComposer/ExplorerFilter.js
index ba876c054e..e48ac27536 100644
--- a/src/components/ExplorerQueryComposer/ExplorerFilter.js
+++ b/src/components/ExplorerQueryComposer/ExplorerFilter.js
@@ -6,49 +6,49 @@
* the root directory of this source tree.
*/
export const Constraints = {
- '$eq': {
+ $eq: {
name: 'equals',
},
- '$ne': {
+ $ne: {
name: 'does not equal',
},
- '$lt': {
+ $lt: {
name: 'less than',
field: 'Number',
composable: true,
},
- '$le': {
+ $le: {
name: 'less than or equal',
field: 'Number',
composable: true,
},
- '$gt': {
+ $gt: {
name: 'greater than',
field: 'Number',
composable: true,
},
- '$ge': {
+ $ge: {
name: 'greater than or equal',
field: 'Number',
composable: true,
},
- '$contains': {
+ $contains: {
name: 'contains string',
field: 'String',
composable: true,
},
- 'json_extract_scalar': {
+ json_extract_scalar: {
name: 'json',
field: 'JSON',
- composable: true
- }
+ composable: true,
+ },
};
export const FieldConstraints = {
- 'Boolean': [ '$eq' ],
- 'Number': [ '$eq', '$ne', '$lt', '$le', '$gt', '$ge' ],
- 'String': [ '$eq', '$ne', '$contains' ],
- 'Date': [ '$eq', '$ne', '$lt', '$le', '$gt', '$ge' ],
- 'JSON': [ '$eq', '$ne', 'json_extract_scalar' ],
- 'JSONValue': [ '$eq', '$ne' ]
+ Boolean: ['$eq'],
+ Number: ['$eq', '$ne', '$lt', '$le', '$gt', '$ge'],
+ String: ['$eq', '$ne', '$contains'],
+ Date: ['$eq', '$ne', '$lt', '$le', '$gt', '$ge'],
+ JSON: ['$eq', '$ne', 'json_extract_scalar'],
+ JSONValue: ['$eq', '$ne'],
};
diff --git a/src/components/ExplorerQueryComposer/ExplorerQueryComposer.example.js b/src/components/ExplorerQueryComposer/ExplorerQueryComposer.example.js
index c5efbad358..e2b5d014f2 100644
--- a/src/components/ExplorerQueryComposer/ExplorerQueryComposer.example.js
+++ b/src/components/ExplorerQueryComposer/ExplorerQueryComposer.example.js
@@ -6,7 +6,7 @@
* the root directory of this source tree.
*/
import ExplorerQueryComposer from 'components/ExplorerQueryComposer/ExplorerQueryComposer.react';
-import React from 'react';
+import React from 'react';
export const component = ExplorerQueryComposer;
@@ -15,28 +15,24 @@ export const demos = [
name: 'New query',
render: () => (
-
+
- )
+ ),
},
{
name: 'Old query, but no name',
render: () => (
-
+
- )
+ ),
},
{
name: 'Query composer that shows grouping and aggregate',
render: () => (
-
+
- )
- }
-]
+ ),
+ },
+];
diff --git a/src/components/ExplorerQueryComposer/ExplorerQueryComposer.react.js b/src/components/ExplorerQueryComposer/ExplorerQueryComposer.react.js
index 046ae0f232..552786ffd8 100644
--- a/src/components/ExplorerQueryComposer/ExplorerQueryComposer.react.js
+++ b/src/components/ExplorerQueryComposer/ExplorerQueryComposer.react.js
@@ -5,17 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Button from 'components/Button/Button.react';
+import baseStyles from 'stylesheets/base.scss';
+import Button from 'components/Button/Button.react';
import ChromeDropdown from 'components/ChromeDropdown/ChromeDropdown.react';
-import DateTimeEntry from 'components/DateTimeEntry/DateTimeEntry.react';
-import {
- Constraints,
- FieldConstraints
-} from 'components/ExplorerQueryComposer/ExplorerFilter';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/ExplorerQueryComposer/ExplorerQueryComposer.scss';
+import DateTimeEntry from 'components/DateTimeEntry/DateTimeEntry.react';
+import { Constraints, FieldConstraints } from 'components/ExplorerQueryComposer/ExplorerFilter';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/ExplorerQueryComposer/ExplorerQueryComposer.scss';
const TABLE_SOURCES_LABEL = ['API Event', 'Custom Event'];
@@ -32,7 +29,7 @@ const FIELD_LABELS = {
'App Build Version',
'App Display Version',
'Timestamp (s)',
- 'Latency (s)'
+ 'Latency (s)',
],
'Custom Event': [
'Event Name',
@@ -45,63 +42,65 @@ const FIELD_LABELS = {
'OS Version',
'App Build Version',
'App Display Version',
- 'Timestamp (s)'
- ]
+ 'Timestamp (s)',
+ ],
};
const AGGREGATE_TYPE_LABELS = [
- 'Count', 'Count Distinct', 'Sum', 'Minimum', 'Median', '99th Percentile', 'Average'
+ 'Count',
+ 'Count Distinct',
+ 'Sum',
+ 'Minimum',
+ 'Median',
+ '99th Percentile',
+ 'Average',
];
-const REQUIRED_GROUPING_LABELS = [
- 'Time (day)', 'Time (hour)'
-];
+const REQUIRED_GROUPING_LABELS = ['Time (day)', 'Time (hour)'];
-const ORDER_LABELS = [
- 'Ascending', 'Descending'
-];
+const ORDER_LABELS = ['Ascending', 'Descending'];
const FIELD_TYPE = {
- 'Request Type' : 'String',
- 'Class' : 'String',
- 'Event Name' : 'String',
+ 'Request Type': 'String',
+ Class: 'String',
+ 'Event Name': 'String',
/* eslint-disable no-constant-condition */
- 'Dimensions' : false ? 'JSON' : 'String', //In progress features. Change false to true to work on this feature.
+ Dimensions: false ? 'JSON' : 'String', //In progress features. Change false to true to work on this feature.
/* eslint-enable */
- 'Installation ID' : 'String',
- 'Parse User ID' : 'String',
- 'Parse SDK' : 'String',
- 'Parse SDK Version' : 'String',
- 'OS' : 'String',
- 'OS Version' : 'String',
- 'App Build Version' : 'String',
- 'App Display Version' : 'String',
- 'Timestamp (s)' : 'Date',
- 'Latency (s)' : 'Number',
+ 'Installation ID': 'String',
+ 'Parse User ID': 'String',
+ 'Parse SDK': 'String',
+ 'Parse SDK Version': 'String',
+ OS: 'String',
+ 'OS Version': 'String',
+ 'App Build Version': 'String',
+ 'App Display Version': 'String',
+ 'Timestamp (s)': 'Date',
+ 'Latency (s)': 'Number',
};
-let availableFilters = {};
-for (let field in FIELD_TYPE) {
+const availableFilters = {};
+for (const field in FIELD_TYPE) {
availableFilters[field] = FieldConstraints[FIELD_TYPE[field]];
}
const TIMESERIES_DEFAULT_STATE = {
groups: [REQUIRED_GROUPING_LABELS[0]],
- limit: undefined
+ limit: undefined,
};
const NON_TIMESERIES_DEFAULT_STATE = {
aggregates: [],
groups: [],
- limit: 100
+ limit: 100,
};
-let constraintLookup = {};
-for (let c in Constraints) {
+const constraintLookup = {};
+for (const c in Constraints) {
constraintLookup[Constraints[c].name] = c;
}
-let setFocus = (input) => {
+const setFocus = input => {
if (input !== null) {
input.focus();
}
@@ -111,17 +110,50 @@ function validateNumeric() {
return true;
}
-let fieldView = (type, value, onChangeValue) => {
- let fieldStyle = { width: '32%', marginLeft: '1%', display: 'inline-block' };
+const fieldView = (type, value, onChangeValue) => {
+ const fieldStyle = {
+ width: '32%',
+ marginLeft: '1%',
+ display: 'inline-block',
+ };
switch (type) {
case null:
return null;
case 'String':
- return
onChangeValue(e.target.value)} ref={setFocus}/>;
+ return (
+
onChangeValue(e.target.value)}
+ ref={setFocus}
+ />
+ );
case 'Number':
- return
onChangeValue(validateNumeric(e.target.value) ? e.target.value : (value || ''))} />;
+ return (
+
+ onChangeValue(validateNumeric(e.target.value) ? e.target.value : value || '')
+ }
+ />
+ );
case 'Date':
- return
;
+ return (
+
+
+
+ );
default:
throw new Error('Incompatible type ' + type + ' used to render fieldView.');
}
@@ -131,26 +163,28 @@ export default class ExplorerQueryComposer extends React.Component {
constructor(props) {
super();
- let initialState = this.getInitialStateFromProps(props);
+ const initialState = this.getInitialStateFromProps(props);
this.state = {
// newName is used to revert the edit if cancelled.
newName: '',
editing: false,
isSaved: false,
- ...initialState
+ ...initialState,
};
}
getInitialStateFromProps(props) {
- let query = props.query || {};
+ const query = props.query || {};
let defaultState = {};
if (props.isTimeSeries) {
defaultState = {
...TIMESERIES_DEFAULT_STATE,
- aggregates: [{
- op: AGGREGATE_TYPE_LABELS[0],
- col: FIELD_LABELS[TABLE_SOURCES_LABEL[0]][0]
- }]
+ aggregates: [
+ {
+ op: AGGREGATE_TYPE_LABELS[0],
+ col: FIELD_LABELS[TABLE_SOURCES_LABEL[0]][0],
+ },
+ ],
};
} else {
defaultState = NON_TIMESERIES_DEFAULT_STATE;
@@ -163,23 +197,23 @@ export default class ExplorerQueryComposer extends React.Component {
groups: query.groups || defaultState.groups,
limit: query.limit || defaultState.limit,
filters: query.filters || [],
- orders: query.orders || []
+ orders: query.orders || [],
};
}
getOrderOptions() {
- let options = [];
+ const options = [];
this.state.aggregates.forEach((value, index) => {
options.push({
key: 'aggregate|' + index,
- value: [value.col, value.op].join(' ')
+ value: [value.col, value.op].join(' '),
});
});
this.state.groups.forEach((value, index) => {
options.push({
key: 'group|' + index,
- value: value
+ value: value,
});
});
@@ -187,19 +221,19 @@ export default class ExplorerQueryComposer extends React.Component {
}
componentWillReceiveProps(nextProps) {
- let initialState = this.getInitialStateFromProps(nextProps);
+ const initialState = this.getInitialStateFromProps(nextProps);
this.setState({ ...initialState });
}
toggleEditing() {
this.setState({
editing: !this.state.editing,
- newName: this.state.name
+ newName: this.state.name,
});
}
handleSave() {
- let query = this.props.query || {};
+ const query = this.props.query || {};
this.props.onSave({
source: this.state.source,
name: this.state.name,
@@ -208,15 +242,15 @@ export default class ExplorerQueryComposer extends React.Component {
limit: this.state.limit,
filters: this.state.filters,
// Only pass them if order is valid
- orders: this.state.orders.filter((order) => order.col !== null && order.col !== undefined),
+ orders: this.state.orders.filter(order => order.col !== null && order.col !== undefined),
localId: query.localId,
- objectId: query.objectId
+ objectId: query.objectId,
});
this.setState({
editing: false,
name: this.state.newName,
- isSaved: !!this.state.newName
+ isSaved: !!this.state.newName,
});
}
@@ -226,45 +260,49 @@ export default class ExplorerQueryComposer extends React.Component {
handleAddAggregate() {
this.setState({
- aggregates: this.state.aggregates.concat([{
- op: AGGREGATE_TYPE_LABELS[0],
- col: FIELD_LABELS[this.state.source][0]
- }])
+ aggregates: this.state.aggregates.concat([
+ {
+ op: AGGREGATE_TYPE_LABELS[0],
+ col: FIELD_LABELS[this.state.source][0],
+ },
+ ]),
});
}
handleAddGroup() {
this.setState({
- groups: this.state.groups.concat([
- FIELD_LABELS[this.state.source][0]
- ])
+ groups: this.state.groups.concat([FIELD_LABELS[this.state.source][0]]),
});
}
handleAddFilter() {
this.setState({
- filters: this.state.filters.concat([{
- op: '$eq',
- col: FIELD_LABELS[this.state.source][0],
- val: null
- }])
+ filters: this.state.filters.concat([
+ {
+ op: '$eq',
+ col: FIELD_LABELS[this.state.source][0],
+ val: null,
+ },
+ ]),
});
}
handleAddOrder() {
this.setState({
- orders: this.state.orders.concat([{
- col: null,
- asc: ORDER_LABELS[0]
- }])
+ orders: this.state.orders.concat([
+ {
+ col: null,
+ asc: ORDER_LABELS[0],
+ },
+ ]),
});
}
handleSourceChange(newSource) {
- let initialState = this.getInitialStateFromProps(this.props);
+ const initialState = this.getInitialStateFromProps(this.props);
this.setState({
...initialState,
- source: newSource
+ source: newSource,
});
}
@@ -273,15 +311,16 @@ export default class ExplorerQueryComposer extends React.Component {
this.setState({ [stateKey]: this.state[stateKey] });
}
- renderAggregate(aggregate, index=0) {
+ renderAggregate(aggregate, index = 0) {
let deleteButton = null;
- if (!this.props.isTimeSeries || index !== 0 ) {
+ if (!this.props.isTimeSeries || index !== 0) {
deleteButton = (
+ onClick={this.removeAdditionalQuery.bind(this, 'aggregates', index)}
+ >
×
@@ -295,20 +334,23 @@ export default class ExplorerQueryComposer extends React.Component {
{
- let aggregates = this.state.aggregates;
+ onChange={val => {
+ const aggregates = this.state.aggregates;
aggregates[index] = {
op: val,
- col: FIELD_LABELS[this.state.source][0]
+ col: FIELD_LABELS[this.state.source][0],
};
this.setState({ aggregates });
}}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
-
of
+
+ of
+
{
@@ -321,13 +363,14 @@ export default class ExplorerQueryComposer extends React.Component {
return true;
}
})}
- onChange={(val) => {
- let aggregates = this.state.aggregates;
+ onChange={val => {
+ const aggregates = this.state.aggregates;
aggregates[index].col = val;
this.setState({ aggregates });
}}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
{deleteButton}
@@ -335,16 +378,17 @@ export default class ExplorerQueryComposer extends React.Component {
);
}
- renderGroup(grouping, index=0) {
+ renderGroup(grouping, index = 0) {
let deleteButton = null;
- let specialGroup = this.props.isTimeSeries && index === 0;
+ const specialGroup = this.props.isTimeSeries && index === 0;
if (!specialGroup) {
deleteButton = (
+ onClick={this.removeAdditionalQuery.bind(this, 'groups', index)}
+ >
×
@@ -357,25 +401,28 @@ export default class ExplorerQueryComposer extends React.Component {
{
- let groups = this.state.groups;
+ onChange={val => {
+ const groups = this.state.groups;
groups[index] = val;
this.setState({ groups });
}}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
{deleteButton}
);
}
- renderFilter(filter, index=0) {
- let type = Object.prototype.hasOwnProperty.call(Constraints[filter.op], 'field') ? Constraints[filter.op].field : FIELD_TYPE[filter.col];
+ renderFilter(filter, index = 0) {
+ const type = Object.prototype.hasOwnProperty.call(Constraints[filter.op], 'field')
+ ? Constraints[filter.op].field
+ : FIELD_TYPE[filter.col];
let constraintView = null;
if (type === 'JSON') {
- let isJSONView = filter.op === 'json_extract_scalar';
+ const isJSONView = filter.op === 'json_extract_scalar';
let jsonView = null;
if (isJSONView) {
@@ -384,64 +431,67 @@ export default class ExplorerQueryComposer extends React.Component {
jsonView = (
Constraints[c].name)}
- onChange={(val) => {
- let filters = this.state.filters;
+ options={FieldConstraints['JSONValue'].map(c => Constraints[c].name)}
+ onChange={val => {
+ const filters = this.state.filters;
filters[index] = {
col: filter.col,
op: filter.op,
json_path: filter.json_path,
json_scalar_op: constraintLookup[val],
- val: filter.val
+ val: filter.val,
};
this.setState({ filters });
- }} />
+ }}
+ />
{
- let filters = this.state.filters;
+ onChange={e => {
+ const filters = this.state.filters;
filters[index] = {
col: filter.col,
op: filter.op,
json_path: filter.json_path,
json_scalar_op: filter.json_scalar_op,
- val: e.target.value
+ val: e.target.value,
};
this.setState({ filters });
- }} />
+ }}
+ />
);
}
- let constraintInputValue = isJSONView ? filter.json_path : filter.val;
+ const constraintInputValue = isJSONView ? filter.json_path : filter.val;
constraintView = (
Constraints[c].name)}
- onChange={(val) => {
- let filters = this.state.filters;
+ options={availableFilters[filter.col].map(c => Constraints[c].name)}
+ onChange={val => {
+ const filters = this.state.filters;
filters[index] = {
col: filter.col,
op: constraintLookup[val],
- val: filter.val
+ val: filter.val,
};
this.setState({ filters });
- }} />
+ }}
+ />
{
- let filters = this.state.filters;
+ onChange={e => {
+ const filters = this.state.filters;
let newFilter = null;
if (isJSONView) {
newFilter = {
@@ -449,19 +499,20 @@ export default class ExplorerQueryComposer extends React.Component {
op: filter.op,
val: filter.val,
json_path: e.target.value,
- json_scalar_op: filter.json_scalar_op
+ json_scalar_op: filter.json_scalar_op,
};
} else {
newFilter = {
col: filter.col,
op: filter.op,
- val: e.target.value
- }
+ val: e.target.value,
+ };
}
filters[index] = newFilter;
this.setState({ filters });
}}
- ref={setFocus} />
+ ref={setFocus}
+ />
{jsonView}
@@ -472,25 +523,26 @@ export default class ExplorerQueryComposer extends React.Component {
Constraints[c].name)}
- onChange={(val) => {
- let filters = this.state.filters;
+ options={availableFilters[filter.col].map(c => Constraints[c].name)}
+ onChange={val => {
+ const filters = this.state.filters;
filters[index] = {
col: filter.col,
op: constraintLookup[val],
- val: null
+ val: null,
};
this.setState({ filters });
- }} />
+ }}
+ />
- {fieldView(type, filter.val, (val) => {
- let filters = this.state.filters;
+ {fieldView(type, filter.val, val => {
+ const filters = this.state.filters;
filters[index] = {
col: filter.col,
op: filter.op,
- val: val
+ val: val,
};
this.setState({ filters });
})}
@@ -503,28 +555,30 @@ export default class ExplorerQueryComposer extends React.Component {
Filter
{
- let filters = this.state.filters;
+ onChange={val => {
+ const filters = this.state.filters;
filters[index] = {
col: val,
op: '$eq',
- val: null
+ val: null,
};
this.setState({ filters });
- }} />
+ }}
+ />
{constraintView}
+ onClick={this.removeAdditionalQuery.bind(this, 'filters', index)}
+ >
×
@@ -538,38 +592,41 @@ export default class ExplorerQueryComposer extends React.Component {
Sort by
{
- let orders = this.state.orders;
+ onChange={val => {
+ const orders = this.state.orders;
orders[index] = {
col: val,
- asc: ORDER_LABELS[0]
+ asc: ORDER_LABELS[0],
};
this.setState({ orders });
}}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
{
- let orders = this.state.orders;
+ onChange={val => {
+ const orders = this.state.orders;
orders[index].asc = val;
this.setState({ orders });
}}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
+ onClick={this.removeAdditionalQuery.bind(this, 'orders', index)}
+ >
×
@@ -579,7 +636,8 @@ export default class ExplorerQueryComposer extends React.Component {
}
render() {
- let { query, isNew, isTimeSeries, onDismiss } = this.props;
+ let { query } = this.props;
+ const { isNew, isTimeSeries, onDismiss } = this.props;
query = query || {};
// First and foremost, let's not waste time if the query itself is not composable.
@@ -594,11 +652,12 @@ export default class ExplorerQueryComposer extends React.Component {
+ onClick={onDismiss}
+ />
@@ -611,24 +670,27 @@ export default class ExplorerQueryComposer extends React.Component {
headerView = (
+ placeholder={'Give your query a name'}
+ />
- { this.state.isSaved ? 'Rename' : 'Save' }
+ onClick={this.handleSave.bind(this)}
+ >
+ {this.state.isSaved ? 'Rename' : 'Save'}
+ onClick={this.toggleEditing.bind(this)}
+ >
Cancel
@@ -637,14 +699,15 @@ export default class ExplorerQueryComposer extends React.Component {
} else {
headerView = (
-
{ this.state.name || 'Build a custom query' }
- { isNew ? null : (
+
{this.state.name || 'Build a custom query'}
+ {isNew ? null : (
- { this.state.isSaved ? 'Rename' : 'Save' }
+ onClick={this.toggleEditing.bind(this)}
+ >
+ {this.state.isSaved ? 'Rename' : 'Save'}
)}
@@ -665,9 +728,7 @@ export default class ExplorerQueryComposer extends React.Component {
);
group = (
-
- {this.renderGroup(this.state.groups[0])}
-
+
{this.renderGroup(this.state.groups[0])}
);
} else {
// On table/json view, we hide aggregate and group. And we also show limit.
@@ -676,11 +737,12 @@ export default class ExplorerQueryComposer extends React.Component {
);
@@ -692,22 +754,24 @@ export default class ExplorerQueryComposer extends React.Component {
));
}
- let offset = isTimeSeries ? 1 : 0;
- let extraAggregateModels = isTimeSeries ? this.state.aggregates.slice(1) : this.state.aggregates;
- let extraAggregates = extraAggregateModels.map((aggregate, i) => (
+ const offset = isTimeSeries ? 1 : 0;
+ const extraAggregateModels = isTimeSeries
+ ? this.state.aggregates.slice(1)
+ : this.state.aggregates;
+ const extraAggregates = extraAggregateModels.map((aggregate, i) => (
{this.renderAggregate(aggregate, i + offset)}
));
- let extraGroupModels = isTimeSeries ? this.state.groups.slice(1) : this.state.groups;
- let extraGroups = extraGroupModels.map((group, i) => (
+ const extraGroupModels = isTimeSeries ? this.state.groups.slice(1) : this.state.groups;
+ const extraGroups = extraGroupModels.map((group, i) => (
{this.renderGroup(group, i + offset)}
));
- let filters = this.state.filters.map((filter, i) => (
+ const filters = this.state.filters.map((filter, i) => (
{this.renderFilter(filter, i)}
@@ -718,10 +782,11 @@ export default class ExplorerQueryComposer extends React.Component {
sortButton = (
+ width="100%"
+ value="Order by"
+ color="white"
+ onClick={this.handleAddOrder.bind(this)}
+ />
);
}
@@ -731,30 +796,28 @@ export default class ExplorerQueryComposer extends React.Component {
footerButton = (
+ onClick={this.handleSave.bind(this)}
+ />
);
} else {
footerButton = (
-
+
+ onClick={this.handleSave.bind(this)}
+ />
);
@@ -762,9 +825,7 @@ export default class ExplorerQueryComposer extends React.Component {
return (
-
- {headerView}
-
+
{headerView}
@@ -773,8 +834,9 @@ export default class ExplorerQueryComposer extends React.Component {
value={this.state.source}
options={TABLE_SOURCES_LABEL}
onChange={this.handleSourceChange.bind(this)}
- color='blue'
- width='100%' />
+ color="blue"
+ width="100%"
+ />
@@ -790,54 +852,49 @@ export default class ExplorerQueryComposer extends React.Component {
+ width="100%"
+ value="Add aggregate"
+ color="white"
+ onClick={this.handleAddAggregate.bind(this)}
+ />
+ width="100%"
+ value="Add grouping"
+ color="white"
+ onClick={this.handleAddGroup.bind(this)}
+ />
+ width="100%"
+ value="Filter input rows"
+ color="white"
+ onClick={this.handleAddFilter.bind(this)}
+ />
{sortButton}
-
- {footerButton}
-
+
{footerButton}
);
}
}
ExplorerQueryComposer.propTypes = {
- query: PropTypes.object.describe(
- 'The query to be edited by this composer.'
- ),
- onSave: PropTypes.func.isRequired.describe(
- 'Function to be called on query created/saved.'
- ),
- onDismiss: PropTypes.func.describe(
- 'Function to be called on dismiss button clicked.'
- ),
+ query: PropTypes.object.describe('The query to be edited by this composer.'),
+ onSave: PropTypes.func.isRequired.describe('Function to be called on query created/saved.'),
+ onDismiss: PropTypes.func.describe('Function to be called on dismiss button clicked.'),
isNew: PropTypes.bool.describe(
'True if the composer is trying to compose a new query. ' +
- 'False if the composer is editing an existing one.'
+ 'False if the composer is editing an existing one.'
),
isTimeSeries: PropTypes.bool.describe(
'If set to true, add default group (day, hour) and aggregate to the composer. ' +
- 'Otherwise, render limit inside the composer.'
- )
-}
+ 'Otherwise, render limit inside the composer.'
+ ),
+};
diff --git a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js
index 22d2610fe9..a0063799c8 100644
--- a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js
+++ b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.example.js
@@ -6,115 +6,126 @@
* the root directory of this source tree.
*/
import ExplorerQueryPicker from 'components/ExplorerQueryPicker/ExplorerQueryPicker.react';
-import React from 'react';
+import React from 'react';
export const component = ExplorerQueryPicker;
export const demos = [
{
render: () => {
- let queries = [
+ const queries = [
{
name: 'Audience',
children: [
{
name: 'Daily Active Installations',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Daily Active Users',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Monthly Active Installations',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Monthly Active Users',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Core',
children: [
{
name: 'Gogo Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'User Count',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Installation Count',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Events',
children: [
{
name: 'API Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Analytics Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'File Requests',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Notifications',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'App Opens',
- query: { },
- preset: true
+ query: {},
+ preset: true,
},
{
name: 'Push Opens',
- query: { },
- preset: true
- }
- ]
- }, {
+ query: {},
+ preset: true,
+ },
+ ],
+ },
+ {
name: 'Recent Queries',
children: [
{
name: 'User Count Aggregate',
- query: { }
- }
- ]
- }, {
+ query: {},
+ },
+ ],
+ },
+ {
name: 'Saved Queries',
children: [
{
name: 'Gogo Queries',
- query: { }
+ query: {},
},
{
name: 'Saved Queries',
- query: { }
- }
- ]
- }
+ query: {},
+ },
+ ],
+ },
];
- return {/* Do nothing */}} />
- }
- }
+ return (
+ {
+ /* Do nothing */
+ }}
+ />
+ );
+ },
+ },
];
diff --git a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js
index 51a76b9ad6..24ac44ee47 100644
--- a/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js
+++ b/src/components/ExplorerQueryPicker/ExplorerQueryPicker.react.js
@@ -5,40 +5,38 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Button from 'components/Button/Button.react';
+import baseStyles from 'stylesheets/base.scss';
+import Button from 'components/Button/Button.react';
import CascadingView from 'components/CascadingView/CascadingView.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/ExplorerQueryPicker/ExplorerQueryPicker.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/ExplorerQueryPicker/ExplorerQueryPicker.scss';
-let ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => {
+const ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => {
return (
Choose a query to visualize
- {queries.map((queryGroup) => {
+ {queries.map(queryGroup => {
let childrenView = null;
if (queryGroup.children.length > 0) {
childrenView = queryGroup.children.map((query, j) => {
return (
-
+
onSelect(query)}
- className={styles.queryLabel}>
+ className={styles.queryLabel}
+ >
{query.name}
- {query.preset ? null : onDelete(query)}
- className={styles.del}>
- ×
- }
+ {query.preset ? null : (
+ onDelete(query)} className={styles.del}>
+ ×
+
+ )}
);
});
@@ -47,31 +45,23 @@ let ExplorerQueryPicker = ({ queries, onCompose, onSelect, onDelete }) => {
if (!emptyMessage) {
emptyMessage = `No query found in ${queryGroup.name}.`;
}
- childrenView = (
-
- {emptyMessage}
-
- );
+ childrenView =
{emptyMessage}
;
}
return (
+ key={queryGroup.name}
+ >
{childrenView}
);
})}
@@ -83,16 +73,12 @@ export default ExplorerQueryPicker;
ExplorerQueryPicker.propTypes = {
queries: PropTypes.arrayOf(PropTypes.object).isRequired.describe(
'An array of queryGroups. Each querygroup should include the following fields: name, children. ' +
- 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
- 'name, query, (optional)preset.'
+ 'children of queryGroup contains an array of queries. Each query should include the following fields: ' +
+ 'name, query, (optional)preset.'
),
onCompose: PropTypes.func.isRequired.describe(
'Function to be called when "Build a custom query" button is clicked.'
),
- onSelect: PropTypes.func.describe(
- 'Function to be called when a query is being selected.'
- ),
- onDelete: PropTypes.func.describe(
- 'Function to be called when a query is being deleted.'
- )
+ onSelect: PropTypes.func.describe('Function to be called when a query is being selected.'),
+ onDelete: PropTypes.func.describe('Function to be called when a query is being deleted.'),
};
diff --git a/src/components/Field/Field.example.js b/src/components/Field/Field.example.js
index 8cb9068ca4..23de039927 100644
--- a/src/components/Field/Field.example.js
+++ b/src/components/Field/Field.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Toggle from 'components/Toggle/Toggle.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Toggle from 'components/Toggle/Toggle.react';
import TextInput from 'components/TextInput/TextInput.react';
export const component = Field;
@@ -17,17 +17,26 @@ export const demos = [
{
render: () => (
}
- input={
{}} />} />
- )
+ label={
+
+ }
+ input={ {}} />}
+ />
+ ),
},
{
render: () => (
}
- input={ {}} />} />
- )
- }
+ label={
+
+ }
+ input={ {}} />}
+ />
+ ),
+ },
];
diff --git a/src/components/Field/Field.react.js b/src/components/Field/Field.react.js
index 53706bd2b9..7c9f06b617 100644
--- a/src/components/Field/Field.react.js
+++ b/src/components/Field/Field.react.js
@@ -6,27 +6,27 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/Field/Field.scss';
+import React from 'react';
+import styles from 'components/Field/Field.scss';
-let Field = ({label, input, labelWidth = 50, labelPadding, height, className}) => {
- let classes = [styles.field];
+const Field = ({ label, input, labelWidth = 50, labelPadding, height, className }) => {
+ const classes = [styles.field];
if (className) {
classes.push(className);
}
labelWidth = labelWidth || 50;
if (label && labelPadding) {
- label = React.cloneElement(
- label,
- { ...label.props, padding: labelPadding }
- );
+ label = React.cloneElement(label, {
+ ...label.props,
+ padding: labelPadding,
+ });
}
return (
{label}
-
@@ -38,15 +38,13 @@ export default Field;
Field.propTypes = {
label: PropTypes.node.describe(
'The label content, placed on the left side of the Field. ' +
- 'It can be any renderable content.'
+ 'It can be any renderable content.'
),
input: PropTypes.node.describe(
'The input content, placed on the right side of the Field. ' +
- 'It can be any renderable content.'
- ),
- className: PropTypes.string.describe(
- 'A CSS class name to add to this field'
+ 'It can be any renderable content.'
),
+ className: PropTypes.string.describe('A CSS class name to add to this field'),
labelWidth: PropTypes.number.describe(
'A percentage value for the width of the left label. It cannot be 0.'
),
@@ -55,5 +53,5 @@ Field.propTypes = {
),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).describe(
'The height of the field. Can be a string containing any CSS unit, or a number of pixels. By default, it will expand to fit it\u2019s content, with a min-height of 80px.'
- )
+ ),
};
diff --git a/src/components/Field/Field.scss b/src/components/Field/Field.scss
index 116a490828..2978470710 100644
--- a/src/components/Field/Field.scss
+++ b/src/components/Field/Field.scss
@@ -14,6 +14,7 @@
border-width: 1px 1px 0 1px;
min-height: 80px;
background: white;
+ display: flex;
&:last-of-type {
border-bottom-width: 1px;
@@ -25,16 +26,8 @@
}
.left {
- position: absolute;
- left: 0;
- height: 100%;
-}
-
-.centered {
- @include transform(translateY(-50%));
- position: absolute;
- width: 100%;
- top: 50%;
+ display: flex;
+ align-items: center;
}
.right {
@@ -46,6 +39,7 @@
display: flex;
justify-content: center;
align-items: center;
+ flex: 1
}
diff --git a/src/components/Fieldset/Fieldset.example.js b/src/components/Fieldset/Fieldset.example.js
index 22521366e8..cb83ab0432 100644
--- a/src/components/Fieldset/Fieldset.example.js
+++ b/src/components/Fieldset/Fieldset.example.js
@@ -5,24 +5,23 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
import Fieldset from 'components/Fieldset/Fieldset.react';
-import Label from 'components/Label/Label.react';
-import Toggle from 'components/Toggle/Toggle.react';
+import Label from 'components/Label/Label.react';
+import Toggle from 'components/Toggle/Toggle.react';
export const component = Fieldset;
export const demos = [
{
render: () => (
-
+
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/Fieldset/Fieldset.react.js b/src/components/Fieldset/Fieldset.react.js
index 62dee6d53d..bcab28ebaf 100644
--- a/src/components/Fieldset/Fieldset.react.js
+++ b/src/components/Fieldset/Fieldset.react.js
@@ -6,16 +6,14 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/Fieldset/Fieldset.scss';
+import React from 'react';
+import styles from 'components/Fieldset/Fieldset.scss';
-let Fieldset = ({ legend, description, children }) => (
+const Fieldset = ({ legend, description, children }) => (
{legend}
{description}
-
- {children}
-
+
{children}
);
@@ -27,5 +25,5 @@ Fieldset.propTypes = {
),
description: PropTypes.node.describe(
'The secondary header of the Fieldset. It can be any renderable content.'
- )
+ ),
};
diff --git a/src/components/FileEditor/FileEditor.react.js b/src/components/FileEditor/FileEditor.react.js
index abc87b654b..71c0af55fb 100644
--- a/src/components/FileEditor/FileEditor.react.js
+++ b/src/components/FileEditor/FileEditor.react.js
@@ -15,7 +15,7 @@ export default class FileEditor extends React.Component {
super();
this.state = {
- value: props.value
+ value: props.value,
};
this.checkExternalClick = this.checkExternalClick.bind(this);
@@ -28,7 +28,7 @@ export default class FileEditor extends React.Component {
componentDidMount() {
document.body.addEventListener('click', this.checkExternalClick);
document.body.addEventListener('keypress', this.handleKey);
- let fileInputElement = document.getElementById('fileInput');
+ const fileInputElement = document.getElementById('fileInput');
if (fileInputElement) {
fileInputElement.click();
}
@@ -68,9 +68,9 @@ export default class FileEditor extends React.Component {
}
async handleChange(e) {
- let file = e.target.files[0];
+ const file = e.target.files[0];
if (file) {
- let base64 = await this.getBase64(file);
+ const base64 = await this.getBase64(file);
this.props.onCommit(new Parse.File(file.name, { base64 }));
}
}
@@ -78,9 +78,18 @@ export default class FileEditor extends React.Component {
render() {
const file = this.props.value;
return (
-
+
diff --git a/src/components/FileInput/FileInput.example.js b/src/components/FileInput/FileInput.example.js
index ae85f567c4..d46e13ae35 100644
--- a/src/components/FileInput/FileInput.example.js
+++ b/src/components/FileInput/FileInput.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import Label from 'components/Label/Label.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import Label from 'components/Label/Label.react';
import FileInput from 'components/FileInput/FileInput.react';
export const component = FileInput;
@@ -18,18 +18,27 @@ export const demos = [
render: () => (
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/FileInput/FileInput.react.js b/src/components/FileInput/FileInput.react.js
index cff626fd95..a2ee18a830 100644
--- a/src/components/FileInput/FileInput.react.js
+++ b/src/components/FileInput/FileInput.react.js
@@ -5,13 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import { escape } from 'lib/StringEscaping';
-import styles from 'components/FileInput/FileInput.scss';
+import styles from 'components/FileInput/FileInput.scss';
export default class FileInput extends React.Component {
handleChange(e) {
- let file = e.target.files[0];
+ const file = e.target.files[0];
this.props.onChange(file);
}
@@ -27,10 +27,7 @@ export default class FileInput extends React.Component {
}
if (this.props.value.name && this.props.value.url) {
return (
-
+
{escape(this.props.value.name)}
);
@@ -38,7 +35,7 @@ export default class FileInput extends React.Component {
}
render() {
- let inputProps = {
+ const inputProps = {
type: 'file',
value: '',
disabled: this.props.disabled,
@@ -47,13 +44,13 @@ export default class FileInput extends React.Component {
if (this.props.accept) {
inputProps.accept = this.props.accept;
}
- let label = this.renderLabel();
- let buttonStyles = [styles.button];
+ const label = this.renderLabel();
+ const buttonStyles = [styles.button];
if (this.props.disabled || this.props.uploading) {
buttonStyles.push(styles.disabled);
}
if (label) {
- buttonStyles.push(styles.withLabel)
+ buttonStyles.push(styles.withLabel);
}
return (
diff --git a/src/components/FileTree/FileTree.react.js b/src/components/FileTree/FileTree.react.js
index 656a18fccf..fbfe0970c5 100644
--- a/src/components/FileTree/FileTree.react.js
+++ b/src/components/FileTree/FileTree.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
+import Icon from 'components/Icon/Icon.react';
import { Link } from 'react-router-dom';
-import React from 'react';
-import styles from 'components/FileTree/FileTree.scss';
+import React from 'react';
+import styles from 'components/FileTree/FileTree.scss';
export default class FileTree extends React.Component {
constructor(props) {
@@ -16,7 +16,7 @@ export default class FileTree extends React.Component {
let open = !props.name;
if (props.current && props.name) {
- let dirPath = (props.prefix || '') + props.name + '/';
+ const dirPath = (props.prefix || '') + props.name + '/';
if (props.current.startsWith(dirPath)) {
open = true;
}
@@ -31,8 +31,16 @@ export default class FileTree extends React.Component {
let dir = null;
if (this.props.name) {
dir = (
-
this.setState((state) => ({ open: !state.open }))}>
-
+
this.setState(state => ({ open: !state.open }))}
+ >
+
{this.props.name}
);
@@ -40,12 +48,12 @@ export default class FileTree extends React.Component {
let content = null;
if (this.state.open) {
- let dirs = {};
- let files = [];
- this.props.files.forEach((f) => {
- let folderEnd = f.indexOf('/');
+ const dirs = {};
+ const files = [];
+ this.props.files.forEach(f => {
+ const folderEnd = f.indexOf('/');
if (folderEnd > -1) {
- let folder = f.substr(0, folderEnd);
+ const folder = f.substr(0, folderEnd);
if (!dirs[folder]) {
dirs[folder] = [];
}
@@ -54,27 +62,29 @@ export default class FileTree extends React.Component {
files.push(f);
}
});
- let folders = Object.keys(dirs);
+ const folders = Object.keys(dirs);
folders.sort();
content = (
- {folders.map((f) => (
+ {folders.map(f => (
+ current={this.props.current}
+ />
))}
- {files.map((f) => {
- let path = (this.props.name ? this.props.prefix + this.props.name + '/' : '') + f;
- let isCurrent = this.props.current === path;
+ {files.map(f => {
+ const path = (this.props.name ? this.props.prefix + this.props.name + '/' : '') + f;
+ const isCurrent = this.props.current === path;
return (
+ to={{ pathname: this.props.linkPrefix + path }}
+ >
{f}
);
diff --git a/src/components/Filter/Filter.react.js b/src/components/Filter/Filter.react.js
index d7bfd75e27..1d12def83b 100644
--- a/src/components/Filter/Filter.react.js
+++ b/src/components/Filter/Filter.react.js
@@ -5,11 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as Filters from 'lib/Filters';
-import { List, Map } from 'immutable';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import stringCompare from 'lib/stringCompare';
+import * as Filters from 'lib/Filters';
+import { List, Map } from 'immutable';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import stringCompare from 'lib/stringCompare';
import { CurrentApp } from 'context/currentApp';
function changeField(schema, filters, index, newField) {
@@ -22,27 +22,27 @@ function changeField(schema, filters, index, newField) {
const newFilter = new Map({
field: newField,
constraint: useExisting ? constraint : Filters.FieldConstraints[schema[newField].type][0],
- compareTo: (useExisting && typeof defaultCompare === typeof compare) ? compare : defaultCompare
+ compareTo: useExisting && typeof defaultCompare === typeof compare ? compare : defaultCompare,
});
return filters.set(index, newFilter);
}
function changeConstraint(schema, filters, index, newConstraint, prevCompareTo) {
- let field = filters.get(index).get('field');
+ const field = filters.get(index).get('field');
let compareType = schema[field].type;
if (Object.prototype.hasOwnProperty.call(Filters.Constraints[newConstraint], 'field')) {
compareType = Filters.Constraints[newConstraint].field;
}
- let newFilter = new Map({
+ const newFilter = new Map({
field: field,
constraint: newConstraint,
- compareTo: prevCompareTo ?? Filters.DefaultComparisons[compareType]
- })
+ compareTo: prevCompareTo ?? Filters.DefaultComparisons[compareType],
+ });
return filters.set(index, newFilter);
}
function changeCompareTo(schema, filters, index, type, newCompare) {
- let newValue = newCompare;
+ const newValue = newCompare;
return filters.set(index, filters.get(index).set('compareTo', newValue));
}
@@ -50,24 +50,26 @@ function deleteRow(filters, index) {
return filters.delete(index);
}
-let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, className }) => {
+const Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, className }) => {
const currentApp = React.useContext(CurrentApp);
blacklist = blacklist || [];
- let available = Filters.availableFilters(schema, filters);
+ const available = Filters.availableFilters(schema, filters);
return (
{filters.toArray().map((filter, i) => {
- let field = filter.get('field');
- let constraint = filter.get('constraint');
- let compareTo = filter.get('compareTo');
+ const field = filter.get('field');
+ const constraint = filter.get('constraint');
+ const compareTo = filter.get('compareTo');
- let fields = Object.keys(available).concat([]);
+ const fields = Object.keys(available).concat([]);
if (fields.indexOf(field) < 0) {
fields.push(field);
}
// Get the column preference of the current class.
- const currentColumnPreference = currentApp.columnPreference ? currentApp.columnPreference[className] : null;
+ const currentColumnPreference = currentApp.columnPreference
+ ? currentApp.columnPreference[className]
+ : null;
// Check if the preference exists.
if (currentColumnPreference) {
@@ -78,7 +80,7 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class
fields.sort((a, b) => {
// Only "a" should sorted to the top.
if (fieldsToSortToTop.includes(a) && !fieldsToSortToTop.includes(b)) {
- return -1
+ return -1;
}
// Only "b" should sorted to the top.
if (!fieldsToSortToTop.includes(a) && fieldsToSortToTop.includes(b)) {
@@ -96,7 +98,9 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class
fields.sort();
}
- let constraints = Filters.FieldConstraints[schema[field].type].filter((c) => blacklist.indexOf(c) < 0);
+ const constraints = Filters.FieldConstraints[schema[field].type].filter(
+ c => blacklist.indexOf(c) < 0
+ );
let compareType = schema[field].type;
if (Object.prototype.hasOwnProperty.call(Filters.Constraints[constraint], 'field')) {
compareType = Filters.Constraints[constraint].field;
@@ -122,14 +126,14 @@ let Filter = ({ schema, filters, renderRow, onChange, onSearch, blacklist, class
onChangeCompareTo: newCompare => {
onChange(changeCompareTo(schema, filters, i, compareType, newCompare));
},
- onKeyDown: ({key}) => {
+ onKeyDown: ({ key }) => {
if (key === 'Enter') {
onSearch();
}
},
onDeleteRow: () => {
onChange(deleteRow(filters, i));
- }
+ },
});
})}
@@ -145,7 +149,5 @@ Filter.propTypes = {
filters: PropTypes.instanceOf(List).isRequired.describe(
'An array of filter objects. Each filter contains "field", "comparator", and "compareTo" fields.'
),
- renderRow: PropTypes.func.isRequired.describe(
- 'A function for rendering a row of a filter.'
- )
+ renderRow: PropTypes.func.isRequired.describe('A function for rendering a row of a filter.'),
};
diff --git a/src/components/FlowFooter/FlowFooter.react.js b/src/components/FlowFooter/FlowFooter.react.js
index 7477486490..126e4a9371 100644
--- a/src/components/FlowFooter/FlowFooter.react.js
+++ b/src/components/FlowFooter/FlowFooter.react.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import PropTypes from 'lib/PropTypes';
-import styles from 'components/FlowFooter/FlowFooter.scss';
+import styles from 'components/FlowFooter/FlowFooter.scss';
-let FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) => (
+const FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) => (
{secondary}
{primary}
-
+
{errorMessage || children}
@@ -23,19 +23,11 @@ let FlowFooter = ({ primary, secondary, errorMessage, borderTop, children }) =>
export default FlowFooter;
FlowFooter.propTypes = {
- primary: PropTypes.node.describe(
- 'A primary action Button.'
- ),
- secondary: PropTypes.node.describe(
- 'A secondary action Button.'
- ),
- errorMessage: PropTypes.node.describe(
- 'The error message of the flow.'
- ),
- borderTop: PropTypes.string.describe(
- 'Style override for footer border-top.'
- ),
+ primary: PropTypes.node.describe('A primary action Button.'),
+ secondary: PropTypes.node.describe('A secondary action Button.'),
+ errorMessage: PropTypes.node.describe('The error message of the flow.'),
+ borderTop: PropTypes.string.describe('Style override for footer border-top.'),
children: PropTypes.node.describe(
'The text of the footer.
tags will be rendered in bold.'
),
-}
+};
diff --git a/src/components/FlowView/FlowView.react.js b/src/components/FlowView/FlowView.react.js
index 275f272b4e..cf2a9dbbf3 100644
--- a/src/components/FlowView/FlowView.react.js
+++ b/src/components/FlowView/FlowView.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
+import Button from 'components/Button/Button.react';
import FlowFooter from 'components/FlowFooter/FlowFooter.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
import SaveButton from 'components/SaveButton/SaveButton.react';
export default class FlowView extends React.Component {
@@ -23,21 +23,21 @@ export default class FlowView extends React.Component {
}
componentWillReceiveProps(props) {
- let newChanges = {...this.state.changes};
- for (let k in props.initialFields) {
+ const newChanges = { ...this.state.changes };
+ for (const k in props.initialFields) {
if (this.state.changes[k] === props.initialFields[k]) {
delete newChanges[k];
}
}
- this.setState({changes: newChanges});
+ this.setState({ changes: newChanges });
}
currentFields() {
- let fields = {};
- for (let k in this.props.initialFields) {
+ const fields = {};
+ for (const k in this.props.initialFields) {
fields[k] = this.props.initialFields[k];
}
- for (let k in this.state.changes) {
+ for (const k in this.state.changes) {
fields[k] = this.state.changes[k];
}
return fields;
@@ -45,7 +45,7 @@ export default class FlowView extends React.Component {
setField(key, value, preserveSavingState = false) {
if (this.state.saveState !== SaveButton.States.SAVING) {
- let newChanges = {...this.state.changes};
+ const newChanges = { ...this.state.changes };
newChanges[key] = value;
if (newChanges[key] === this.props.initialFields[key]) {
delete newChanges[key];
@@ -72,7 +72,7 @@ export default class FlowView extends React.Component {
}
render() {
- let {
+ const {
inProgressText,
submitText,
showFooter = () => true,
@@ -82,24 +82,24 @@ export default class FlowView extends React.Component {
validate = () => '',
onSubmit,
afterSave = () => {},
- secondaryButton = () => ,
+ secondaryButton = () => (
+
+ ),
} = this.props;
- let {
- changes,
- saveState,
- saveError,
- } = this.state;
- let setField = this.setField.bind(this);
- let resetFields = this.resetFields.bind(this);
- let fields = this.currentFields();
- let form = renderForm({ fields, changes, setField, resetFields });
+ const { changes, saveState, saveError } = this.state;
+ const setField = this.setField.bind(this);
+ const resetFields = this.resetFields.bind(this);
+ const fields = this.currentFields();
+ const form = renderForm({ fields, changes, setField, resetFields });
- let invalidFormMessage = validate({ changes, fields });
- let hasFormValidationError = React.isValidElement(invalidFormMessage) || (invalidFormMessage && invalidFormMessage.length > 0);
+ const invalidFormMessage = validate({ changes, fields });
+ const hasFormValidationError =
+ React.isValidElement(invalidFormMessage) ||
+ (invalidFormMessage && invalidFormMessage.length > 0);
let errorMessage = '';
let footerMessage = null;
let shouldShowFooter = showFooter(changes);
@@ -120,47 +120,83 @@ export default class FlowView extends React.Component {
footerMessage = shouldShowFooter ? footerContents({ changes, fields }) : '';
}
- let saveButton = {
- this.setState({ saveState: SaveButton.States.SAVING });
- onSubmit({ changes, fields, setField, resetFields }).then(() => {
- this.setState({ saveState: SaveButton.States.SUCCEEDED });
- afterSave({ fields, setField, resetFields });
- }).catch(({ message, error, notice, errors = [] }) => {
- this.setState({
- saveState: SaveButton.States.FAILED,
- saveError: errors.join(' ') || message || error || notice || 'An error occurred',
- });
- });
- }}
- />;
+ const saveButton = (
+ {
+ this.setState({ saveState: SaveButton.States.SAVING });
+ onSubmit({ changes, fields, setField, resetFields })
+ .then(() => {
+ this.setState({ saveState: SaveButton.States.SUCCEEDED });
+ afterSave({ fields, setField, resetFields });
+ })
+ .catch(({ message, error, notice, errors = [] }) => {
+ this.setState({
+ saveState: SaveButton.States.FAILED,
+ saveError: errors.join(' ') || message || error || notice || 'An error occurred',
+ });
+ });
+ }}
+ />
+ );
- let footer = shouldShowFooter ?
- {footerMessage}
- : null;
+ const footer = shouldShowFooter ? (
+
+ {footerMessage}
+
+ ) : null;
- return {form}{footer}
;
+ return (
+
+ {form}
+ {footer}
+
+ );
}
}
FlowView.propTypes = {
- initialChanges: PropTypes.object.describe('A map of field names to their initial changed values (where applicable), used to seed the form'),
- initialFields: PropTypes.object.isRequired.describe('A map of field names to their starting values. For a creation form, this is probably an empty object.'),
- renderForm: PropTypes.func.isRequired.describe('A function used to render the body of the form. It receives an object with fields (the current form state), changes (an isolated set of changes), setField(name, value) (a method for updating a field), and resetFields().'),
- footerContents: PropTypes.func.isRequired.describe('A function that renders the message in the footer. It receives an object with fields (the current form state) and changes (an isolated set of changes). This will only be called if some changes have been made.'),
- inProgressText: PropTypes.string.describe('Text for commit button when request is in progress. Default is "Save changes".'),
- submitText: PropTypes.string.describe('Text for commit button when filling out form. Default is "Saving\u2026"'),
- onSubmit: PropTypes.func.describe('Function to call when submitting the FlowView. Must return a promise. It receives an object with fields (the current form state) and changes (an isolated set of changes).'),
- afterSave: PropTypes.func.describe('Function to call after saving succeeds. It receives the fields, setField(), and resetFields(). Use this if you require custom modification to fields after save succeeds (eg. in PushSettings we clear the GCM credentials fields after they are saved)'),
- validate: PropTypes.func.describe('Function that validates the form. If it returns a non-empty string, that string is display in the footer, and the submit button is disabled. You can return "use default" to disable the button and use the default message (not in red), but you really shouldn\'t.'),
- showFooter: PropTypes.func.describe('Recieves the changes, and returns false if the footer should be hidden. By default the footer shows if there are any changes.'),
- secondaryButton: PropTypes.func.describe('Overrride the cancel button by passing a function that returns your custom node. By default, the cancel button says "Cancel" and calls resetFields().'),
- defaultFooterMessage: PropTypes.node.describe('A message for the footer when the validate message is "use default"'),
+ initialChanges: PropTypes.object.describe(
+ 'A map of field names to their initial changed values (where applicable), used to seed the form'
+ ),
+ initialFields: PropTypes.object.isRequired.describe(
+ 'A map of field names to their starting values. For a creation form, this is probably an empty object.'
+ ),
+ renderForm: PropTypes.func.isRequired.describe(
+ 'A function used to render the body of the form. It receives an object with fields (the current form state), changes (an isolated set of changes), setField(name, value) (a method for updating a field), and resetFields().'
+ ),
+ footerContents: PropTypes.func.isRequired.describe(
+ 'A function that renders the message in the footer. It receives an object with fields (the current form state) and changes (an isolated set of changes). This will only be called if some changes have been made.'
+ ),
+ inProgressText: PropTypes.string.describe(
+ 'Text for commit button when request is in progress. Default is "Save changes".'
+ ),
+ submitText: PropTypes.string.describe(
+ 'Text for commit button when filling out form. Default is "Saving\u2026"'
+ ),
+ onSubmit: PropTypes.func.describe(
+ 'Function to call when submitting the FlowView. Must return a promise. It receives an object with fields (the current form state) and changes (an isolated set of changes).'
+ ),
+ afterSave: PropTypes.func.describe(
+ 'Function to call after saving succeeds. It receives the fields, setField(), and resetFields(). Use this if you require custom modification to fields after save succeeds (eg. in PushSettings we clear the GCM credentials fields after they are saved)'
+ ),
+ validate: PropTypes.func.describe(
+ 'Function that validates the form. If it returns a non-empty string, that string is display in the footer, and the submit button is disabled. You can return "use default" to disable the button and use the default message (not in red), but you really shouldn\'t.'
+ ),
+ showFooter: PropTypes.func.describe(
+ 'Recieves the changes, and returns false if the footer should be hidden. By default the footer shows if there are any changes.'
+ ),
+ secondaryButton: PropTypes.func.describe(
+ 'Overrride the cancel button by passing a function that returns your custom node. By default, the cancel button says "Cancel" and calls resetFields().'
+ ),
+ defaultFooterMessage: PropTypes.node.describe(
+ 'A message for the footer when the validate message is "use default"'
+ ),
};
diff --git a/src/components/FormButton/FormButton.example.js b/src/components/FormButton/FormButton.example.js
index fa40a84485..1f9e15ab55 100644
--- a/src/components/FormButton/FormButton.example.js
+++ b/src/components/FormButton/FormButton.example.js
@@ -5,25 +5,27 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
import FormButton from 'components/FormButton/FormButton.react';
-import Label from 'components/Label/Label.react';
+import Label from 'components/Label/Label.react';
export const component = FormButton;
export const demos = [
{
render: () => (
-
+
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/FormButton/FormButton.react.js b/src/components/FormButton/FormButton.react.js
index 61564b39df..b4a71ed1ea 100644
--- a/src/components/FormButton/FormButton.react.js
+++ b/src/components/FormButton/FormButton.react.js
@@ -9,13 +9,13 @@ import Button from 'components/Button/Button.react';
import React from 'react';
import styles from 'components/FormButton/FormButton.scss';
-let FormButton = (props) => (
+const FormButton = props => (
-
+
);
-let { ...otherPropTypes } = Button.propTypes;
+const { ...otherPropTypes } = Button.propTypes;
FormButton.propTypes = otherPropTypes;
export default FormButton;
diff --git a/src/components/FormModal/FormModal.react.js b/src/components/FormModal/FormModal.react.js
index eb375e6545..422a3f7e96 100644
--- a/src/components/FormModal/FormModal.react.js
+++ b/src/components/FormModal/FormModal.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import PropTypes from 'lib/PropTypes';
-import Modal from 'components/Modal/Modal.react';
-import FormNote from 'components/FormNote/FormNote.react';
+import Modal from 'components/Modal/Modal.react';
+import FormNote from 'components/FormNote/FormNote.react';
export default class FormModal extends React.Component {
constructor() {
@@ -20,7 +20,7 @@ export default class FormModal extends React.Component {
}
render() {
- let {
+ const {
children,
open,
submitText = 'Confirm',
@@ -33,7 +33,7 @@ export default class FormModal extends React.Component {
showErrors = true,
...modalProps
} = this.props;
- let showModal = open || this.state.inProgress;
+ const showModal = open || this.state.inProgress;
if (!modalProps.type) {
if (this.state.errorMessage.length > 0) {
modalProps.type = Modal.Types.DANGER;
@@ -41,57 +41,76 @@ export default class FormModal extends React.Component {
modalProps.type = Modal.Types.VALID;
}
}
- return (showModal ? {
- this.setState({
- errorMessage: '',
- inProgress: true,
- });
- onSubmit().then(result => {
+ return showModal ? (
+ {
+ this.setState({
+ errorMessage: '',
+ inProgress: true,
+ });
+ onSubmit()
+ .then(result => {
+ onClose();
+ clearFields();
+ onSuccess(result);
+ this.setState({ inProgress: false });
+ })
+ .catch(({ message, error, notice, errors = [] }) => {
+ this.setState({
+ errorMessage: errors.join(' ') || message || error || notice || 'An error occurred',
+ inProgress: false,
+ });
+ });
+ }}
+ onCancel={() => {
onClose();
clearFields();
- onSuccess(result);
- this.setState({inProgress: false});
- }).catch(({ message, error, notice, errors = [] }) => {
this.setState({
- errorMessage: errors.join(' ') || message || error || notice || 'An error occurred',
- inProgress: false,
+ errorMessage: '',
});
- });
- }}
- onCancel={() => {
- onClose();
- clearFields();
- this.setState({
- errorMessage: '',
- });
- }}
- disabled={!enabled}
- canCancel={!this.state.inProgress}
- progress={this.state.inProgress} >
- {children}
- {showErrors ? 0}
- color='red' >
- {this.state.errorMessage}
- : null}
- : null)
+ }}
+ disabled={!enabled}
+ canCancel={!this.state.inProgress}
+ progress={this.state.inProgress}
+ >
+ {children}
+ {showErrors ? (
+ 0} color="red">
+ {this.state.errorMessage}
+
+ ) : null}
+
+ ) : null;
}
}
-let { ...forwardedModalProps} = Modal.propTypes;
+const { ...forwardedModalProps } = Modal.propTypes;
FormModal.propTypes = {
...forwardedModalProps,
children: PropTypes.node.describe('The form elements to be rendered in the modal.'),
open: PropTypes.bool.isRequired.describe('Whether or not to show the modal.'),
- submitText: PropTypes.string.describe('The text to show on the CTA button. Defaults to "Confirm"'),
- inProgressText: PropTypes.string.describe('The text to show to the CTA button while the request is in flight. Defaults to "Confirming\u2026".'),
- onSubmit: PropTypes.func.isRequired.describe('A function that will be called when the user submits the form. This function must return a promise.'),
- onSuccess: PropTypes.func.describe('A function that will be called with the result of the promise created in onSubmit.'),
- onClose: PropTypes.func.isRequired.describe('A function that will be called when the modal is closing. After this function is called, the parent should not pass "true" to the "open" prop.'),
- clearFields: PropTypes.func.describe('A function that should clear the state of the form fields inside the modal.'),
+ submitText: PropTypes.string.describe(
+ 'The text to show on the CTA button. Defaults to "Confirm"'
+ ),
+ inProgressText: PropTypes.string.describe(
+ 'The text to show to the CTA button while the request is in flight. Defaults to "Confirming\u2026".'
+ ),
+ onSubmit: PropTypes.func.isRequired.describe(
+ 'A function that will be called when the user submits the form. This function must return a promise.'
+ ),
+ onSuccess: PropTypes.func.describe(
+ 'A function that will be called with the result of the promise created in onSubmit.'
+ ),
+ onClose: PropTypes.func.isRequired.describe(
+ 'A function that will be called when the modal is closing. After this function is called, the parent should not pass "true" to the "open" prop.'
+ ),
+ clearFields: PropTypes.func.describe(
+ 'A function that should clear the state of the form fields inside the modal.'
+ ),
enabled: PropTypes.bool.describe('Set to false to disable the confirm button.'),
- showErrors: PropTypes.bool.describe('Set to false to hide errors if you are doing custom error handling (such as migration and new app modals).'),
+ showErrors: PropTypes.bool.describe(
+ 'Set to false to hide errors if you are doing custom error handling (such as migration and new app modals).'
+ ),
};
diff --git a/src/components/FormNote/FormNote.react.js b/src/components/FormNote/FormNote.react.js
index d1c2879a74..9eb754b5f6 100644
--- a/src/components/FormNote/FormNote.react.js
+++ b/src/components/FormNote/FormNote.react.js
@@ -11,7 +11,7 @@ import React from 'react';
import SliderWrap from 'components/SliderWrap/SliderWrap.react';
import styles from 'components/FormNote/FormNote.scss';
-let FormNote = ({ show, children, color, ...other }) => (
+const FormNote = ({ show, children, color, ...other }) => (
{children}
@@ -19,7 +19,7 @@ let FormNote = ({ show, children, color, ...other }) => (
FormNote.propTypes = {
show: PropTypes.bool,
- color: PropTypes.oneOf(['blue', 'green', 'orange', 'red'])
+ color: PropTypes.oneOf(['blue', 'green', 'orange', 'red']),
};
export default FormNote;
diff --git a/src/components/FormTable/FormTable.example.js b/src/components/FormTable/FormTable.example.js
index 778dbb0a6b..f04429adcc 100644
--- a/src/components/FormTable/FormTable.example.js
+++ b/src/components/FormTable/FormTable.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import FormTable from 'components/FormTable/FormTable.react';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import FormTable from 'components/FormTable/FormTable.react';
+import Label from 'components/Label/Label.react';
+import React from 'react';
export const component = FormTable;
@@ -16,58 +16,67 @@ export const demos = [
{
render: () => (
}
- input={ }
+ input={
+ {
+ alert('Delete button clicked.');
},
- {
- key: 'important',
- value: 'pay attention',
- strong: true,
- }
- ],
- },
- {
- title: 'Title',
- color: 'red',
- onDelete: () => {alert('Delete button clicked.')},
- notes: [
- {
- key: 'foo',
- keyColor: 'red',
- value: 'bar',
- }
- ]
- }
- ]} />
- } />
- )
+ notes: [
+ {
+ key: 'foo',
+ keyColor: 'red',
+ value: 'bar',
+ },
+ ],
+ },
+ ]}
+ />
+ }
+ />
+ ),
},
{
render: () => (
}
- input={
- } />
- )
+ label={ }
+ input={
+
+ }
+ />
+ ),
},
];
diff --git a/src/components/FormTable/FormTable.react.js b/src/components/FormTable/FormTable.react.js
index 35cf9cf6d5..6bb2fef38f 100644
--- a/src/components/FormTable/FormTable.react.js
+++ b/src/components/FormTable/FormTable.react.js
@@ -9,39 +9,50 @@ import PropTypes from 'lib/PropTypes';
import React from 'react';
import styles from 'components/FormTable/FormTable.scss';
-let Row = ({
- title,
- notes = [],
- color = 'blue',
- onDelete,
- keyWidth
-}) => {
+const Row = ({ title, notes = [], color = 'blue', onDelete, keyWidth }) => {
return (
{title}
- {typeof onDelete === 'function' ? × : null}
+ {typeof onDelete === 'function' ? (
+
+ ×
+
+ ) : null}
{notes.map(({ key, keyColor = '', value, strong }, index) => {
- return
- {key}
- {strong ? {value} : {value} }
-
;
+ return (
+
+
+ {key}
+
+ {strong ? {value} : {value} }
+
+ );
})}
);
};
-let FormTable = ({ items, keyWidth = '70px' }) => (
+const FormTable = ({ items, keyWidth = '70px' }) => (
- {items.map((item, index) =>
)}
+ {items.map((item, index) => (
+
+ ))}
);
FormTable.propTypes = {
- items: PropTypes.array.isRequired.describe('An array of objects to go in the table. Each object should include a title (string), optional color (blue, red, green, orange), optional onDelete function, and notes (array). Each object in the notes array should contain a key (string), keyColor (blue, red, green, or orange), a value (string), and optionally strong (bool) to use a bold font for the value.'),
- keyWidth: PropTypes.string.describe('A CSS unit for the width of the key portion of the table. Defaults to 70px.'),
+ items: PropTypes.array.isRequired.describe(
+ 'An array of objects to go in the table. Each object should include a title (string), optional color (blue, red, green, orange), optional onDelete function, and notes (array). Each object in the notes array should contain a key (string), keyColor (blue, red, green, or orange), a value (string), and optionally strong (bool) to use a bold font for the value.'
+ ),
+ keyWidth: PropTypes.string.describe(
+ 'A CSS unit for the width of the key portion of the table. Defaults to 70px.'
+ ),
};
export default FormTable;
diff --git a/src/components/FourOhFour/FourOhFour.react.js b/src/components/FourOhFour/FourOhFour.react.js
index 2e53789d92..ae95fb8fcf 100644
--- a/src/components/FourOhFour/FourOhFour.react.js
+++ b/src/components/FourOhFour/FourOhFour.react.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import styles from 'components/FourOhFour/FourOhFour.scss';
+import React from 'react';
+import styles from 'components/FourOhFour/FourOhFour.scss';
import { withRouter } from 'lib/withRouter';
const EMOJI_COUNT = 30;
@@ -17,7 +17,7 @@ class FourOhFour extends React.Component {
super();
this.state = {
- emoji: (Math.random() * EMOJI_COUNT) | 0
+ emoji: (Math.random() * EMOJI_COUNT) | 0,
};
this.timeout = null;
this.updateEmoji = this.updateEmoji.bind(this);
@@ -47,12 +47,15 @@ class FourOhFour extends React.Component {
Oh no, we can't find that page!
- this.props.navigate(-1)}>Go back
+ this.props.navigate(-1)}>
+ Go back
+
diff --git a/src/components/GeoPointEditor/GeoPointEditor.react.js b/src/components/GeoPointEditor/GeoPointEditor.react.js
index 597bc529ee..76ec915aa3 100644
--- a/src/components/GeoPointEditor/GeoPointEditor.react.js
+++ b/src/components/GeoPointEditor/GeoPointEditor.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { GeoPoint } from 'parse';
-import React from 'react';
-import styles from 'components/GeoPointEditor/GeoPointEditor.scss';
+import { GeoPoint } from 'parse';
+import React from 'react';
+import styles from 'components/GeoPointEditor/GeoPointEditor.scss';
import validateNumeric from 'lib/validateNumeric';
export default class GeoPointEditor extends React.Component {
@@ -54,16 +54,19 @@ export default class GeoPointEditor extends React.Component {
checkExternalClick() {
// timeout needed because activeElement is set after onBlur event is done
- setTimeout(function() {
- // check if activeElement is something else from input fields,
- // to avoid commiting new value on every switch of focus beetween latitude and longitude fields
- if (
- document.activeElement !== this.latitudeRef.current &&
- document.activeElement !== this.longitudeRef.current
- ) {
- this.commitValue();
- }
- }.bind(this), 1);
+ setTimeout(
+ function () {
+ // check if activeElement is something else from input fields,
+ // to avoid commiting new value on every switch of focus beetween latitude and longitude fields
+ if (
+ document.activeElement !== this.latitudeRef.current &&
+ document.activeElement !== this.longitudeRef.current
+ ) {
+ this.commitValue();
+ }
+ }.bind(this),
+ 1
+ );
}
handleKeyLatitude(e) {
@@ -88,7 +91,11 @@ export default class GeoPointEditor extends React.Component {
if (typeof this.state.longitude === 'string') {
longitude = parseFloat(this.state.longitude);
}
- if (this.props.value && latitude === this.props.value.latitude && longitude === this.props.value.longitude) {
+ if (
+ this.props.value &&
+ latitude === this.props.value.latitude &&
+ longitude === this.props.value.longitude
+ ) {
// No change, return the original object
this.props.onCommit(this.props.value);
} else {
@@ -102,35 +109,39 @@ export default class GeoPointEditor extends React.Component {
}
render() {
- let onChange = (target, e) => {
- let value = e.target.value;
+ const onChange = (target, e) => {
+ const value = e.target.value;
if (!validateNumeric(value)) {
- var values = value.split(',');
-
- if (values.length == 2) {
- values = values.map(val => val.trim());
-
- if (values[0].length > 0 && validateNumeric(values[0])) {
-
- if (values[1].length <= 0 || !validateNumeric(values[1])) {
- this.setState({ latitude: values[0] });
- this.longitudeRef.current.focus();
- this.longitudeRef.current.setSelectionRange(0, String(this.state.longitude).length);
- return;
- }
-
- if (validateNumeric(values[1])) {
- this.setState({ latitude: values[0] });
- this.setState({ longitude: values[1] });
- this.longitudeRef.current.focus();
- return;
- }
+ const regex = /[[("' ]?(?[0-9.]+)["' ]?,["' ]?(?[0-9.]+)["' )\]]?/;
+ const match = regex.exec(value);
+
+ if (!match) {
+ return null;
+ }
+
+ const values = [match.groups.x, match.groups.y];
+
+ if (values[0].length > 0 && validateNumeric(values[0])) {
+ if (values[1].length <= 0 || !validateNumeric(values[1])) {
+ this.setState({ latitude: values[0] });
+ this.longitudeRef.current.focus();
+ this.longitudeRef.current.setSelectionRange(0, String(this.state.longitude).length);
+ return;
+ }
+
+ if (validateNumeric(values[1])) {
+ this.setState({ latitude: values[0] });
+ this.setState({ longitude: values[1] });
+ this.longitudeRef.current.focus();
+ return;
}
}
}
- this.setState({ [target]: validateNumeric(value) ? value : this.state[target] });
+ this.setState({
+ [target]: validateNumeric(value) ? value : this.state[target],
+ });
};
return (
@@ -138,12 +149,14 @@ export default class GeoPointEditor extends React.Component {
ref={this.latitudeRef}
value={this.state.latitude}
onBlur={this.checkExternalClick}
- onChange={onChange.bind(this, 'latitude')} />
+ onChange={onChange.bind(this, 'latitude')}
+ />
+ onChange={onChange.bind(this, 'longitude')}
+ />
);
}
diff --git a/src/components/GeoPointInput/GeoPointInput.react.js b/src/components/GeoPointInput/GeoPointInput.react.js
index c80484519b..6d81d883c0 100644
--- a/src/components/GeoPointInput/GeoPointInput.react.js
+++ b/src/components/GeoPointInput/GeoPointInput.react.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/GeoPointInput/GeoPointInput.scss';
export default class GeoPointInput extends React.Component {
@@ -16,7 +16,7 @@ export default class GeoPointInput extends React.Component {
}
this.props.onChange({
latitude: e.target.value,
- longitude: this.props.value ? this.props.value.longitude : '0.0'
+ longitude: this.props.value ? this.props.value.longitude : '0.0',
});
}
@@ -27,12 +27,12 @@ export default class GeoPointInput extends React.Component {
}
this.props.onChange({
latitude: this.props.value ? this.props.value.latitude : '0.0',
- longitude: e.target.value
+ longitude: e.target.value,
});
}
render() {
- let value = this.props.value || { latitude: '0.0', longitude: '0.0' };
+ const value = this.props.value || { latitude: '0.0', longitude: '0.0' };
return (
);
diff --git a/src/components/Icon/Icon.example.js b/src/components/Icon/Icon.example.js
index 94bd04711a..25fd8c3862 100644
--- a/src/components/Icon/Icon.example.js
+++ b/src/components/Icon/Icon.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
+import Icon from 'components/Icon/Icon.react';
import React from 'react';
export const component = Icon;
@@ -14,11 +14,11 @@ export const demos = [
{
render: () => (
-
-
-
-
+
+
+
+
- )
- }
+ ),
+ },
];
diff --git a/src/components/Icon/Icon.react.js b/src/components/Icon/Icon.react.js
index e32e65eabb..750e80806b 100644
--- a/src/components/Icon/Icon.react.js
+++ b/src/components/Icon/Icon.react.js
@@ -6,18 +6,18 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import React from 'react';
-let Icon = ({ name, fill, width, height }) => {
- let props = {
+const Icon = ({ name, fill, width, height }) => {
+ const props = {
width: width,
- height: height
+ height: height,
};
if (fill) {
props.fill = fill;
}
return (
-
+
);
@@ -26,16 +26,8 @@ let Icon = ({ name, fill, width, height }) => {
export default Icon;
Icon.propTypes = {
- name: PropTypes.string.isRequired.describe(
- 'The icon name. This will be the name found in the '
- ),
- width: PropTypes.number.isRequired.describe(
- 'The icon width, in pixels.'
- ),
- height: PropTypes.number.isRequired.describe(
- 'The icon height, in pixels.'
- ),
- fill: PropTypes.string.describe(
- 'A valid color, used as the fill property for the SVG.'
- )
+ name: PropTypes.string.isRequired.describe('The icon name. This will be the name found in the '),
+ width: PropTypes.number.isRequired.describe('The icon width, in pixels.'),
+ height: PropTypes.number.isRequired.describe('The icon height, in pixels.'),
+ fill: PropTypes.string.describe('A valid color, used as the fill property for the SVG.'),
};
diff --git a/src/components/InlineSubmitInput/InlineSubmitInput.example.js b/src/components/InlineSubmitInput/InlineSubmitInput.example.js
index 1309237305..91d561e2a4 100644
--- a/src/components/InlineSubmitInput/InlineSubmitInput.example.js
+++ b/src/components/InlineSubmitInput/InlineSubmitInput.example.js
@@ -18,18 +18,23 @@ export const demos = [
render: () => (
}
- input={ alert('submitting: ' + v)}
- submitButtonText='ADD' />} />
+ label={ }
+ input={
+ alert('submitting: ' + v)} submitButtonText="ADD" />
+ }
+ />
}
- input={ v.length > 5}
- placeholder='Must be longer than 5 letters'
- onSubmit={(v) => alert('submitting: ' + v)}
- submitButtonText='ADD' />} />
+ label={ }
+ input={
+ v.length > 5}
+ placeholder="Must be longer than 5 letters"
+ onSubmit={v => alert('submitting: ' + v)}
+ submitButtonText="ADD"
+ />
+ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/InlineSubmitInput/InlineSubmitInput.react.js b/src/components/InlineSubmitInput/InlineSubmitInput.react.js
index e8563090a7..f92bb2aa79 100644
--- a/src/components/InlineSubmitInput/InlineSubmitInput.react.js
+++ b/src/components/InlineSubmitInput/InlineSubmitInput.react.js
@@ -29,7 +29,7 @@ export default class InlineSubmitInput extends React.Component {
}
handleSubmit() {
- this.props.onSubmit(this.state.value).then((shouldClearInput) => {
+ this.props.onSubmit(this.state.value).then(shouldClearInput => {
if (shouldClearInput) {
this.handleInputChange('');
}
@@ -43,14 +43,14 @@ export default class InlineSubmitInput extends React.Component {
+ onChange={this.handleInputChange.bind(this)}
+ />
- { this.state.showButton ?
-
+ {this.state.showButton ? (
+
{this.props.submitButtonText}
- : null
- }
+ ) : null}
);
}
@@ -66,7 +66,5 @@ InlineSubmitInput.propTypes = {
validate: PropTypes.func.describe(
'A function fired when the input is changed. It receives the new value as its only parameter, and must return a boolean for whether to show the submit button.'
),
- placeholder: PropTypes.string.describe(
- 'A placeholder string, for when the input is empty'
- ),
+ placeholder: PropTypes.string.describe('A placeholder string, for when the input is empty'),
};
diff --git a/src/components/IntervalInput/IntervalInput.example.js b/src/components/IntervalInput/IntervalInput.example.js
index 043e3e2db6..2e5999e176 100644
--- a/src/components/IntervalInput/IntervalInput.example.js
+++ b/src/components/IntervalInput/IntervalInput.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import IntervalInput from 'components/IntervalInput/IntervalInput.react';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import IntervalInput from 'components/IntervalInput/IntervalInput.react';
+import Label from 'components/Label/Label.react';
+import React from 'react';
export const component = IntervalInput;
@@ -27,7 +27,8 @@ class IntervalDemo extends React.Component {
+ onChange={this.handleChange.bind(this)}
+ />
);
}
}
@@ -35,11 +36,9 @@ class IntervalDemo extends React.Component {
export const demos = [
{
render: () => (
-
-
}
- input={
} />
+
+ } input={ } />
- )
- }
+ ),
+ },
];
diff --git a/src/components/IntervalInput/IntervalInput.react.js b/src/components/IntervalInput/IntervalInput.react.js
index 97afccbe7b..8de75595fa 100644
--- a/src/components/IntervalInput/IntervalInput.react.js
+++ b/src/components/IntervalInput/IntervalInput.react.js
@@ -10,28 +10,32 @@ import Option from 'components/Dropdown/Option.react';
import PropTypes from 'lib/PropTypes';
import React from 'react';
-let IntervalInput = ({ count, unit, onChange }) => {
- let counts = [];
- let max = (unit === 'hour') ? 23 : 59;
+const IntervalInput = ({ count, unit, onChange }) => {
+ const counts = [];
+ const max = unit === 'hour' ? 23 : 59;
for (let i = 1; i <= max; i++) {
- counts.push(
{String(i)} );
+ counts.push(
+
+ {String(i)}
+
+ );
}
- let countChange = (newCount) => onChange(parseInt(newCount, 10), unit);
- let unitChange = (newUnit) => {
+ const countChange = newCount => onChange(parseInt(newCount, 10), unit);
+ const unitChange = newUnit => {
if (newUnit === 'minute') {
return onChange(count, newUnit);
} else {
return onChange(Math.min(23, count), newUnit);
}
- }
+ };
return (
-
+
{counts}
-
+
{count === 1 ? 'Minute' : 'Minutes'}
{count === 1 ? 'Hour' : 'Hours'}
@@ -44,5 +48,5 @@ export default IntervalInput;
IntervalInput.propTypes = {
count: PropTypes.number.isRequired,
unit: PropTypes.oneOf(['minute', 'hour']),
- onChange: PropTypes.func.isRequired
+ onChange: PropTypes.func.isRequired,
};
diff --git a/src/components/JsonPrinter/JsonPrinter.example.js b/src/components/JsonPrinter/JsonPrinter.example.js
index 449ad2978d..639128aea1 100644
--- a/src/components/JsonPrinter/JsonPrinter.example.js
+++ b/src/components/JsonPrinter/JsonPrinter.example.js
@@ -5,26 +5,29 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
-import React from 'react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
+import React from 'react';
export const component = JsonPrinter;
-let json = {
+const json = {
results: [
{
name: 'Apples',
description: 'Blah blah blah...',
count: 12,
edible: true,
- lengthyProp: 'This is a really long sentence that hopefully fills up the entire width of the container, and demonstrates overflow.'
- }, {
+ lengthyProp:
+ 'This is a really long sentence that hopefully fills up the entire width of the container, and demonstrates overflow.',
+ },
+ {
name: 'Bananas',
description: 'Blah blah blah...',
count: 3,
edible: true,
- }, {
+ },
+ {
name: 'Rocks',
description: 'Blah blah blah...',
count: 44,
@@ -32,18 +35,18 @@ let json = {
acquiredAt: {
name: 'Dusty Flea Market',
address: '1 Main St',
- phone: '555-555-1234'
- }
- }
- ]
+ phone: '555-555-1234',
+ },
+ },
+ ],
};
export const demos = [
{
render: () => (
-
+
- )
- }
+ ),
+ },
];
diff --git a/src/components/JsonPrinter/JsonPrinter.react.js b/src/components/JsonPrinter/JsonPrinter.react.js
index fddf5a75c3..a4e904fa8f 100644
--- a/src/components/JsonPrinter/JsonPrinter.react.js
+++ b/src/components/JsonPrinter/JsonPrinter.react.js
@@ -6,21 +6,20 @@
* the root directory of this source tree.
*/
import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
-let JsonPrinter = ({ object }) => (
+const JsonPrinter = ({ object }) => (
-)
+ fullPage={false}
+ />
+);
export default JsonPrinter;
JsonPrinter.propTypes = {
- object: PropTypes.any.describe(
- 'The JavaScript object to stringify and print.'
- )
+ object: PropTypes.any.describe('The JavaScript object to stringify and print.'),
};
diff --git a/src/components/KeyField/KeyField.example.js b/src/components/KeyField/KeyField.example.js
index 2288d00d3d..c9f22ad96c 100644
--- a/src/components/KeyField/KeyField.example.js
+++ b/src/components/KeyField/KeyField.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import Field from 'components/Field/Field.react';
import Fieldset from 'components/Fieldset/Fieldset.react';
import KeyField from 'components/KeyField/KeyField.react';
@@ -16,14 +16,16 @@ export const component = KeyField;
export const demos = [
{
render: () => (
-
+
}
- input={abc123qwerty4567890 } />
+ label={ }
+ input={abc123qwerty4567890 }
+ />
}
- input={abc123qwerty4567890 } />
+ label={ }
+ input={abc123qwerty4567890 }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/KeyField/KeyField.react.js b/src/components/KeyField/KeyField.react.js
index 2d1945eecd..449f57eebb 100644
--- a/src/components/KeyField/KeyField.react.js
+++ b/src/components/KeyField/KeyField.react.js
@@ -6,16 +6,16 @@
* the root directory of this source tree.
*/
import FormButton from 'components/FormButton/FormButton.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/KeyField/KeyField.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/KeyField/KeyField.scss';
export default class KeyField extends React.Component {
constructor(props) {
super();
this.state = {
- hidden: props.hidden
+ hidden: props.hidden,
};
}
@@ -32,7 +32,8 @@ export default class KeyField extends React.Component {
return (
+ onClick={this.show.bind(this)}
+ />
);
}
return {this.props.children}
;
@@ -42,6 +43,10 @@ export default class KeyField extends React.Component {
KeyField.propTypes = {
children: PropTypes.node.describe('The contents of the field. Ideally, this is an app key.'),
hidden: PropTypes.bool.describe('Determines whether the field is initially hidden'),
- name: PropTypes.string.describe('If the field is initially hidden, this name will be used in the button used to show it. If the value is NAME, the button will contain the text "Show NAME Key"'),
- whenHiddenText: PropTypes.string.describe('Use this instead of "name" if you aren\'t showing a key.'),
+ name: PropTypes.string.describe(
+ 'If the field is initially hidden, this name will be used in the button used to show it. If the value is NAME, the button will contain the text "Show NAME Key"'
+ ),
+ whenHiddenText: PropTypes.string.describe(
+ 'Use this instead of "name" if you aren\'t showing a key.'
+ ),
};
diff --git a/src/components/Label/Label.example.js b/src/components/Label/Label.example.js
index 2869cdd7a3..a7eb1f2c96 100644
--- a/src/components/Label/Label.example.js
+++ b/src/components/Label/Label.example.js
@@ -15,8 +15,9 @@ export const demos = [
{
render: () => (
}
- input={null} />
- )
- }
+ label={ }
+ input={null}
+ />
+ ),
+ },
];
diff --git a/src/components/Label/Label.react.js b/src/components/Label/Label.react.js
index efc32129f2..d48f5d276c 100644
--- a/src/components/Label/Label.react.js
+++ b/src/components/Label/Label.react.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import fieldStyles from 'components/Field/Field.scss';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/Label/Label.scss';
+import fieldStyles from 'components/Field/Field.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/Label/Label.scss';
-let Label = (props) => {
- let padding = (props.padding || 20) + 'px';
+const Label = props => {
+ const padding = (props.padding || 20) + 'px';
return (
+ style={{ padding: '0 ' + padding, ...props.style }}>
{props.text}
{props.description ?
{props.description}
: null}
@@ -25,13 +25,7 @@ let Label = (props) => {
export default Label;
Label.propTypes = {
- text: PropTypes.node.describe(
- 'The main text/node of the label.'
- ),
- description: PropTypes.node.describe(
- 'The secondary text/node of the label.'
- ),
- padding: PropTypes.number.describe(
- 'Allows you to override the left-right padding of the label.'
- )
+ text: PropTypes.node.describe('The main text/node of the label.'),
+ description: PropTypes.node.describe('The secondary text/node of the label.'),
+ padding: PropTypes.number.describe('Allows you to override the left-right padding of the label.'),
};
diff --git a/src/components/LiveReload/LiveReload.example.js b/src/components/LiveReload/LiveReload.example.js
index 5dbc4127ed..b5081bf98c 100644
--- a/src/components/LiveReload/LiveReload.example.js
+++ b/src/components/LiveReload/LiveReload.example.js
@@ -6,15 +6,18 @@
* the root directory of this source tree.
*/
import LiveReload from 'components/LiveReload/LiveReload.react';
-import React from 'react';
+import React from 'react';
export const component = LiveReload;
export const demos = [
{
- render: () => Promise.resolve(Math.random())}
- render={num => {num} }
- refreshIntervalMillis={100}
- initialData={0}/>,
+ render: () => (
+ Promise.resolve(Math.random())}
+ render={num => {num} }
+ refreshIntervalMillis={100}
+ initialData={0}
+ />
+ ),
},
- ];
+];
diff --git a/src/components/LiveReload/LiveReload.react.js b/src/components/LiveReload/LiveReload.react.js
index 2497760ac3..4d369413df 100644
--- a/src/components/LiveReload/LiveReload.react.js
+++ b/src/components/LiveReload/LiveReload.react.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
import { abortableGet } from 'lib/AJAX';
export default class LiveReload extends React.Component {
@@ -16,66 +16,76 @@ export default class LiveReload extends React.Component {
this.timer = null;
this.refreshIntervalMillis = props.refreshIntervalMillis || 1000;
this.state = {
- currentData: props.initialData,
- };
+ currentData: props.initialData,
+ };
}
fetchNewData() {
- clearTimeout(this.timer);
- let promise = null;
- let xhr = null;
- if (typeof(this.props.source) === 'function') {
- let obj = this.props.source();
- if (obj.promise && obj.xhr) {
- promise = obj.promise;
- xhr = obj.xhr;
- } else {
- promise = obj;
- }
- } else {
- let obj = abortableGet(this.props.source);
- promise = obj.promise;
- xhr = obj.xhr;
- }
- this.abortXHR = () => {
- if (xhr) {
- xhr.abort();
- }
- clearTimeout(this.timer);
- }
- promise.then(data => {
- this.setState({currentData: data});
- }).finally(() => {
- if (this.shouldContinueReloading) {
- this.timer = setTimeout(this.fetchNewData.bind(this), this.refreshIntervalMillis);
- }
- });
- return promise;
- }
+ clearTimeout(this.timer);
+ let promise = null;
+ let xhr = null;
+ if (typeof this.props.source === 'function') {
+ const obj = this.props.source();
+ if (obj.promise && obj.xhr) {
+ promise = obj.promise;
+ xhr = obj.xhr;
+ } else {
+ promise = obj;
+ }
+ } else {
+ const obj = abortableGet(this.props.source);
+ promise = obj.promise;
+ xhr = obj.xhr;
+ }
+ this.abortXHR = () => {
+ if (xhr) {
+ xhr.abort();
+ }
+ clearTimeout(this.timer);
+ };
+ promise
+ .then(data => {
+ this.setState({ currentData: data });
+ })
+ .finally(() => {
+ if (this.shouldContinueReloading) {
+ this.timer = setTimeout(this.fetchNewData.bind(this), this.refreshIntervalMillis);
+ }
+ });
+ return promise;
+ }
componentWillMount() {
- this.shouldContinueReloading = true;
- if (!this.state.currentData) {
- this.fetchNewData();
- } else {
- this.timer = setTimeout(this.fetchNewData.bind(this), this.refreshIntervalMillis);
- }
- }
+ this.shouldContinueReloading = true;
+ if (!this.state.currentData) {
+ this.fetchNewData();
+ } else {
+ this.timer = setTimeout(this.fetchNewData.bind(this), this.refreshIntervalMillis);
+ }
+ }
- componentWillUnmount() {
- this.abortXHR();
- this.shouldContinueReloading = false;
- clearTimeout(this.timer);
- }
+ componentWillUnmount() {
+ this.abortXHR();
+ this.shouldContinueReloading = false;
+ clearTimeout(this.timer);
+ }
render() {
- return this.props.render(this.state.currentData);
+ return this.props.render(this.state.currentData);
}
}
LiveReload.propTypes = {
- refreshIntervalMillis: PropTypes.number.describe('How often to refresh the data, in milliseconds. Defaults to 1s.'),
- source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired.describe('Either a URL to fetch updates from, or a function that returns a promise that fetches update, or a function that returns { promise, xhr }.'),
- render: PropTypes.func.isRequired.describe('Receives the data from the URL or promise and returns the component to be rendered.'),
- initialData: PropTypes.any.describe('Initial data to seed the component before any request has been made. If this is not passed, then your render function must be prepared to handle being called with "undefined" instead of data. Passing this will also cause the first load to happen after refreshIntervalMillis, while not passing it will cause it the first load to happen immediately.'),
-}
+ refreshIntervalMillis: PropTypes.number.describe(
+ 'How often to refresh the data, in milliseconds. Defaults to 1s.'
+ ),
+ source: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired.describe(
+ 'Either a URL to fetch updates from, or a function that returns a promise that fetches update, or a function that returns { promise, xhr }.'
+ ),
+ render: PropTypes.func.isRequired.describe(
+ 'Receives the data from the URL or promise and returns the component to be rendered.'
+ ),
+ initialData: PropTypes.any.describe(
+ 'Initial data to seed the component before any request has been made. If this is not passed, then your render function must be prepared to handle being called with "undefined" instead of data. Passing this will also cause the first load to happen after refreshIntervalMillis, while not passing it will cause it the first load to happen immediately.'
+ ),
+};
diff --git a/src/components/Loader/Loader.example.js b/src/components/Loader/Loader.example.js
index 673be9733f..70ffe04773 100644
--- a/src/components/Loader/Loader.example.js
+++ b/src/components/Loader/Loader.example.js
@@ -7,7 +7,7 @@
*/
import baseStyles from 'stylesheets/base.scss';
import Loader from 'components/Loader/Loader.react';
-import React from 'react';
+import React from 'react';
export const component = Loader;
@@ -15,15 +15,17 @@ export const demos = [
{
render() {
return (
-
+
);
- }
- }
+ },
+ },
];
diff --git a/src/components/Loader/Loader.react.js b/src/components/Loader/Loader.react.js
index 277972f68f..f88613edcb 100644
--- a/src/components/Loader/Loader.react.js
+++ b/src/components/Loader/Loader.react.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/Loader/Loader.scss';
const SMALL_RADIUS = 20;
@@ -26,27 +26,27 @@ function getRadius(t) {
function getPosition(t) {
if (t < POINTS.A) {
- let multiplier = LENGTH / SMALL_RADIUS;
+ const multiplier = LENGTH / SMALL_RADIUS;
return {
x: SMALL_RADIUS + SMALL_RADIUS * Math.cos(t * multiplier + Math.PI / 2),
- y: 2 * LARGE_RADIUS + SMALL_RADIUS - SMALL_RADIUS * Math.sin(t * multiplier + Math.PI / 2)
+ y: 2 * LARGE_RADIUS + SMALL_RADIUS - SMALL_RADIUS * Math.sin(t * multiplier + Math.PI / 2),
};
} else if (t < POINTS.B) {
return {
x: 2 * SMALL_RADIUS,
- y: LENGTH * (POINTS.A - t) + 2 * LARGE_RADIUS + SMALL_RADIUS
+ y: LENGTH * (POINTS.A - t) + 2 * LARGE_RADIUS + SMALL_RADIUS,
};
} else if (t < POINTS.C) {
- let t2 = t - POINTS.B;
- let multiplier = LENGTH / LARGE_RADIUS;
+ const t2 = t - POINTS.B;
+ const multiplier = LENGTH / LARGE_RADIUS;
return {
x: 2 * SMALL_RADIUS + LARGE_RADIUS - LARGE_RADIUS * Math.cos(t2 * multiplier),
- y: LARGE_RADIUS - LARGE_RADIUS * Math.sin(t2 * multiplier)
+ y: LARGE_RADIUS - LARGE_RADIUS * Math.sin(t2 * multiplier),
};
} else {
return {
x: LENGTH * (POINTS.C - t) + 2 * SMALL_RADIUS + LARGE_RADIUS,
- y: 2 * LARGE_RADIUS
+ y: 2 * LARGE_RADIUS,
};
}
}
@@ -73,7 +73,7 @@ export default class Loader extends React.Component {
if (!this.mounted) {
return;
}
- let delta = new Date() - this.mountTime;
+ const delta = new Date() - this.mountTime;
let t = (delta / DURATION) % 1;
let pos = getPosition(t);
let style = this.dot0Ref.current.style;
diff --git a/src/components/LoaderContainer/LoaderContainer.example.js b/src/components/LoaderContainer/LoaderContainer.example.js
index 2d1a111855..c6037dbd0e 100644
--- a/src/components/LoaderContainer/LoaderContainer.example.js
+++ b/src/components/LoaderContainer/LoaderContainer.example.js
@@ -5,91 +5,95 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
import PushAudiencesSelector from 'components/PushAudiencesSelector/PushAudiencesSelector.react';
-import React from 'react';
+import React from 'react';
export const component = LoaderContainer;
class PushAudiencesSelectorDemo extends React.Component {
constructor() {
super();
- this.state = { current: '1'};
+ this.state = { current: '1' };
}
handleChange(value) {
- this.setState({ current: value});
+ this.setState({ current: value });
}
render() {
return (
-
+
);
}
}
-let mockData = [
+const mockData = [
{
objectId: '1',
name: 'Everyone',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '2',
name: 'iOS Users in US',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
- {
+ {
objectId: '3',
name: 'Completed Checkout <30 days',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
- {
+ {
objectId: '4',
name: 'New Users',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '5',
name: 'Everyone',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '6',
name: 'iOS Users in US',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '7',
name: 'Completed Checkout <30 days',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '8',
name: 'New Users',
count: 148,
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
];
class LoaderContainerDemo extends React.Component {
constructor() {
super();
- this.state = { loading: true};
+ this.state = { loading: true };
}
- render(){
+ render() {
return (
);
@@ -99,9 +103,7 @@ class LoaderContainerDemo extends React.Component {
export const demos = [
{
render() {
- return (
-
- );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/LoaderContainer/LoaderContainer.react.js b/src/components/LoaderContainer/LoaderContainer.react.js
index 3eca78ddba..48272ff2e2 100644
--- a/src/components/LoaderContainer/LoaderContainer.react.js
+++ b/src/components/LoaderContainer/LoaderContainer.react.js
@@ -5,21 +5,25 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Loader from 'components/Loader/Loader.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/LoaderContainer/LoaderContainer.scss';
+import baseStyles from 'stylesheets/base.scss';
+import Loader from 'components/Loader/Loader.react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/LoaderContainer/LoaderContainer.scss';
//Loader wrapper component
//Wraps child component with a layer and centered
const LoaderContainer = ({ loading, hideAnimation, children, solid = true }) => (
-
- {children}
-
-
- {(hideAnimation || !loading) ? null :
}
+ {children}
+
+ {hideAnimation || !loading ? null : }
);
@@ -30,9 +34,7 @@ LoaderContainer.propTypes = {
loading: PropTypes.bool.describe(
'State of the loader (true displays loader, false hides loader).'
),
- hideAnimation: PropTypes.bool.describe(
- 'Whether to hide the animation within the container.'
- ),
+ hideAnimation: PropTypes.bool.describe('Whether to hide the animation within the container.'),
solid: PropTypes.bool.describe(
'Optional flag to have an solid background. Defaults to true. If false an opacity of 70% is used.'
),
diff --git a/src/components/LoaderDots/LoaderDots.example.js b/src/components/LoaderDots/LoaderDots.example.js
index 012ac2ea63..777514615f 100644
--- a/src/components/LoaderDots/LoaderDots.example.js
+++ b/src/components/LoaderDots/LoaderDots.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import LoaderDots from 'components/LoaderDots/LoaderDots.react';
export const component = LoaderDots;
@@ -17,6 +17,6 @@ export const demos = [
- )
- }
+ ),
+ },
];
diff --git a/src/components/LoaderDots/LoaderDots.react.js b/src/components/LoaderDots/LoaderDots.react.js
index 47ef036e7b..9605c67bc4 100644
--- a/src/components/LoaderDots/LoaderDots.react.js
+++ b/src/components/LoaderDots/LoaderDots.react.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/LoaderDots/LoaderDots.scss';
export default class LoaderDots extends React.Component {
diff --git a/src/components/LogView/LogView.example.js b/src/components/LogView/LogView.example.js
index 8125b57835..cf0c04845f 100644
--- a/src/components/LogView/LogView.example.js
+++ b/src/components/LogView/LogView.example.js
@@ -14,7 +14,7 @@ export const component = LogView;
export const demos = [
{
render() {
- let text1 = `I2015-09-30T00:25:26.950Z]Deployed v1 with triggers:
+ const text1 = `I2015-09-30T00:25:26.950Z]Deployed v1 with triggers:
Item:
before_save
after_save
@@ -26,21 +26,21 @@ export const demos = [
anotherFunction
WillError`;
- let text2 = `I2015-09-30T00:35:42.336Z]v2 before_save triggered for Item:
+ const text2 = `I2015-09-30T00:35:42.336Z]v2 before_save triggered for Item:
Input: {"original":null,"update":{"name":"i"}}
Result: Update changed to {"name":"i","count":12}`;
- let text3 = `I2015-10-06T22:39:11.029Z]v4 Ran cloud function doSomething with:
+ const text3 = `I2015-10-06T22:39:11.029Z]v4 Ran cloud function doSomething with:
Input: {}
Result: {}`;
return (
-
-
-
+
+
+
);
- }
- }
+ },
+ },
];
diff --git a/src/components/LogView/LogView.react.js b/src/components/LogView/LogView.react.js
index f50a9cbc69..da84974122 100644
--- a/src/components/LogView/LogView.react.js
+++ b/src/components/LogView/LogView.react.js
@@ -5,15 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import styles from 'components/LogView/LogView.scss';
+import React from 'react';
+import styles from 'components/LogView/LogView.scss';
-let LogView = (props) => {
- return (
-
- {props.children}
-
- );
-}
+const LogView = props => {
+ return
{props.children} ;
+};
export default LogView;
diff --git a/src/components/LogView/LogViewEntry.example.js b/src/components/LogView/LogViewEntry.example.js
index 2d63b015de..3743839bb2 100644
--- a/src/components/LogView/LogViewEntry.example.js
+++ b/src/components/LogView/LogViewEntry.example.js
@@ -12,8 +12,6 @@ export const component = LogViewEntry;
export const demos = [
{
- render: () => (
-
- )
- }
+ render: () =>
,
+ },
];
diff --git a/src/components/LogView/LogViewEntry.react.js b/src/components/LogView/LogViewEntry.react.js
index c434f2ec84..70a3888786 100644
--- a/src/components/LogView/LogViewEntry.react.js
+++ b/src/components/LogView/LogViewEntry.react.js
@@ -6,41 +6,38 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/LogView/LogViewEntry.scss';
+import React from 'react';
+import styles from 'components/LogView/LogViewEntry.scss';
const TIMESTAMP_REGEX = [
'([a-z])', // Any Single Word Character (Not Whitespace) 1
'((?:2|1)\\d{3}(?:-|\\/)(?:(?:0[1-9])|(?:1[0-2]))(?:-|\\/)(?:(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))(?:T|\\s)(?:(?:[0-1][0-9])|(?:2[0-3])):(?:[0-5][0-9]):(?:[0-5][0-9]))', // Time Stamp 1
- '(\\.)', // Any Single Character 1
- '(\\d)', // Any Single Digit 1
- '(\\d)', // Any Single Digit 2
- '(\\d)', // Any Single Digit 3
- '([a-z])', // Any Single Word Character (Not Whitespace) 2
- '(\\])' // Any Single Character 2
+ '(\\.)', // Any Single Character 1
+ '(\\d)', // Any Single Digit 1
+ '(\\d)', // Any Single Digit 2
+ '(\\d)', // Any Single Digit 3
+ '([a-z])', // Any Single Word Character (Not Whitespace) 2
+ '(\\])', // Any Single Character 2
].join('');
-let isError = (str) => str[0] === 'E';
+const isError = str => str[0] === 'E';
-let getLogEntryInfo = (str) => {
- let re = getTimestampRegex();
- let timeStampStr = str.match(re) ? str.match(re)[0] : '';
+const getLogEntryInfo = str => {
+ const re = getTimestampRegex();
+ const timeStampStr = str.match(re) ? str.match(re)[0] : '';
return {
time: timeStampStr,
- content: str.replace(timeStampStr,''),
- error: isError(str)
+ content: str.replace(timeStampStr, ''),
+ error: isError(str),
};
-}
+};
//example timestamp: 'I2015-09-30T00:36:45.522Z]'
-let getTimestampRegex = () => new RegExp(TIMESTAMP_REGEX,['i']);
+const getTimestampRegex = () => new RegExp(TIMESTAMP_REGEX, ['i']);
-let LogViewEntry = ({
- text = '',
- timestamp,
-}) => {
- let logEntryInfo = getLogEntryInfo(text);
- let classes = [styles.entry, logEntryInfo.error ? styles.error: ''];
+const LogViewEntry = ({ text = '', timestamp }) => {
+ const logEntryInfo = getLogEntryInfo(text);
+ const classes = [styles.entry, logEntryInfo.error ? styles.error : ''];
return (
{/* handle the timestamp format used by both Parse Server and Parse.com */}
@@ -48,12 +45,10 @@ let LogViewEntry = ({
{logEntryInfo.content}
);
-}
+};
export default LogViewEntry;
LogViewEntry.propTypes = {
- text: PropTypes.string.isRequired.describe(
- 'The content of the log view entry.'
- ),
+ text: PropTypes.string.isRequired.describe('The content of the log view entry.'),
};
diff --git a/src/components/LoginForm/LoginForm.example.js b/src/components/LoginForm/LoginForm.example.js
index 010dee5832..7747ee04be 100644
--- a/src/components/LoginForm/LoginForm.example.js
+++ b/src/components/LoginForm/LoginForm.example.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import LoginForm from 'components/LoginForm/LoginForm.react';
-import LoginRow from 'components/LoginRow/LoginRow.react';
-import React from 'react';
+import LoginRow from 'components/LoginRow/LoginRow.react';
+import React from 'react';
export const component = LoginForm;
@@ -16,49 +16,48 @@ export const demos = [
render() {
return (
-
- } />
- } />
+
+ } />
+ } />
);
- }
- }, {
+ },
+ },
+ {
render() {
return (
}
- action='Sign Up'>
-
} />
+ action="Sign Up"
+ >
} />
+ label="Email"
+ input={
}
+ />
} />
+ label="Password"
+ input={
+
+ }
+ />
} />
+ label="App Name"
+ input={
}
+ />
+
} />
);
- }
- }
+ },
+ },
];
diff --git a/src/components/LoginForm/LoginForm.react.js b/src/components/LoginForm/LoginForm.react.js
index 6a55819f3c..bc897fa745 100644
--- a/src/components/LoginForm/LoginForm.react.js
+++ b/src/components/LoginForm/LoginForm.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import CSRFInput from 'components/CSRFInput/CSRFInput.react';
-import Icon from 'components/Icon/Icon.react';
-import React from 'react';
-import styles from 'components/LoginForm/LoginForm.scss';
+import CSRFInput from 'components/CSRFInput/CSRFInput.react';
+import Icon from 'components/Icon/Icon.react';
+import React from 'react';
+import styles from 'components/LoginForm/LoginForm.scss';
import baseStyles from 'stylesheets/base.scss';
// Class-style component, because we need refs
@@ -20,8 +20,8 @@ export default class LoginForm extends React.Component {
render() {
return (
{
if (this.props.disableSubmit) {
return;
}
this.props.formSubmit();
- this.formRef.current.submit()
+ this.formRef.current.submit();
}}
className={styles.submit}
- value={this.props.action} />
+ value={this.props.action}
+ />
);
diff --git a/src/components/LoginRow/LoginRow.react.js b/src/components/LoginRow/LoginRow.react.js
index 69215b29d1..c6568f7d83 100644
--- a/src/components/LoginRow/LoginRow.react.js
+++ b/src/components/LoginRow/LoginRow.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/LoginRow/LoginRow.scss';
-let LoginRow = ({ label, input, extra }) => (
+const LoginRow = ({ label, input, extra }) => (
{label}
{extra ? {extra}
: null}
diff --git a/src/components/Markdown/Markdown.example.js b/src/components/Markdown/Markdown.example.js
index 2684e8cfa7..5155e6009d 100644
--- a/src/components/Markdown/Markdown.example.js
+++ b/src/components/Markdown/Markdown.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import Markdown from 'components/Markdown/Markdown.react';
export const component = Markdown;
@@ -14,7 +14,7 @@ export const demos = [
{
name: 'Demo name',
render: () => {
- let content = `
+ const content = `
**bold** *italic*
~~This code is a mistake~~
@@ -35,10 +35,7 @@ export const demos = [
some code block
\`\`\`
`;
- return (
-
- );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/Markdown/Markdown.react.js b/src/components/Markdown/Markdown.react.js
index 7860b06860..0d1b22b86f 100644
--- a/src/components/Markdown/Markdown.react.js
+++ b/src/components/Markdown/Markdown.react.js
@@ -5,27 +5,22 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
-import { marked } from 'marked';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
+import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
+import { marked } from 'marked';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
import ReactDOMServer from 'react-dom/server';
// Refer to https://github.com/chjj/marked.
-let renderer = new marked.Renderer();
+const renderer = new marked.Renderer();
renderer.code = (code, lang) => {
- let snippet = (
-
- );
+ const snippet = ;
return ReactDOMServer.renderToString(snippet);
};
-let Markdown = ({ content }) => {
- let rawHtml = marked(content, {
+const Markdown = ({ content }) => {
+ const rawHtml = marked(content, {
sanitize: true,
renderer: renderer,
});
@@ -39,7 +34,5 @@ let Markdown = ({ content }) => {
export default Markdown;
Markdown.propTypes = {
- content: PropTypes.string.isRequired.describe(
- 'The content that will be rendered as markdown.'
- ),
-}
+ content: PropTypes.string.isRequired.describe('The content that will be rendered as markdown.'),
+};
diff --git a/src/components/Modal/Modal.example.js b/src/components/Modal/Modal.example.js
index b701830d17..2714e73d48 100644
--- a/src/components/Modal/Modal.example.js
+++ b/src/components/Modal/Modal.example.js
@@ -13,7 +13,7 @@ export const component = Modal;
class ModalDemo extends React.Component {
constructor() {
- super()
+ super();
this.state = {
showModal: false,
};
@@ -23,14 +23,18 @@ class ModalDemo extends React.Component {
return (
{})}/>
- {this.state.showModal ? {})}
- onCancel={this.setState.bind(this, { showModal: false }, () => {})}>
- {this.props.children}
- : null}
+ value="Show demo modal"
+ onClick={this.setState.bind(this, { showModal: true }, () => {})}
+ />
+ {this.state.showModal ? (
+ {})}
+ onCancel={this.setState.bind(this, { showModal: false }, () => {})}
+ >
+ {this.props.children}
+
+ ) : null}
);
}
@@ -39,14 +43,22 @@ class ModalDemo extends React.Component {
export const demos = [
{
name: 'Modal with children',
- render: () => Children ,
+ render: () => (
+
+ Children
+
+ ),
},
{
name: 'Modal without children',
- render: () => {null} ,
+ render: () => {null} ,
},
{
name: 'Modal without children, but with buttons on right anyway.',
- render: () => {null} ,
+ render: () => (
+
+ {null}
+
+ ),
},
];
diff --git a/src/components/Modal/Modal.react.js b/src/components/Modal/Modal.react.js
index 9a267b6203..9ffe0e47ab 100644
--- a/src/components/Modal/Modal.react.js
+++ b/src/components/Modal/Modal.react.js
@@ -5,23 +5,23 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Field from 'components/Field/Field.react';
-import Icon from 'components/Icon/Icon.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
-import PropTypes from 'lib/PropTypes';
-import styles from 'components/Modal/Modal.scss';
+import Button from 'components/Button/Button.react';
+import Field from 'components/Field/Field.react';
+import Icon from 'components/Icon/Icon.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
+import PropTypes from 'lib/PropTypes';
+import styles from 'components/Modal/Modal.scss';
-let origin = new Position(0, 0);
-let buttonColors = {
+const origin = new Position(0, 0);
+const buttonColors = {
danger: 'red',
info: 'blue',
- valid: 'green'
+ valid: 'green',
};
-let Modal = (({
+const Modal = ({
type = Modal.Types.INFO,
icon,
iconSize = 36,
@@ -45,7 +45,7 @@ let Modal = (({
buttonsInCenter = React.Children.count(children) === 0,
}) => {
if (children) {
- children = React.Children.map(children, (c) => {
+ children = React.Children.map(children, c => {
if (c && c.type === Field && c.props.label) {
return React.cloneElement(c, { ...c.props, labelPadding: 24 });
}
@@ -53,67 +53,80 @@ let Modal = (({
});
}
- let footer = customFooter || (
-
- {showCancel &&
}
- { showContinue &&
}
+ const footer = customFooter || (
+
+ {showCancel && }
+ {showContinue && (
+
+ )}
+ progress={progress}
+ />
);
- let wrappedChildren = textModal ?
- {children}
-
: children;
+ const wrappedChildren = textModal ?
{children}
: children;
return (
-
+
-
{title}
+
+ {title}
+
{subtitle}
- {icon ?
+ {icon ? (
-
-
: null}
+
+
+ ) : null}
{wrappedChildren}
{footer}
);
-});
+};
Modal.Types = {
DANGER: 'danger',
INFO: 'info',
- VALID: 'valid'
+ VALID: 'valid',
};
Modal.propTypes = {
- type: PropTypes.string.describe('Used to change the color of the modal and buttons. Use Modal.Types.DANGER, Modal.Types.INFO, or Modal.Types.VALID.'),
+ type: PropTypes.string.describe(
+ 'Used to change the color of the modal and buttons. Use Modal.Types.DANGER, Modal.Types.INFO, or Modal.Types.VALID.'
+ ),
icon: PropTypes.string.describe('The Icon to display in the top right corner.'),
iconSize: PropTypes.number.describe('The size of the Icon in the top right corner.'),
title: PropTypes.string.isRequired.describe('The title of the modal.'),
subtitle: PropTypes.node.describe('The subtitle of the modal. Usually a string or .'),
cancelText: PropTypes.string.describe('String for the cancel button. Defaults to "Cancel".'),
onCancel: PropTypes.func.describe('Called when the cancel button is clicked.'),
- canCancel: PropTypes.bool.describe('Determines whether this modal can be cancelled. Defaults to true. Useful to prevent the user from attempting to cancel a modal when a related request is in-flight.'),
- showCancel: PropTypes.bool.describe('Determines whether to show the cancel button. Defaults to true.'),
+ canCancel: PropTypes.bool.describe(
+ 'Determines whether this modal can be cancelled. Defaults to true. Useful to prevent the user from attempting to cancel a modal when a related request is in-flight.'
+ ),
+ showCancel: PropTypes.bool.describe(
+ 'Determines whether to show the cancel button. Defaults to true.'
+ ),
confirmText: PropTypes.string.describe('String for the confirm button. Defaults to "Okay".'),
onConfirm: PropTypes.func.describe('Called when the confirm button is clicked.'),
disabled: PropTypes.bool.describe('If true, the confirm button will be disabled.'),
@@ -121,7 +134,9 @@ Modal.propTypes = {
customFooter: PropTypes.node.describe('used to fill any custom footer use case.'),
textModal: PropTypes.bool.describe('Used for modals that contain only text to pad the text.'),
width: PropTypes.number.describe('custom width of modal.'),
- buttonsInCenter: PropTypes.bool.describe('If true, the buttons will appear in the center of the modal, instead of to the right. By default, the buttons appear on the right unless the modal contains no children, in which case they appear in the center.'),
+ buttonsInCenter: PropTypes.bool.describe(
+ 'If true, the buttons will appear in the center of the modal, instead of to the right. By default, the buttons appear on the right unless the modal contains no children, in which case they appear in the center.'
+ ),
};
export default Modal;
diff --git a/src/components/MoneyInput/MoneyInput.example.js b/src/components/MoneyInput/MoneyInput.example.js
index f353703e82..bef7221e54 100644
--- a/src/components/MoneyInput/MoneyInput.example.js
+++ b/src/components/MoneyInput/MoneyInput.example.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import Label from 'components/Label/Label.react';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import Label from 'components/Label/Label.react';
import MoneyInput from 'components/MoneyInput/MoneyInput.react';
class Wrapper extends React.Component {
@@ -25,13 +25,15 @@ export const demos = [
}
- input={ {}} />} />
+ label={ }
+ input={ {}} />}
+ />
}
- input={ {}} />} />
+ label={ }
+ input={ {}} />}
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/MoneyInput/MoneyInput.react.js b/src/components/MoneyInput/MoneyInput.react.js
index 0c3e948475..b8586db4c0 100644
--- a/src/components/MoneyInput/MoneyInput.react.js
+++ b/src/components/MoneyInput/MoneyInput.react.js
@@ -9,28 +9,26 @@ import PropTypes from 'lib/PropTypes';
import React from 'react';
import styles from 'components/MoneyInput/MoneyInput.scss';
-let MoneyInput = ({ enabled = true, value, onChange = () => {}}) => {
- return {
- onChange(e.nativeEvent.target.value);
- }}
- />
-}
+const MoneyInput = ({ enabled = true, value, onChange = () => {} }) => {
+ return (
+ {
+ onChange(e.nativeEvent.target.value);
+ }}
+ />
+ );
+};
export default MoneyInput;
MoneyInput.propTypes = {
- enabled: PropTypes.bool.describe(
- 'Determines whether the input is enabled.'
- ),
+ enabled: PropTypes.bool.describe('Determines whether the input is enabled.'),
onChange: PropTypes.func.describe(
'A function fired when the input is changed. It receives the new value as its only parameter.'
),
- value: PropTypes.number.isRequired.describe(
- 'The current value of the controlled input.'
- ),
+ value: PropTypes.number.isRequired.describe('The current value of the controlled input.'),
};
diff --git a/src/components/MultiSelect/MultiSelect.example.js b/src/components/MultiSelect/MultiSelect.example.js
index 13855f281c..a3e226523a 100644
--- a/src/components/MultiSelect/MultiSelect.example.js
+++ b/src/components/MultiSelect/MultiSelect.example.js
@@ -5,11 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import MultiSelect from 'components/MultiSelect/MultiSelect.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import MultiSelect from 'components/MultiSelect/MultiSelect.react';
import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
-import React from 'react';
+import React from 'react';
export const component = MultiSelect;
@@ -25,14 +25,19 @@ class MultiSelectDemo extends React.Component {
render() {
return (
-
- Red
- Orange
- Yellow
- Green
- Blue
- Purple
- Rainbow
+
+ Red
+ Orange
+ Yellow
+ Green
+ Blue
+ Purple
+ Rainbow
);
}
@@ -41,20 +46,22 @@ class MultiSelectDemo extends React.Component {
export const demos = [
{
render: () => (
-
+
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
+ ),
},
{
render: () => (
-
+
}
- input={ } />
+ label={ }
+ input={ }
+ />
- )
+ ),
},
];
diff --git a/src/components/MultiSelect/MultiSelect.react.js b/src/components/MultiSelect/MultiSelect.react.js
index 2e3dcb1758..67e37a2c85 100644
--- a/src/components/MultiSelect/MultiSelect.react.js
+++ b/src/components/MultiSelect/MultiSelect.react.js
@@ -5,39 +5,39 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import hasAncestor from 'lib/hasAncestor';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import stringList from 'lib/stringList';
-import styles from 'components/MultiSelect/MultiSelect.scss';
-import Chip from 'components/Chip/Chip.react';
+import hasAncestor from 'lib/hasAncestor';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import stringList from 'lib/stringList';
+import styles from 'components/MultiSelect/MultiSelect.scss';
+import Chip from 'components/Chip/Chip.react';
export default class MultiSelect extends React.Component {
constructor() {
super();
this.state = {
open: false,
- position: null
- }
+ position: null,
+ };
this.popoverRef = React.createRef();
this.dropdownRef = React.createRef();
this.handleScroll = () => {
const node = this.dropdownRef.current;
- let newPosition = this.props.fixed ? Position.inWindow(node) : Position.inDocument(node);
+ const newPosition = this.props.fixed ? Position.inWindow(node) : Position.inDocument(node);
newPosition.y += node.offsetHeight;
- if(this.popoverRef.current){
+ if (this.popoverRef.current) {
this.popoverRef.current.setPosition(newPosition);
}
- }
+ };
}
componentDidMount() {
- window.addEventListener('scroll', this.handleScroll)
+ window.addEventListener('scroll', this.handleScroll);
}
- componentWillUnmount(){
- window.removeEventListener('scroll', this.handleScroll)
+ componentWillUnmount() {
+ window.removeEventListener('scroll', this.handleScroll);
}
componentWillReceiveProps() {
@@ -47,7 +47,7 @@ export default class MultiSelect extends React.Component {
setPosition() {
const node = this.dropdownRef.current;
- let newPosition = this.props.fixed ? Position.inWindow(node) : Position.inDocument(node);
+ const newPosition = this.props.fixed ? Position.inWindow(node) : Position.inDocument(node);
newPosition.y += node.offsetHeight; //Move dropdown down below field
//The forceUpdate call is necessary in case the size of the field changes size during the current render.
this.setState({ position: newPosition }, () => this.forceUpdate());
@@ -61,7 +61,7 @@ export default class MultiSelect extends React.Component {
close(e) {
if (!hasAncestor(e.target, this.dropdownRef.current)) {
//In the case where the user clicks on the node, toggle() will handle closing the dropdown.
- this.setState({open: false});
+ this.setState({ open: false });
}
}
@@ -78,30 +78,35 @@ export default class MultiSelect extends React.Component {
render() {
let popover = null;
if (this.state.open) {
- let width = this.dropdownRef.current.clientWidth;
-
- let classes = [styles.menu];
- if (this.props.dense){
+ const width = this.dropdownRef.current.clientWidth;
+
+ const classes = [styles.menu];
+ if (this.props.dense) {
classes.push(styles.dense);
}
popover = (
-
+
- {React.Children.map(this.props.children, c => React.cloneElement(c,
- {
- ...c.props,
- checked: this.props.value.indexOf(c.props.value) > -1,
- onClick: c.props.disabled? null : this.select.bind(this, c.props.value)
- }
- ))}
+ {React.Children.map(this.props.children, c =>
+ React.cloneElement(c, {
+ ...c.props,
+ checked: this.props.value.indexOf(c.props.value) > -1,
+ onClick: c.props.disabled ? null : this.select.bind(this, c.props.value),
+ })
+ )}
- )
+ );
}
- let selection = [];
- let classes = [styles.current];
+ const selection = [];
+ const classes = [styles.current];
React.Children.forEach(this.props.children, c => {
if (this.props.value.indexOf(c.props.value) > -1) {
selection.push(c.props.children);
@@ -111,45 +116,48 @@ export default class MultiSelect extends React.Component {
if (this.props.width) {
dropdownStyle = {
width: this.props.width,
- float: 'left'
+ float: 'left',
};
}
- let dropDownClasses = [styles.dropdown];
- if (this.props.dense){
+ const dropDownClasses = [styles.dropdown];
+ if (this.props.dense) {
dropDownClasses.push(styles.dense);
}
let content = null;
- if (selection.length === 0 && this.props.placeHolder){
+ if (selection.length === 0 && this.props.placeHolder) {
content = this.props.placeHolder;
classes.push(styles.placeholder);
} else {
-
- content = this.props.chips?
- selection.map((child,index) => {
- let item;
- if(Array.isArray(this.props.value)){
- item = this.props.value[index]
- }
- return (
- {
- if(removed) this.select(removed);
- }}>
- {child}
- )}
- ) :
- stringList(selection, this.props.endDelineator);
+ content = this.props.chips
+ ? selection.map((child, index) => {
+ let item;
+ if (Array.isArray(this.props.value)) {
+ item = this.props.value[index];
+ }
+ return (
+ {
+ if (removed) {
+ this.select(removed);
+ }
+ }}
+ >
+ {child}
+
+ );
+ })
+ : stringList(selection, this.props.endDelineator);
}
return (
- {content}
+ {content}
{popover}
@@ -170,13 +178,7 @@ MultiSelect.propTypes = {
placeHolder: PropTypes.string.describe(
'Option placeholder text to be displayed when no options are chosen.'
),
- endDelineator: PropTypes.string.describe(
- 'End delineator to separate last selected option.'
- ),
- dense: PropTypes.bool.describe(
- 'Mini variant - less height'
- ),
- chips: PropTypes.bool.describe(
- 'Display chip for every selected item'
- ),
-}
+ endDelineator: PropTypes.string.describe('End delineator to separate last selected option.'),
+ dense: PropTypes.bool.describe('Mini variant - less height'),
+ chips: PropTypes.bool.describe('Display chip for every selected item'),
+};
diff --git a/src/components/MultiSelect/MultiSelectOption.react.js b/src/components/MultiSelect/MultiSelectOption.react.js
index 3e8bf8ad08..595a61a229 100644
--- a/src/components/MultiSelect/MultiSelectOption.react.js
+++ b/src/components/MultiSelect/MultiSelectOption.react.js
@@ -5,36 +5,27 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
-import React from 'react';
-import styles from 'components/MultiSelect/MultiSelect.scss';
+import Icon from 'components/Icon/Icon.react';
+import React from 'react';
+import styles from 'components/MultiSelect/MultiSelect.scss';
+
+const MultiSelectOption = ({ checked, children, dense, disabled, ...other }) => {
+ const classes = [styles.option, disabled ? styles.disabled : undefined];
-let MultiSelectOption = ({ checked, children, dense, disabled, ...other }) => {
-
- const classes = [styles.option,
- disabled? styles.disabled: undefined
- ];
-
const icon = checked ? (
-
+
) : (
- )
-
- return (
+ );
-
- {children}
- {disabled ? : icon}
-
-);
-}
+ return (
+
+ {children}
+ {disabled ? : icon}
+
+ );
+};
export default MultiSelectOption;
diff --git a/src/components/NumberEditor/NumberEditor.react.js b/src/components/NumberEditor/NumberEditor.react.js
index 0a63275272..50b8771f57 100644
--- a/src/components/NumberEditor/NumberEditor.react.js
+++ b/src/components/NumberEditor/NumberEditor.react.js
@@ -14,7 +14,7 @@ export default class NumberEditor extends React.Component {
super();
this.state = {
- value: props.value || 0
+ value: props.value || 0,
};
this.checkExternalClick = this.checkExternalClick.bind(this);
@@ -35,7 +35,7 @@ export default class NumberEditor extends React.Component {
checkExternalClick(e) {
if (e.target !== this.inputRef.current) {
- this.commitValue()
+ this.commitValue();
}
}
@@ -58,16 +58,15 @@ export default class NumberEditor extends React.Component {
}
render() {
- let onChange = (e) => {
- let value = e.target.value;
- this.setState({ value: validateNumeric(value) ? value : this.state.value });
+ const onChange = e => {
+ const value = e.target.value;
+ this.setState({
+ value: validateNumeric(value) ? value : this.state.value,
+ });
};
return (
-
+
);
}
diff --git a/src/components/PasswordStrength/PasswordStrength.react.js b/src/components/PasswordStrength/PasswordStrength.react.js
index e15bffdc1c..34e2fcd9de 100644
--- a/src/components/PasswordStrength/PasswordStrength.react.js
+++ b/src/components/PasswordStrength/PasswordStrength.react.js
@@ -5,21 +5,33 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/PasswordStrength/PasswordStrength.scss';
const tips = [
'It should be at least 8 characters',
'Consider making your password stronger',
- 'Looks great'
+ 'Looks great',
];
-let PasswordStrength = ({ strength }) => {
+const PasswordStrength = ({ strength }) => {
return (
-
-
+
+
{strength > -1 ?
{tips[strength]}
: null}
);
diff --git a/src/components/PermissionsDialog/PermissionsDialog.example.js b/src/components/PermissionsDialog/PermissionsDialog.example.js
index 5f21a96bdb..c80d9833c8 100644
--- a/src/components/PermissionsDialog/PermissionsDialog.example.js
+++ b/src/components/PermissionsDialog/PermissionsDialog.example.js
@@ -6,20 +6,25 @@
* the root directory of this source tree.
*/
// import Parse from 'parse';
-import React from 'react';
+import React from 'react';
import PermissionsDialog from 'components/PermissionsDialog/PermissionsDialog.react';
-import Button from 'components/Button/Button.react';
+import Button from 'components/Button/Button.react';
export const component = PermissionsDialog;
function validateSimple(text) {
-
if (text.startsWith('u')) {
- return Promise.resolve({ entry: { id: text, get:() => 'demouser' } , type:'user'});
+ return Promise.resolve({
+ entry: { id: text, get: () => 'demouser' },
+ type: 'user',
+ });
}
if (text.startsWith('role:')) {
const roleName = text.substring(5);
- return Promise.resolve({ entry: {id:`1d0f${roleName}`, getName:()=>roleName}, type:'role' });
+ return Promise.resolve({
+ entry: { id: `1d0f${roleName}`, getName: () => roleName },
+ type: 'role',
+ });
}
if (text.startsWith('ptr')) {
return Promise.resolve({ entry: text, type: 'pointer' });
@@ -28,15 +33,21 @@ function validateSimple(text) {
}
function validateAdvanced(text) {
- if (text==='*') {
- return Promise.resolve({ entry: '*' , type:'public'});
+ if (text === '*') {
+ return Promise.resolve({ entry: '*', type: 'public' });
}
if (text.startsWith('u')) {
- return Promise.resolve({ entry: { id: text, get:() => 'demouser' } , type:'user'});
+ return Promise.resolve({
+ entry: { id: text, get: () => 'demouser' },
+ type: 'user',
+ });
}
if (text.startsWith('role:')) {
const roleName = text.substring(5);
- return Promise.resolve({ entry: {id:`1d0f${roleName}`, getName:()=>roleName}, type:'role' });
+ return Promise.resolve({
+ entry: { id: `1d0f${roleName}`, getName: () => roleName },
+ type: 'role',
+ });
}
if (text.startsWith('ptr')) {
return Promise.resolve({ entry: text, type: 'pointer' });
@@ -45,24 +56,19 @@ function validateAdvanced(text) {
}
const columns = {
- 'email': { type: 'String',},
- 'password':{ type: 'String', },
- 'ptr_owner':{ type: 'Pointer', targetClass:'_User'},
- 'nickname':{ type: 'String',},
- 'ptr_followers':{ type: 'Array', },
- 'ptr_friends':{ type: 'Array', }
+ email: { type: 'String' },
+ password: { type: 'String' },
+ ptr_owner: { type: 'Pointer', targetClass: '_User' },
+ nickname: { type: 'String' },
+ ptr_followers: { type: 'Array' },
+ ptr_friends: { type: 'Array' },
};
-const userPointers = [
- 'ptr_followers',
- 'ptr_owner',
- 'ptr_friends'
-]
-
+const userPointers = ['ptr_followers', 'ptr_owner', 'ptr_friends'];
class DialogDemo extends React.Component {
constructor() {
- super()
+ super();
this.state = {
showSimple: false,
showAdvanced: false,
@@ -73,28 +79,35 @@ class DialogDemo extends React.Component {
return (
{
this.setState({
- showSimple: true
+ showSimple: true,
});
- }}/>
+ }}
+ />
{
this.setState({
- showAdvanced: true
+ showAdvanced: true,
});
- }}/>
- {this.state.showSimple ?
+ }}
+ />
+ {this.state.showSimple ? (
Learn more about ACLs and app security}
+ confirmText="Save ACL"
+ details={Learn more about ACLs and app security }
permissions={{
- read: {'*': true, 'role:admin': true, 'role:user': true, 'us3r1d':true},
- write: {'*': true, 'role:admin':true },
+ read: {
+ '*': true,
+ 'role:admin': true,
+ 'role:user': true,
+ us3r1d: true,
+ },
+ write: { '*': true, 'role:admin': true },
}}
validateEntry={validateSimple}
onCancel={() => {
@@ -103,25 +116,30 @@ class DialogDemo extends React.Component {
});
}}
coolumns={columns}
- onConfirm={(perms) => {
+ onConfirm={perms => {
console.log(perms);
- }} /> : null}
- {this.state.showAdvanced ?
+ }}
+ />
+ ) : null}
+ {this.state.showAdvanced ? (
Learn more about CLPs and app security}
+ confirmText="Save CLP"
+ details={Learn more about CLPs and app security }
permissions={{
- get: {'*': false, 'us3r1d': true, 'role:admin': true,},
- find: {'*': true, 'us3r1d': true, 'role:admin': true, },
- create: {'*': true, },
- update: {'*': true, pointerFields: ['user']},
- delete: {'*': true, },
- addField: {'*': true, 'requiresAuthentication': true},
+ get: { '*': false, us3r1d: true, 'role:admin': true },
+ find: { '*': true, us3r1d: true, 'role:admin': true },
+ create: { '*': true },
+ update: { '*': true, pointerFields: ['user'] },
+ delete: { '*': true },
+ addField: { '*': true, requiresAuthentication: true },
readUserFields: ['ptr_owner', 'ptr_followers', 'ptr_friends'],
writeUserFields: ['ptr_owner'],
- protectedFields: {'*': ['password', 'email'], 'userField:ptr_owner': []}
+ protectedFields: {
+ '*': ['password', 'email'],
+ 'userField:ptr_owner': [],
+ },
}}
columns={columns}
validateEntry={validateAdvanced}
@@ -131,9 +149,11 @@ class DialogDemo extends React.Component {
showAdvanced: false,
});
}}
- onConfirm={(perms) => {
+ onConfirm={perms => {
console.log(perms);
- }} /> : null}
+ }}
+ />
+ ) : null}
);
}
@@ -142,7 +162,7 @@ class DialogDemo extends React.Component {
export const demos = [
{
render() {
- return ( );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/PermissionsDialog/PermissionsDialog.react.js b/src/components/PermissionsDialog/PermissionsDialog.react.js
index 8ae4241d19..6e2e03f42b 100644
--- a/src/components/PermissionsDialog/PermissionsDialog.react.js
+++ b/src/components/PermissionsDialog/PermissionsDialog.react.js
@@ -5,54 +5,54 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Button from 'components/Button/Button.react';
-import Checkbox from 'components/Checkbox/Checkbox.react';
-import Icon from 'components/Icon/Icon.react';
-import Pill from 'components/Pill/Pill.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
-import ScrollHint from 'components/ScrollHint/ScrollHint.react'
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-import styles from 'components/PermissionsDialog/PermissionsDialog.scss';
-import Toggle from 'components/Toggle/Toggle.react';
-import Autocomplete from 'components/Autocomplete/Autocomplete.react';
-import { Map, fromJS } from 'immutable';
-import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
-import {CurrentApp} from '../../context/currentApp';
+import baseStyles from 'stylesheets/base.scss';
+import Button from 'components/Button/Button.react';
+import Checkbox from 'components/Checkbox/Checkbox.react';
+import Icon from 'components/Icon/Icon.react';
+import Pill from 'components/Pill/Pill.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
+import ScrollHint from 'components/ScrollHint/ScrollHint.react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import styles from 'components/PermissionsDialog/PermissionsDialog.scss';
+import Toggle from 'components/Toggle/Toggle.react';
+import Autocomplete from 'components/Autocomplete/Autocomplete.react';
+import { Map, fromJS } from 'immutable';
+import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
+import { CurrentApp } from '../../context/currentApp';
import generatePath from '../../lib/generatePath';
-let origin = new Position(0, 0);
+const origin = new Position(0, 0);
function resolvePermission(perms, rowId, column) {
- let isPublicRow = rowId === '*';
- let isAuthRow = rowId === 'requiresAuthentication'; // exists only on CLP
- let isEntryRow = !isAuthRow && !isPublicRow;
+ const isPublicRow = rowId === '*';
+ const isAuthRow = rowId === 'requiresAuthentication'; // exists only on CLP
+ const isEntryRow = !isAuthRow && !isPublicRow;
- let publicAccess = perms.get(column).get('*');
- let auth = perms.get(column).get('requiresAuthentication');
- let checked = perms.get(column).get(rowId);
+ const publicAccess = perms.get(column).get('*');
+ const auth = perms.get(column).get('requiresAuthentication');
+ const checked = perms.get(column).get(rowId);
- let forceChecked = publicAccess && !auth;
- let indeterminate = isPublicRow && auth;
+ const forceChecked = publicAccess && !auth;
+ const indeterminate = isPublicRow && auth;
// the logic is:
// Checkbox is shown for:
// - Public row: always
// - Authn row: always
// - Entry row: when requires auth OR not Public
- let editable = isPublicRow || isAuthRow || (isEntryRow && !forceChecked);
+ const editable = isPublicRow || isAuthRow || (isEntryRow && !forceChecked);
return {
checked,
editable,
- indeterminate
+ indeterminate,
};
}
function resolvePointerPermission(perms, pointerPerms, rowId, column) {
- let publicAccess = perms.get(column) && perms.get(column).get('*');
- let auth = perms.get(column).get('requiresAuthentication');
+ const publicAccess = perms.get(column) && perms.get(column).get('*');
+ const auth = perms.get(column).get('requiresAuthentication');
// Pointer permission can be grouped as read/write
let permsGroup;
@@ -65,30 +65,30 @@ function resolvePointerPermission(perms, pointerPerms, rowId, column) {
permsGroup = 'write';
}
- let checked = pointerPerms.get(permsGroup) || pointerPerms.get(column); //pointerPerms.get(permsGroup) && pointerPerms.get(permsGroup).get(rowId);
+ const checked = pointerPerms.get(permsGroup) || pointerPerms.get(column); //pointerPerms.get(permsGroup) && pointerPerms.get(permsGroup).get(rowId);
- let forceChecked = publicAccess && !auth;
+ const forceChecked = publicAccess && !auth;
// Checkbox is shown for:
// - Public row: always
// - Authn row: always
// - Entry row: when requires auth OR not Public
- let editable = !forceChecked;
+ const editable = !forceChecked;
return {
checked,
- editable
+ editable,
};
}
function renderAdvancedCheckboxes(rowId, perms, advanced, onChange) {
- let get = resolvePermission(perms, rowId, 'get');
- let find = resolvePermission(perms, rowId, 'find');
- let count = resolvePermission(perms, rowId, 'count');
- let create = resolvePermission(perms, rowId, 'create');
- let update = resolvePermission(perms, rowId, 'update');
- let del = resolvePermission(perms, rowId, 'delete');
- let addField = resolvePermission(perms, rowId, 'addField');
+ const get = resolvePermission(perms, rowId, 'get');
+ const find = resolvePermission(perms, rowId, 'find');
+ const count = resolvePermission(perms, rowId, 'count');
+ const create = resolvePermission(perms, rowId, 'create');
+ const update = resolvePermission(perms, rowId, 'update');
+ const del = resolvePermission(perms, rowId, 'delete');
+ const addField = resolvePermission(perms, rowId, 'addField');
if (advanced) {
return [
@@ -168,23 +168,21 @@ function renderAdvancedCheckboxes(rowId, perms, advanced, onChange) {
) : (
)}
-
+
,
];
}
- let showReadCheckbox = find.editable || get.editable || count.editable;
- let showWriteCheckbox = create.editable || update.editable || del.editable;
+ const showReadCheckbox = find.editable || get.editable || count.editable;
+ const showWriteCheckbox = create.editable || update.editable || del.editable;
- let readChecked = find.checked && get.checked && count.checked;
- let writeChecked = create.checked && update.checked && del.checked;
+ const readChecked = find.checked && get.checked && count.checked;
+ const writeChecked = create.checked && update.checked && del.checked;
- let indeterminateRead =
- [get, find, count].some(s => s.checked) &&
- [get, find, count].some(s => !s.checked);
+ const indeterminateRead =
+ [get, find, count].some(s => s.checked) && [get, find, count].some(s => !s.checked);
- let indeterminateWrite =
- [create, update, del].some(s => s.checked) &&
- [create, update, del].some(s => !s.checked);
+ const indeterminateWrite =
+ [create, update, del].some(s => s.checked) && [create, update, del].some(s => !s.checked);
return [
@@ -205,9 +203,7 @@ function renderAdvancedCheckboxes(rowId, perms, advanced, onChange) {
label="Write"
checked={writeChecked}
indeterminate={indeterminateWrite}
- onChange={value =>
- onChange(rowId, ['create', 'update', 'delete'], value)
- }
+ onChange={value => onChange(rowId, ['create', 'update', 'delete'], value)}
/>
) : (
@@ -223,35 +219,29 @@ function renderAdvancedCheckboxes(rowId, perms, advanced, onChange) {
) : (
)}
-
+ ,
];
}
function renderSimpleCheckboxes(rowId, perms, onChange) {
// Public state
- let allowPublicRead = perms.get('read').get('*');
- let allowPublicWrite = perms.get('write').get('*');
+ const allowPublicRead = perms.get('read').get('*');
+ const allowPublicWrite = perms.get('write').get('*');
// requireAuthentication state
- let onlyAuthRead = perms.get('read').get('requiresAuthentication');
- let onlyAuthWrite = perms.get('write').get('requiresAuthentication');
-
- let isAuthRow = rowId === 'requiresAuthentication';
- let isPublicRow = rowId === '*';
-
- let showReadCheckbox =
- isAuthRow ||
- (!onlyAuthRead && isPublicRow) ||
- (!onlyAuthRead && !allowPublicRead);
- let showWriteCheckbox =
- isAuthRow ||
- (!onlyAuthWrite && isPublicRow) ||
- (!onlyAuthWrite && !allowPublicWrite);
-
- let readChecked =
- perms.get('read').get(rowId) || allowPublicRead || isAuthRow;
- let writeChecked =
- perms.get('write').get(rowId) || allowPublicWrite || isAuthRow;
+ const onlyAuthRead = perms.get('read').get('requiresAuthentication');
+ const onlyAuthWrite = perms.get('write').get('requiresAuthentication');
+
+ const isAuthRow = rowId === 'requiresAuthentication';
+ const isPublicRow = rowId === '*';
+
+ const showReadCheckbox =
+ isAuthRow || (!onlyAuthRead && isPublicRow) || (!onlyAuthRead && !allowPublicRead);
+ const showWriteCheckbox =
+ isAuthRow || (!onlyAuthWrite && isPublicRow) || (!onlyAuthWrite && !allowPublicWrite);
+
+ const readChecked = perms.get('read').get(rowId) || allowPublicRead || isAuthRow;
+ const writeChecked = perms.get('write').get(rowId) || allowPublicWrite || isAuthRow;
return [
@@ -275,108 +265,94 @@ function renderSimpleCheckboxes(rowId, perms, onChange) {
) : (
)}
-
+
,
];
}
-function renderPointerCheckboxes(
- rowId,
- perms,
- pointerPerms,
- advanced,
- onChange
-) {
- let get = resolvePointerPermission(perms, pointerPerms, rowId, 'get');
- let find = resolvePointerPermission(perms, pointerPerms, rowId, 'find');
- let count = resolvePointerPermission(perms, pointerPerms, rowId, 'count');
- let create = resolvePointerPermission(perms, pointerPerms, rowId, 'create');
- let update = resolvePointerPermission(perms, pointerPerms, rowId, 'update');
- let del = resolvePointerPermission(perms, pointerPerms, rowId, 'delete');
- let addField = resolvePointerPermission(
- perms,
- pointerPerms,
- rowId,
- 'addField'
- );
+function renderPointerCheckboxes(rowId, perms, pointerPerms, advanced, onChange) {
+ const get = resolvePointerPermission(perms, pointerPerms, rowId, 'get');
+ const find = resolvePointerPermission(perms, pointerPerms, rowId, 'find');
+ const count = resolvePointerPermission(perms, pointerPerms, rowId, 'count');
+ const create = resolvePointerPermission(perms, pointerPerms, rowId, 'create');
+ const update = resolvePointerPermission(perms, pointerPerms, rowId, 'update');
+ const del = resolvePointerPermission(perms, pointerPerms, rowId, 'delete');
+ const addField = resolvePointerPermission(perms, pointerPerms, rowId, 'addField');
// whether this field is listed under readUserFields[]
- let readUserFields = pointerPerms.get('read');
+ const readUserFields = pointerPerms.get('read');
// or writeUserFields[]
- let writeUserFields = pointerPerms.get('write');
+ const writeUserFields = pointerPerms.get('write');
- let read = {
+ const read = {
checked: readUserFields || (get.checked && find.checked && count.checked),
- editable: true
+ editable: true,
};
- let write = {
+ const write = {
checked:
- writeUserFields ||
- (create.checked && update.checked && del.checked && addField.checked),
- editable: true
+ writeUserFields || (create.checked && update.checked && del.checked && addField.checked),
+ editable: true,
};
- let cols = [];
+ const cols = [];
if (!advanced) {
// simple view mode
// detect whether public access is enabled
//for read
- let publicReadGrouped = perms.getIn(['read', '*']);
- let publicReadGranular =
- perms.getIn(['get', '*']) &&
- perms.getIn(['find', '*']) &&
- perms.getIn(['count', '*']);
+ const publicReadGrouped = perms.getIn(['read', '*']);
+ const publicReadGranular =
+ perms.getIn(['get', '*']) && perms.getIn(['find', '*']) && perms.getIn(['count', '*']);
// for write
- let publicWriteGrouped = perms.getIn(['write', '*']);
- let publicWriteGranular =
+ const publicWriteGrouped = perms.getIn(['write', '*']);
+ const publicWriteGranular =
perms.getIn(['create', '*']) &&
perms.getIn(['update', '*']) &&
perms.getIn(['delete', '*']) &&
perms.getIn(['addField', '*']);
// assume public access is on when it is set either for group or for each operation
- let publicRead = publicReadGrouped || publicReadGranular;
- let publicWrite = publicWriteGrouped || publicWriteGranular;
+ const publicRead = publicReadGrouped || publicReadGranular;
+ const publicWrite = publicWriteGrouped || publicWriteGranular;
// --------------
// detect whether auth is required
// for read
- let readAuthGroup = perms.getIn(['read', 'requiresAuthentication']);
- let readAuthSeparate =
+ const readAuthGroup = perms.getIn(['read', 'requiresAuthentication']);
+ const readAuthSeparate =
perms.getIn(['get', 'requiresAuthentication']) &&
perms.getIn(['find', 'requiresAuthentication']) &&
perms.getIn(['count', 'requiresAuthentication']);
// for write
- let writeAuthGroup = perms.getIn(['write', 'requiresAuthentication']);
- let writeAuthSeparate =
+ const writeAuthGroup = perms.getIn(['write', 'requiresAuthentication']);
+ const writeAuthSeparate =
perms.getIn(['create', 'requiresAuthentication']) &&
perms.getIn(['update', 'requiresAuthentication']) &&
perms.getIn(['delete', 'requiresAuthentication']) &&
perms.getIn(['addField', 'requiresAuthentication']);
// assume auth is required when it's set either for group or for each operation
- let readAuth = readAuthGroup || readAuthSeparate;
- let writeAuth = writeAuthGroup || writeAuthSeparate;
+ const readAuth = readAuthGroup || readAuthSeparate;
+ const writeAuth = writeAuthGroup || writeAuthSeparate;
// when all ops have public access and none requiure auth, show non-editable checked icon
- let readForceChecked = publicRead && !readAuth;
- let writeForceChecked = publicWrite && !writeAuth;
+ const readForceChecked = publicRead && !readAuth;
+ const writeForceChecked = publicWrite && !writeAuth;
// --------------
// detect whether to show indeterminate checkbox (dash icon)
// in simple view indeterminate happens when:
// {read/write}UserFields is not set and
// not all permissions have same value !(all checked || all unchecked)
- let indeterminateRead =
+ const indeterminateRead =
!readUserFields &&
[get, find, count].some(s => s.checked) &&
[get, find, count].some(s => !s.checked);
- let indeterminateWrite =
+ const indeterminateWrite =
!writeUserFields &&
[create, update, del, addField].some(s => s.checked) &&
[create, update, del, addField].some(s => !s.checked);
@@ -542,20 +518,16 @@ export default class PermissionsDialog extends React.Component {
this.observer = new IntersectionObserver(callback, {
root: this.refTable.current,
rootMargin: intersectionMargin,
- threshold: [0.92]
+ threshold: [0.92],
});
this.suggestInput = this.suggestInput.bind(this);
this.buildLabel = this.buildLabel.bind(this);
- let uniqueKeys = [...(advanced ? ['requiresAuthentication'] : []), '*'];
- let perms = {};
- for (let k in permissions) {
- if (
- k !== 'readUserFields' &&
- k !== 'writeUserFields' &&
- k !== 'protectedFields'
- ) {
+ const uniqueKeys = [...(advanced ? ['requiresAuthentication'] : []), '*'];
+ const perms = {};
+ for (const k in permissions) {
+ if (k !== 'readUserFields' && k !== 'writeUserFields' && k !== 'protectedFields') {
Object.keys(permissions[k]).forEach(key => {
if (key === 'pointerFields') {
//pointerFields is not a regular entity; processed later
@@ -576,9 +548,9 @@ export default class PermissionsDialog extends React.Component {
}
}
- let pointerPermsSubset = {
+ const pointerPermsSubset = {
read: permissions.readUserFields || [],
- write: permissions.writeUserFields || []
+ write: permissions.writeUserFields || [],
};
if (advanced) {
@@ -593,26 +565,27 @@ export default class PermissionsDialog extends React.Component {
// The double check is necessary because the permissions object seems to be empty when accessing the CLP section
// if the class was recently created.
- (pointerPermsSubset.get = permissions.get && permissions.get.pointerFields || []),
- (pointerPermsSubset.find = permissions.find && permissions.find.pointerFields || []),
- (pointerPermsSubset.count = permissions.count && permissions.count.pointerFields || []),
- (pointerPermsSubset.create = permissions.create && permissions.create.pointerFields || []),
- (pointerPermsSubset.update = permissions.update && permissions.update.pointerFields || []),
- (pointerPermsSubset.delete = permissions.delete && permissions.delete.pointerFields || []),
- (pointerPermsSubset.addField = permissions.addField && permissions.addField.pointerFields || []);
+ (pointerPermsSubset.get = (permissions.get && permissions.get.pointerFields) || []),
+ (pointerPermsSubset.find = (permissions.find && permissions.find.pointerFields) || []),
+ (pointerPermsSubset.count = (permissions.count && permissions.count.pointerFields) || []),
+ (pointerPermsSubset.create =
+ (permissions.create && permissions.create.pointerFields) || []),
+ (pointerPermsSubset.update =
+ (permissions.update && permissions.update.pointerFields) || []),
+ (pointerPermsSubset.delete =
+ (permissions.delete && permissions.delete.pointerFields) || []),
+ (pointerPermsSubset.addField =
+ (permissions.addField && permissions.addField.pointerFields) || []);
}
- let pointerPerms = {};
+ const pointerPerms = {};
// form an object where each pointer-field name holds operations it has access to
// e.g. { [field]: { read: true, create: true}, [field2]: {read: true,} ...}
for (const action in pointerPermsSubset) {
// action holds array of field names
for (const field of pointerPermsSubset[action]) {
- pointerPerms[field] = Object.assign(
- { [action]: true },
- pointerPerms[field]
- );
+ pointerPerms[field] = Object.assign({ [action]: true }, pointerPerms[field]);
}
}
// preserve protectedFields
@@ -632,7 +605,7 @@ export default class PermissionsDialog extends React.Component {
columns,
newEntry: '',
entryError: null,
- newKeys: [] // Order for new entries
+ newKeys: [], // Order for new entries
};
}
@@ -649,33 +622,27 @@ export default class PermissionsDialog extends React.Component {
for (const { entry, type } of rows) {
let key;
- let value = {};
+ const value = {};
- if(typeof entry === 'string') {
+ if (typeof entry === 'string') {
key = type + ':' + entry;
value[type] = {
name: entry,
- id: undefined
+ id: undefined,
};
- }
-
- else if (type === 'user') {
+ } else if (type === 'user') {
key = entry.id;
value[type] = {
name: entry.get('username'),
- id: entry.id
+ id: entry.id,
};
- }
-
- else if (type === 'role') {
+ } else if (type === 'role') {
key = 'role:' + entry.getName();
value[type] = {
name: entry.getName(),
- id: entry.id
+ id: entry.id,
};
- }
-
- else if (type === 'pointer') {
+ } else if (type === 'pointer') {
key = entry;
value[type] = true;
}
@@ -763,18 +730,15 @@ export default class PermissionsDialog extends React.Component {
if (this.props.validateEntry) {
this.props.validateEntry(input).then(
({ type, entry }) => {
- let next = { [type]: entry };
+ const next = { [type]: entry };
if (next.public) {
return this.setState({
- entryError: 'You already have a row for Public'
+ entryError: 'You already have a row for Public',
});
}
let id, name, key, newEntry;
-
- let nextKeys;
- let nextEntryTypes;
let nextPerms = this.state.perms;
let nextPointerPerms = this.state.pointerPerms;
@@ -789,33 +753,30 @@ export default class PermissionsDialog extends React.Component {
newEntry = { [type]: true };
} else {
return this.setState({
- entryError: 'Unsupported entry'
+ entryError: 'Unsupported entry',
});
}
// check if key already in list
- if (
- this.state.keys.indexOf(key) > -1 ||
- this.state.newKeys.indexOf(key) > -1
- ) {
+ if (this.state.keys.indexOf(key) > -1 || this.state.newKeys.indexOf(key) > -1) {
return this.setState({
- entryError: 'You already have a row for this object'
+ entryError: 'You already have a row for this object',
});
}
// create new permissions
if (next.pointer) {
if (this.props.advanced) {
- nextPointerPerms = nextPointerPerms.setIn([entry, 'get'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'find'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'count'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'create'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'update'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'delete'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'addField'], true)
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'get'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'find'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'count'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'create'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'update'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'delete'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'addField'], true);
} else {
- nextPointerPerms = nextPointerPerms.setIn([entry, 'read'], true)
- nextPointerPerms = nextPointerPerms.setIn([entry, 'write'], true)
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'read'], true);
+ nextPointerPerms = nextPointerPerms.setIn([entry, 'write'], true);
}
} else {
if (this.props.advanced) {
@@ -832,8 +793,8 @@ export default class PermissionsDialog extends React.Component {
}
}
- nextKeys = this.state.newKeys.concat([key]);
- nextEntryTypes = this.state.entryTypes.set(key, newEntry);
+ const nextKeys = this.state.newKeys.concat([key]);
+ const nextEntryTypes = this.state.entryTypes.set(key, newEntry);
return this.setState(
{
@@ -842,7 +803,7 @@ export default class PermissionsDialog extends React.Component {
newKeys: nextKeys,
entryTypes: nextEntryTypes,
newEntry: '',
- entryError: null
+ entryError: null,
},
() => this.refEntry.current.resetInput()
);
@@ -850,12 +811,11 @@ export default class PermissionsDialog extends React.Component {
() => {
if (this.props.advanced && this.props.enablePointerPermissions) {
this.setState({
- entryError:
- 'Role, User or field not found. Enter a valid id, name or column'
+ entryError: 'Role, User or field not found. Enter a valid id, name or column',
});
} else {
this.setState({
- entryError: 'Role or User not found. Enter a valid name or id.'
+ entryError: 'Role or User not found. Enter a valid name or id.',
});
}
}
@@ -867,20 +827,20 @@ export default class PermissionsDialog extends React.Component {
if (isPointer) {
let index = this.state.pointers.indexOf(key);
if (index > -1) {
- let filtered = this.state.pointers.concat([]);
+ const filtered = this.state.pointers.concat([]);
filtered.splice(index, 1);
return this.setState({
pointers: filtered,
- pointerPerms: this.state.pointerPerms.delete(key)
+ pointerPerms: this.state.pointerPerms.delete(key),
});
}
index = this.state.newKeys.indexOf(key);
if (index > -1) {
- let filtered = this.state.newKeys.concat([]);
+ const filtered = this.state.newKeys.concat([]);
filtered.splice(index, 1);
return this.setState({
newKeys: filtered,
- pointerPerms: this.state.pointerPerms.delete(key)
+ pointerPerms: this.state.pointerPerms.delete(key),
});
}
} else {
@@ -899,38 +859,30 @@ export default class PermissionsDialog extends React.Component {
newPerms = newPerms.deleteIn(['read', key]).deleteIn(['write', key]);
}
if (index > -1) {
- let filtered = this.state.keys.concat([]);
+ const filtered = this.state.keys.concat([]);
filtered.splice(index, 1);
return this.setState({
keys: filtered,
- perms: newPerms
+ perms: newPerms,
});
}
index = this.state.newKeys.indexOf(key);
if (index > -1) {
- let filtered = this.state.newKeys.concat([]);
+ const filtered = this.state.newKeys.concat([]);
filtered.splice(index, 1);
return this.setState({
newKeys: filtered,
- perms: newPerms
+ perms: newPerms,
});
}
}
}
outputPerms() {
- let output = {};
+ const output = {};
let fields = ['read', 'write'];
if (this.props.advanced) {
- fields = [
- 'get',
- 'find',
- 'count',
- 'create',
- 'update',
- 'delete',
- 'addField'
- ];
+ fields = ['get', 'find', 'count', 'create', 'update', 'delete', 'addField'];
}
fields.forEach(field => {
@@ -939,9 +891,9 @@ export default class PermissionsDialog extends React.Component {
if (k === 'pointerFields') {
return;
}
- if (k === 'requiresAuthentication' && !v){
+ if (k === 'requiresAuthentication' && !v) {
// only acceppt requiresAuthentication with true
- return
+ return;
}
if (v) {
output[field][k] = true;
@@ -949,8 +901,8 @@ export default class PermissionsDialog extends React.Component {
});
});
- let readUserFields = [];
- let writeUserFields = [];
+ const readUserFields = [];
+ const writeUserFields = [];
this.state.pointerPerms.forEach((perms, key) => {
if (perms.get('read')) {
readUserFields.push(key);
@@ -984,20 +936,24 @@ export default class PermissionsDialog extends React.Component {
}
urlForKey(key) {
- let isRole = key.startsWith('role:')
- let className = isRole ? '_Role' : '_User';
- let field = isRole ? 'name' : 'objectId';
- let value = isRole ? key.replace('role:', '') : key
- let filters = JSON.stringify([{
- field,
- constraint: 'eq',
- compareTo: value
- }]);
- return generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`);
+ const isRole = key.startsWith('role:');
+ const className = isRole ? '_Role' : '_User';
+ const field = isRole ? 'name' : 'objectId';
+ const value = isRole ? key.replace('role:', '') : key;
+ const filters = JSON.stringify([
+ {
+ field,
+ constraint: 'eq',
+ compareTo: value,
+ },
+ ]);
+ return generatePath(
+ this.context,
+ `browser/${className}?filters=${encodeURIComponent(filters)}`
+ );
}
renderRow(key, columns, types) {
-
const pill = text => (
@@ -1007,8 +963,14 @@ export default class PermissionsDialog extends React.Component {
// types is immutable.js Map
const type = (types && types.get(key)) || {};
- let pointer = this.state.pointerPerms.has(key);
- let label = {key} ;
+ const pointer = this.state.pointerPerms.has(key);
+ let label = (
+
+
+ {key}
+
+
+ );
if (type.user) {
label = (
@@ -1016,13 +978,21 @@ export default class PermissionsDialog extends React.Component {
- {type.user.id}
+
+ {type.user.id}
+
{pill('User')}
- username: {type.user.name ?? 'user not found'}
+ username:{' '}
+
+ {type.user.name ?? 'user not found'}
+
);
@@ -1032,19 +1002,27 @@ export default class PermissionsDialog extends React.Component {
{'role:'}
- {type.role.name}
+
+ {type.role.name}
+
- id: {type.role.id ?? 'role not found'}
+ id:{' '}
+
+ {type.role.id ?? 'role not found'}
+
);
} else if (pointer) {
// get class info from schema
- let { type, targetClass } = columns[key];
+ const { type, targetClass } = columns[key];
- let pillText = type + (targetClass ? `<${targetClass}>` : '');
+ const pillText = type + (targetClass ? `<${targetClass}>` : '');
label = (
@@ -1075,21 +1053,14 @@ export default class PermissionsDialog extends React.Component {
this.toggleField.bind(this)
);
} else {
- content = renderSimpleCheckboxes(
- key,
- this.state.perms,
- this.toggleField.bind(this)
- );
+ content = renderSimpleCheckboxes(key, this.state.perms, this.toggleField.bind(this));
}
}
let trash = null;
if (!this.state.transitioning) {
trash = (
-
+
@@ -1116,11 +1087,7 @@ export default class PermissionsDialog extends React.Component {
this.toggleField.bind(this)
);
}
- return renderSimpleCheckboxes(
- '*',
- this.state.perms,
- this.toggleField.bind(this)
- );
+ return renderSimpleCheckboxes('*', this.state.perms, this.toggleField.bind(this));
}
renderAuthenticatedCheckboxes() {
@@ -1161,17 +1128,17 @@ export default class PermissionsDialog extends React.Component {
const allKeys = [...keys, ...newKeys];
// "userPointer:" fields that were not added yet
- let unusedPointerFields = userPointers.filter(
+ const unusedPointerFields = userPointers.filter(
ptr => !allKeys.includes(ptr) && ptr.includes(input)
);
// roles
- let prefixes = ['role:']
+ const prefixes = ['role:']
.filter(o => o.startsWith(input) && o.length > input.length) // filter matching input
.concat(...unusedPointerFields);
// pointer fields that are not applied yet;
- let availableFields = [];
+ const availableFields = [];
availableFields.push(...prefixes);
@@ -1179,7 +1146,7 @@ export default class PermissionsDialog extends React.Component {
}
render() {
- let classes = [styles.dialog, baseStyles.unselectable];
+ const classes = [styles.dialog, baseStyles.unselectable];
// for 3-column CLP dialog
if (this.props.advanced) {
@@ -1198,13 +1165,7 @@ export default class PermissionsDialog extends React.Component {
}
return (
-
+
{this.props.title}
@@ -1213,16 +1174,14 @@ export default class PermissionsDialog extends React.Component {
className={styles.settings}
onClick={() =>
this.setState(({ showLevels }) => ({
- showLevels: !showLevels
+ showLevels: !showLevels,
}))
}
>
) : null}
- {this.props.advanced && this.state.showLevels ? (
-
- ) : null}
+ {this.props.advanced && this.state.showLevels ?
: null}
@@ -1238,10 +1197,7 @@ export default class PermissionsDialog extends React.Component {
return;
}
this.setState({ level, transitioning: true });
- setTimeout(
- () => this.setState({ transitioning: false }),
- 700
- );
+ setTimeout(() => this.setState({ transitioning: false }), 700);
}}
/>
@@ -1253,9 +1209,7 @@ export default class PermissionsDialog extends React.Component {
@@ -1276,13 +1230,9 @@ export default class PermissionsDialog extends React.Component {
{this.state.keys
.slice(this.props.advanced ? 2 : 1)
- .map(key =>
- this.renderRow(key, this.state.columns, this.state.entryTypes)
- )}
+ .map(key => this.renderRow(key, this.state.columns, this.state.entryTypes))}
{this.props.advanced
- ? this.state.pointers.map(pointer =>
- this.renderRow(pointer, this.state.columns)
- )
+ ? this.state.pointers.map(pointer => this.renderRow(pointer, this.state.columns))
: null}
{this.state.newKeys.map(key =>
this.renderRow(key, this.state.columns, this.state.entryTypes)
@@ -1295,11 +1245,11 @@ export default class PermissionsDialog extends React.Component {
inputStyle={{
width: '290px',
padding: '0 6px',
- margin: '10px 0px 0px 20px'
+ margin: '10px 0px 0px 20px',
}}
suggestionsStyle={{
margin: '-6px 0px 0px 21px',
- width: '290px'
+ width: '290px',
}}
onChange={input => {
this.setState({ newEntry: input, entryError: undefined });
@@ -1309,13 +1259,13 @@ export default class PermissionsDialog extends React.Component {
buildSuggestions={input => this.suggestInput(input)}
buildLabel={input => this.buildLabel(input)}
error={this.state.entryError}
- />
+ />
-
+
(
- )
- }
+ ),
+ },
];
diff --git a/src/components/Pill/Pill.react.js b/src/components/Pill/Pill.react.js
index ffef318f39..e8fae726cc 100644
--- a/src/components/Pill/Pill.react.js
+++ b/src/components/Pill/Pill.react.js
@@ -5,20 +5,29 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/Pill/Pill.scss';
import Icon from 'components/Icon/Icon.react';
//TODO: refactor, may want to move onClick outside or need to make onClick able to handle link/button a11y
-let Pill = ({ value, onClick, fileDownloadLink, followClick = false, shrinkablePill = false }) => (
+const Pill = ({
+ value,
+ onClick,
+ fileDownloadLink,
+ followClick = false,
+ shrinkablePill = false,
+}) => (
- {value}
+
+ {value}
+
{followClick && (
!e.metaKey && onClick()}>
diff --git a/src/components/PlatformCard/PlatformCard.example.js b/src/components/PlatformCard/PlatformCard.example.js
index 3c54ac5549..04b4f994cb 100644
--- a/src/components/PlatformCard/PlatformCard.example.js
+++ b/src/components/PlatformCard/PlatformCard.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import PlatformCard from 'components/PlatformCard/PlatformCard.react';
export const component = PlatformCard;
@@ -13,49 +13,22 @@ export const component = PlatformCard;
export const demos = [
{
name: 'iOS PlatformCard',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'Xamarin PlatformCard',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'OSX PlatformCard',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'RPi PlatformCard',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'Arduino PlatformCard',
- render: () => (
-
- )
+ render: () => ,
},
];
diff --git a/src/components/PlatformCard/PlatformCard.react.js b/src/components/PlatformCard/PlatformCard.react.js
index afee1d67d4..a76bf73d69 100644
--- a/src/components/PlatformCard/PlatformCard.react.js
+++ b/src/components/PlatformCard/PlatformCard.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
+import Icon from 'components/Icon/Icon.react';
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/PlatformCard/PlatformCard.scss';
+import React from 'react';
+import styles from 'components/PlatformCard/PlatformCard.scss';
const FILL = {
blue: '#169cee',
@@ -16,29 +16,36 @@ const FILL = {
red: '#ff395e',
};
-let PlatformCard = ({ platform, name, subtitle, color }) => {
+const PlatformCard = ({ platform, name, subtitle, color }) => {
return (
-
+
+
+
{name}
{subtitle ?
{subtitle}
: null}
);
-}
+};
export default PlatformCard;
PlatformCard.propTypes = {
platform: PropTypes.oneOf([
- 'apple', 'android', 'xamarin', 'unity', 'php', 'windows', 'dotnet', 'arduino', 'rpi', 'rtos'
- ]).isRequired.describe(
- 'The platform name. This will determine the icon.'
- ),
- name: PropTypes.string.isRequired.describe(
- 'The display name.'
- ),
+ 'apple',
+ 'android',
+ 'xamarin',
+ 'unity',
+ 'php',
+ 'windows',
+ 'dotnet',
+ 'arduino',
+ 'rpi',
+ 'rtos',
+ ]).isRequired.describe('The platform name. This will determine the icon.'),
+ name: PropTypes.string.isRequired.describe('The display name.'),
subtitle: PropTypes.string.describe(
'Additional string that will be rendered below the display name.'
),
diff --git a/src/components/Popover/Popover.react.js b/src/components/Popover/Popover.react.js
index 10dfda9d36..adfd091c44 100644
--- a/src/components/Popover/Popover.react.js
+++ b/src/components/Popover/Popover.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import hasAncestor from 'lib/hasAncestor';
-import React from 'react';
-import styles from 'components/Popover/Popover.scss';
+import hasAncestor from 'lib/hasAncestor';
+import React from 'react';
+import styles from 'components/Popover/Popover.scss';
import { createPortal } from 'react-dom';
// We use this component to proxy the current tree's context
@@ -33,9 +33,7 @@ export default class Popover extends React.Component {
document.body.appendChild(this._popoverWrapper);
}
- let wrapperStyle = this.props.fixed
- ? styles.fixed_wrapper
- : styles.popover_wrapper;
+ const wrapperStyle = this.props.fixed ? styles.fixed_wrapper : styles.popover_wrapper;
this._popoverWrapper.className = wrapperStyle;
this._popoverWrapper.appendChild(this._popoverLayer);
@@ -59,6 +57,10 @@ export default class Popover extends React.Component {
this._popoverLayer.dataset.parentContentId = this.props.parentContentId;
}
+ if (this.props['data-popover-type']) {
+ this._popoverLayer.setAttribute('data-popover-type', this.props['data-popover-type']);
+ }
+
document.body.addEventListener('click', this._checkExternalClick);
}
@@ -75,12 +77,14 @@ export default class Popover extends React.Component {
_checkExternalClick(e) {
const { contentId } = this.props;
- const popoverWrapper = contentId
- ? document.getElementById(contentId)
- : this._popoverLayer;
+ const popoverWrapper = contentId ? document.getElementById(contentId) : this._popoverLayer;
const isChromeDropdown = e.target.parentNode.classList.contains('chromeDropdown');
+ // Find the inner popover element so on clicking inside it
+ // we can prevent external click function
+ const innerPopover = e.target.closest('[data-popover-type="inner"]');
if (
!hasAncestor(e.target, popoverWrapper, contentId) &&
+ !innerPopover &&
this.props.onExternalClick &&
!isChromeDropdown
) {
diff --git a/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.example.js b/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.example.js
index 6eb0e024d2..be53e0a4fc 100644
--- a/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.example.js
+++ b/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.example.js
@@ -5,27 +5,31 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import ProtectedFieldsDialog
- from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.react';
-import Button from 'components/Button/Button.react';
+import React from 'react';
+import ProtectedFieldsDialog from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.react';
+import Button from 'components/Button/Button.react';
export const component = ProtectedFieldsDialog;
-
function validateDemo(text) {
- if (text===('*')) {
- return Promise.resolve({ entry: '*' , type:'public'});
+ if (text === '*') {
+ return Promise.resolve({ entry: '*', type: 'public' });
}
- if (text===('authenticated')) {
- return Promise.resolve({ entry: 'authenticated' , type:'auth'});
+ if (text === 'authenticated') {
+ return Promise.resolve({ entry: 'authenticated', type: 'auth' });
}
if (text.startsWith('u')) {
- return Promise.resolve({ entry: { id: text, get:() => 'demouser' } , type:'user'});
+ return Promise.resolve({
+ entry: { id: text, get: () => 'demouser' },
+ type: 'user',
+ });
}
if (text.startsWith('role:')) {
- const roleName = text.substring(5)
- return Promise.resolve({ entry: {id:`1d0f${roleName}`, getName:()=>roleName}, type:'role' });
+ const roleName = text.substring(5);
+ return Promise.resolve({
+ entry: { id: `1d0f${roleName}`, getName: () => roleName },
+ type: 'role',
+ });
}
// if (text.startsWith('f')) {
@@ -38,63 +42,59 @@ function validateDemo(text) {
}
const columns = {
- 'email': { type: 'String',},
- 'password':{ type: 'String', },
- 'ptr_owner':{ type: 'Pointer', targetClass:'_User'},
- 'nickname':{ type: 'String',},
- 'ptr_followers':{ type: 'Array', },
- 'ptr_friends':{ type: 'Array', }
+ email: { type: 'String' },
+ password: { type: 'String' },
+ ptr_owner: { type: 'Pointer', targetClass: '_User' },
+ nickname: { type: 'String' },
+ ptr_followers: { type: 'Array' },
+ ptr_friends: { type: 'Array' },
};
-const userPointers = [
- 'ptr_followers',
- 'ptr_owner',
- 'ptr_friends'
-]
-
+const userPointers = ['ptr_followers', 'ptr_owner', 'ptr_friends'];
class ProtectedFieldsDemo extends React.Component {
constructor() {
- super()
+ super();
this.state = {
- show: false
+ show: false,
};
}
render() {
-
return (
{
this.setState({
- show: true
+ show: true,
});
- }}/>
+ }}
+ />
- Learn more about CLPs and app security}
- protectedFields={{
- '*': ['password', 'email'],
- 'userField:ptr_owner': [],
- 'us3r1d': ['password']
- }}
- userPointers={userPointers}
- columns={columns}
- validateEntry={validateDemo}
- onCancel={() => {
- this.setState({
- show: false,
- });
- }}
- onClose={()=>{}}
- onConfirm={(perms) => {
- console.log(perms);
- }} />
+ Learn more about CLPs and app security}
+ protectedFields={{
+ '*': ['password', 'email'],
+ 'userField:ptr_owner': [],
+ us3r1d: ['password'],
+ }}
+ userPointers={userPointers}
+ columns={columns}
+ validateEntry={validateDemo}
+ onCancel={() => {
+ this.setState({
+ show: false,
+ });
+ }}
+ onClose={() => {}}
+ onConfirm={perms => {
+ console.log(perms);
+ }}
+ />
);
}
@@ -103,7 +103,7 @@ class ProtectedFieldsDemo extends React.Component {
export const demos = [
{
render() {
- return ( );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.react.js b/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.react.js
index b0f062b216..001460f332 100644
--- a/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.react.js
+++ b/src/components/ProtectedFieldsDialog/ProtectedFieldsDialog.react.js
@@ -5,30 +5,30 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import hasAncestor from 'lib/hasAncestor';
-import Button from 'components/Button/Button.react';
-import Autocomplete from 'components/Autocomplete/Autocomplete.react';
-import Icon from 'components/Icon/Icon.react';
-import { Map } from 'immutable';
-import Pill from 'components/Pill/Pill.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import React from 'react';
-import ScrollHint from 'components/ScrollHint/ScrollHint.react'
-import styles from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.scss';
-import MultiSelect from 'components/MultiSelect/MultiSelect.react';
-import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
-import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
-import baseStyles from 'stylesheets/base.scss';
-
-let origin = new Position(0, 0);
+import hasAncestor from 'lib/hasAncestor';
+import Button from 'components/Button/Button.react';
+import Autocomplete from 'components/Autocomplete/Autocomplete.react';
+import Icon from 'components/Icon/Icon.react';
+import { Map } from 'immutable';
+import Pill from 'components/Pill/Pill.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import React from 'react';
+import ScrollHint from 'components/ScrollHint/ScrollHint.react';
+import styles from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.scss';
+import MultiSelect from 'components/MultiSelect/MultiSelect.react';
+import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
+import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
+import baseStyles from 'stylesheets/base.scss';
+
+const origin = new Position(0, 0);
const intersectionMargin = '10px 0px 0px 20px';
export default class ProtectedFieldsDialog extends React.Component {
constructor({ protectedFields, columns }) {
super();
- let keys = Object.keys(protectedFields || {});
+ const keys = Object.keys(protectedFields || {});
this.refEntry = React.createRef();
this.refTable = React.createRef();
@@ -48,7 +48,7 @@ export default class ProtectedFieldsDialog extends React.Component {
this.observer = new IntersectionObserver(callback, {
root: this.refTable.current,
rootMargin: intersectionMargin,
- threshold: [0.92]
+ threshold: [0.92],
});
this.state = {
@@ -59,27 +59,25 @@ export default class ProtectedFieldsDialog extends React.Component {
keys,
newEntry: '',
entryError: null,
- newKeys: []
+ newKeys: [],
};
}
async componentDidMount() {
// validate existing entries, also preserve their types (to render correct pills).
- const rows = await Promise.all(
- this.state.keys.map(key => this.props.validateEntry(key))
- );
+ const rows = await Promise.all(this.state.keys.map(key => this.props.validateEntry(key)));
let entryTypes = new Map({});
for (const { entry, type } of rows) {
let key;
- let value = {};
+ const value = {};
if (type === 'user') {
key = entry.id;
value[type] = {
name: entry.get('username'),
- id: entry.id
+ id: entry.id,
};
}
@@ -87,7 +85,7 @@ export default class ProtectedFieldsDialog extends React.Component {
key = 'role:' + entry.getName();
value[type] = {
name: entry.getName(),
- id: entry.id
+ id: entry.id,
};
}
@@ -109,12 +107,12 @@ export default class ProtectedFieldsDialog extends React.Component {
if (this.props.validateEntry) {
this.props.validateEntry(input).then(
({ type, entry }) => {
- let next = { [type]: entry };
+ const next = { [type]: entry };
let key;
let name;
let id;
- let newEntry = {};
+ const newEntry = {};
if (next.user || next.role) {
// entry for saving
@@ -130,25 +128,22 @@ export default class ProtectedFieldsDialog extends React.Component {
}
if (key) {
- if (
- this.state.keys.includes(key) ||
- this.state.newKeys.includes(key)
- ) {
+ if (this.state.keys.includes(key) || this.state.newKeys.includes(key)) {
return this.setState({
- entryError: 'You already have a row for this object'
+ entryError: 'You already have a row for this object',
});
}
- let nextKeys = this.state.newKeys.concat([key]);
- let nextFields = this.state.protectedFields.set(key, []);
- let nextEntryTypes = this.state.entryTypes.set(key, newEntry);
+ const nextKeys = this.state.newKeys.concat([key]);
+ const nextFields = this.state.protectedFields.set(key, []);
+ const nextEntryTypes = this.state.entryTypes.set(key, newEntry);
return this.setState(
{
entryTypes: nextEntryTypes,
protectedFields: nextFields,
newKeys: nextKeys,
- entryError: null
+ entryError: null,
},
this.refEntry.current.resetInput()
);
@@ -157,12 +152,11 @@ export default class ProtectedFieldsDialog extends React.Component {
() => {
if (this.props.enablePointerPermissions) {
this.setState({
- entryError:
- 'Role, User or field not found. Enter a valid id, name or column.'
+ entryError: 'Role, User or field not found. Enter a valid id, name or column.',
});
} else {
this.setState({
- entryError: 'Role or User not found. Enter a valid name or id'
+ entryError: 'Role or User not found. Enter a valid name or id',
});
}
}
@@ -172,21 +166,21 @@ export default class ProtectedFieldsDialog extends React.Component {
deleteRow(key) {
// remove from proectedFields
- let protectedFields = this.state.protectedFields.delete(key);
+ const protectedFields = this.state.protectedFields.delete(key);
// also remove from local state
- let keys = this.state.keys.filter(k => k !== key);
- let newKeys = this.state.newKeys.filter(k => k !== key);
+ const keys = this.state.keys.filter(k => k !== key);
+ const newKeys = this.state.newKeys.filter(k => k !== key);
return this.setState({
protectedFields,
newKeys,
- keys
+ keys,
});
}
outputPerms() {
- let output = this.state.protectedFields.toObject();
+ const output = this.state.protectedFields.toObject();
return output;
}
@@ -207,11 +201,11 @@ export default class ProtectedFieldsDialog extends React.Component {
* Renders Dropdown allowing to pick multiple fields for an entity (row).
*/
renderSelector(key, schema, selected) {
- let options = [];
- let values = selected || [];
+ const options = [];
+ const values = selected || [];
- let entries = Object.entries(schema);
- for (let [field, { type, targetClass }] of entries) {
+ const entries = Object.entries(schema);
+ for (const [field, { type, targetClass }] of entries) {
if (
field === 'objectId' ||
field === 'createdAt' ||
@@ -221,7 +215,7 @@ export default class ProtectedFieldsDialog extends React.Component {
continue;
}
- let pillText = type + (targetClass ? `<${targetClass}>` : '');
+ const pillText = type + (targetClass ? `<${targetClass}>` : '');
options.push(
@@ -233,17 +227,17 @@ export default class ProtectedFieldsDialog extends React.Component {
);
}
- let noAvailableFields = options.length === 0;
+ const noAvailableFields = options.length === 0;
- if(noAvailableFields){
+ if (noAvailableFields) {
options.push(
-
- {'This class has no fields to protect'}
+
+ {'This class has no fields to protect'}
- )
+ );
}
- const placeholder = 'All fields allowed.'+ (noAvailableFields ? '': ' Click to protect.');
+ const placeholder = 'All fields allowed.' + (noAvailableFields ? '' : ' Click to protect.');
return (
@@ -301,8 +295,7 @@ export default class ProtectedFieldsDialog extends React.Component {
- username:{' '}
- {type.user.name}
+ username: {type.user.name}
);
@@ -330,8 +323,8 @@ export default class ProtectedFieldsDialog extends React.Component {
}
if (type.pointer) {
- let { type, targetClass } = columns[key.substring(10)];
- let pillText = type + (targetClass ? `<${targetClass}>` : '');
+ const { type, targetClass } = columns[key.substring(10)];
+ const pillText = type + (targetClass ? `<${targetClass}>` : '');
label = (
@@ -347,20 +340,13 @@ export default class ProtectedFieldsDialog extends React.Component {
let content = null;
if (!this.state.transitioning) {
- content = this.renderSelector(
- key,
- this.state.columns,
- this.state.protectedFields.get(key)
- );
+ content = this.renderSelector(key, this.state.columns, this.state.protectedFields.get(key));
}
let trash = null;
if (!this.state.transitioning) {
trash = (
-
+
@@ -393,21 +379,20 @@ export default class ProtectedFieldsDialog extends React.Component {
const newKeys = this.state.newKeys;
const allKeys = [...keys, ...newKeys];
- let availablePointerFields = userPointers
+ const availablePointerFields = userPointers
.map(ptr => `userField:${ptr}`)
.filter(ptr => !allKeys.includes(ptr) && ptr.includes(input));
- let possiblePrefix = ['role:']
+ const possiblePrefix = ['role:']
.filter(o => o.startsWith(input) && o.length > input.length) // filter matching prefixes
.concat(...availablePointerFields); //
// pointer fields that are not applied yet;
- let availableFields = [];
+ const availableFields = [];
// do not suggest unique rows that are already added;
- let uniqueOptions = ['*', 'authenticated'].filter(
- key =>
- !allKeys.includes(key) && (input.length == 0 || key.startsWith(input))
+ const uniqueOptions = ['*', 'authenticated'].filter(
+ key => !allKeys.includes(key) && (input.length == 0 || key.startsWith(input))
);
availableFields.push(...uniqueOptions);
@@ -421,7 +406,7 @@ export default class ProtectedFieldsDialog extends React.Component {
{
activeSuggestion: 0,
newEntry: e.currentTarget.innerText,
- showSuggestions: false
+ showSuggestions: false,
},
() => {
this.props.onChange(this.state.newEntry);
@@ -443,9 +428,9 @@ export default class ProtectedFieldsDialog extends React.Component {
}
render() {
- let classes = [styles.dialog, baseStyles.unselectable];
+ const classes = [styles.dialog, baseStyles.unselectable];
- let placeholderText = 'Role/User id/name * or authenticated\u2026';
+ const placeholderText = 'Role/User id/name * or authenticated\u2026';
return (
this.onUserInput(e)}
onSubmit={this.checkEntry.bind(this)}
@@ -498,7 +483,7 @@ export default class ProtectedFieldsDialog extends React.Component {
-
+
{
+const setFocus = input => {
if (input !== null) {
input.focus();
}
-}
+};
function compareValue(info, value, onChangeCompareTo) {
- let type = info.type;
+ const type = info.type;
switch (type) {
case null:
return -
;
case 'String':
- return onChangeCompareTo(_value)} ref={setFocus}/>;
+ return (
+ onChangeCompareTo(_value)}
+ ref={setFocus}
+ />
+ );
case 'Pointer':
- return {
- let obj = new Parse.Object(info.targetClass);
- obj.id = _value;
- onChangeCompareTo(Parse._encode(obj.toPointer()));
- }}
- ref={setFocus} />
+ return (
+ {
+ const obj = new Parse.Object(info.targetClass);
+ obj.id = _value;
+ onChangeCompareTo(Parse._encode(obj.toPointer()));
+ }}
+ ref={setFocus}
+ />
+ );
case 'Boolean':
- return onChangeCompareTo(_value === 'True')} >
- {['True', 'False'].map(value => {value} )}
+ return (
+ onChangeCompareTo(_value === 'True')}
+ >
+ {['True', 'False'].map(value => (
+
+ {value}
+
+ ))}
+ );
case 'Number':
- return onChangeCompareTo(validateNumeric(_value) ? Number(_value) : Number(value))} ref={setFocus}/>;
+ return (
+
+ onChangeCompareTo(validateNumeric(_value) ? Number(_value) : Number(value))
+ }
+ ref={setFocus}
+ />
+ );
case 'Date':
- return onChangeCompareTo(Parse._encode(_value))}
- ref={setFocus} />
+ return (
+ onChangeCompareTo(Parse._encode(_value))}
+ ref={setFocus}
+ />
+ );
}
}
export default class InstallationCondition extends React.Component {
constructor() {
- super()
+ super();
this.state = {
open: false,
filters: new List(),
@@ -74,13 +104,13 @@ export default class InstallationCondition extends React.Component {
}
handleChange(type, selection) {
- let stateChange = {};
+ const stateChange = {};
stateChange[type] = selection;
this.setState(stateChange);
}
render() {
- let input = (
+ const input = (
- {this.props.fields.map(function(object, i){
- return {object} ;
+ placeHolder="field"
+ className={styles.conditionDropdown}
+ >
+ {this.props.fields.map(function (object, i) {
+ return (
+
+ {object}
+
+ );
})}
@@ -100,11 +135,16 @@ export default class InstallationCondition extends React.Component {
fixed={true}
hideArrow={true}
value={Constraints[this.props.currentConstraint].name}
- onChange={(c) => this.props.onChangeConstraint(constraintLookup[c])}
- placeHolder='is'
- className={styles.conditionDropdown}>
- {this.props.constraints.map(function(object, i){
- return
{Constraints[object].name} ;
+ onChange={c => this.props.onChangeConstraint(constraintLookup[c])}
+ placeHolder="is"
+ className={styles.conditionDropdown}
+ >
+ {this.props.constraints.map(function (object, i) {
+ return (
+
+ {Constraints[object].name}
+
+ );
})}
@@ -114,11 +154,8 @@ export default class InstallationCondition extends React.Component {
);
- let labelDescription = (
-
+ const labelDescription = (
+
Remove
);
@@ -126,8 +163,9 @@ export default class InstallationCondition extends React.Component {
return (
}
- input={input} />
+ label={ }
+ input={input}
+ />
);
}
}
diff --git a/src/components/PushAudienceDialog/PushAudienceDialog.example.js b/src/components/PushAudienceDialog/PushAudienceDialog.example.js
index c749dac0f0..09ae755ff5 100644
--- a/src/components/PushAudienceDialog/PushAudienceDialog.example.js
+++ b/src/components/PushAudienceDialog/PushAudienceDialog.example.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
+import Button from 'components/Button/Button.react';
import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceDialog.react';
-import React from 'react';
+import React from 'react';
export const component = PushAudienceDialog;
class PushAudienceDialogDemo extends React.Component {
constructor() {
- super()
+ super();
this.state = {
- showModal: false
+ showModal: false,
};
}
@@ -23,30 +23,33 @@ class PushAudienceDialogDemo extends React.Component {
return (
{
this.setState({
- showModal: true
+ showModal: true,
});
- }}>
-
- {this.state.showModal ?
{
- this.setState({
- showModal: false
- });
- }}/> : null}
+ }}
+ >
+ {this.state.showModal ? (
+ {
+ this.setState({
+ showModal: false,
+ });
+ }}
+ />
+ ) : null}
- )
+ );
}
}
export const demos = [
{
render() {
- return ( );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/PushAudienceDialog/PushAudienceDialog.react.js b/src/components/PushAudienceDialog/PushAudienceDialog.react.js
index 4e40b76bd3..46e2444a6d 100644
--- a/src/components/PushAudienceDialog/PushAudienceDialog.react.js
+++ b/src/components/PushAudienceDialog/PushAudienceDialog.react.js
@@ -5,34 +5,36 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as Filters from 'lib/Filters';
-import * as PushUtils from 'lib/PushUtils';
-import * as PushConstants from 'dashboard/Push/PushConstants';
-import Button from 'components/Button/Button.react';
-import Field from 'components/Field/Field.react';
-import Filter from 'components/Filter/Filter.react';
-import FormNote from 'components/FormNote/FormNote.react';
+import * as Filters from 'lib/Filters';
+import * as PushUtils from 'lib/PushUtils';
+import * as PushConstants from 'dashboard/Push/PushConstants';
+import Button from 'components/Button/Button.react';
+import Field from 'components/Field/Field.react';
+import Filter from 'components/Filter/Filter.react';
+import FormNote from 'components/FormNote/FormNote.react';
import InstallationCondition from 'components/PushAudienceDialog/InstallationCondition.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import MultiSelect from 'components/MultiSelect/MultiSelect.react';
-import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
-import PropTypes from 'lib/PropTypes';
-import queryFromFilters from 'lib/queryFromFilters';
-import React from 'react';
-import styles from 'components/PushAudienceDialog/PushAudienceDialog.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import { List, Map } from 'immutable';
-import { CurrentApp } from 'context/currentApp';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import MultiSelect from 'components/MultiSelect/MultiSelect.react';
+import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
+import PropTypes from 'lib/PropTypes';
+import queryFromFilters from 'lib/queryFromFilters';
+import React from 'react';
+import styles from 'components/PushAudienceDialog/PushAudienceDialog.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import { List, Map } from 'immutable';
+import { CurrentApp } from 'context/currentApp';
const PARSE_SERVER_SUPPORTS_SAVED_AUDIENCES = true;
const AUDIENCE_SIZE_FETCHING_ENABLED = true;
-let filterFormatter = (filters, schema) => {
- return filters.map((filter) => {
+const filterFormatter = (filters, schema) => {
+ return filters.map(filter => {
let type = schema[filter.get('field')];
- if (Object.prototype.hasOwnProperty.call(Filters.Constraints[filter.get('constraint')], 'field')) {
+ if (
+ Object.prototype.hasOwnProperty.call(Filters.Constraints[filter.get('constraint')], 'field')
+ ) {
type = Filters.Constraints[filter.get('constraint')].field;
}
// Format any stringified fields
@@ -41,7 +43,7 @@ let filterFormatter = (filters, schema) => {
}
return filter;
});
-}
+};
export default class PushAudienceDialog extends React.Component {
static contextType = CurrentApp;
@@ -61,12 +63,12 @@ export default class PushAudienceDialog extends React.Component {
}
componentWillMount() {
- let stateSettings = {};
- let audienceInfo = this.props.audienceInfo;
+ const stateSettings = {};
+ const audienceInfo = this.props.audienceInfo;
//this case is only for 'New Segment' to prepopulate existing audience
if (audienceInfo) {
if (audienceInfo.query) {
- let { deviceType } = audienceInfo.query;
+ const { deviceType } = audienceInfo.query;
stateSettings.platforms = deviceType.$in || [];
}
if (audienceInfo.filters) {
@@ -86,22 +88,25 @@ export default class PushAudienceDialog extends React.Component {
}
handleChange(newValue) {
- this.setState(
- { platforms: newValue },
- this.fetchAudienceSize.bind(this)
- );
+ this.setState({ platforms: newValue }, this.fetchAudienceSize.bind(this));
}
handleAddCondition() {
if (!this.props.schema || !Object.keys(this.props.schema).length) {
- this.setState({ errorMessage: 'You first need to create the Installation class before adding conditions to an audience.' });
+ this.setState({
+ errorMessage:
+ 'You first need to create the Installation class before adding conditions to an audience.',
+ });
return;
}
- let available = Filters.availableFilters(this.props.schema, this.state.filters);
- let field = Object.keys(available)[0];
- this.setState(({ filters }) => ({
- filters: filters.push(new Map({ field: field, constraint: available[field][0] }))
- }), this.fetchAudienceSize.bind(this));
+ const available = Filters.availableFilters(this.props.schema, this.state.filters);
+ const field = Object.keys(available)[0];
+ this.setState(
+ ({ filters }) => ({
+ filters: filters.push(new Map({ field: field, constraint: available[field][0] })),
+ }),
+ this.fetchAudienceSize.bind(this)
+ );
}
handleAudienceName(name) {
@@ -114,19 +119,24 @@ export default class PushAudienceDialog extends React.Component {
}
fetchAudienceSize() {
- if (!this.context) { //so we don't break the PIG demo
+ if (!this.context) {
+ //so we don't break the PIG demo
return;
}
let query = {};
- let parseQuery = queryFromFilters('_Installation', this.state.filters);
+ const parseQuery = queryFromFilters('_Installation', this.state.filters);
if (parseQuery && parseQuery.toJSON()) {
query = parseQuery.toJSON().where || {};
}
query.deviceType = { $in: this.state.platforms };
- let {xhr, promise} = this.context.fetchPushSubscriberCount(PushConstants.NEW_SEGMENT_ID, query);
- if (this.xhrHandle) { //cancel existing xhr - prevent from stacking
+ const { xhr, promise } = this.context.fetchPushSubscriberCount(
+ PushConstants.NEW_SEGMENT_ID,
+ query
+ );
+ if (this.xhrHandle) {
+ //cancel existing xhr - prevent from stacking
this.xhrHandle.abort();
}
this.xhrHandle = xhr;
@@ -139,11 +149,16 @@ export default class PushAudienceDialog extends React.Component {
}
valid() {
- if (this.state.platforms.length === 0) {//check that at least one platform is chosen
+ if (this.state.platforms.length === 0) {
+ //check that at least one platform is chosen
return false;
}
- if ((this.state.saveForFuture || this.props.disableNewSegment) && this.state.audienceName.length === 0) { //check that a name is written
+ if (
+ (this.state.saveForFuture || this.props.disableNewSegment) &&
+ this.state.audienceName.length === 0
+ ) {
+ //check that a name is written
return false;
}
//TODO check if conditions are valid
@@ -151,67 +166,75 @@ export default class PushAudienceDialog extends React.Component {
}
render() {
- let options = [];
- let availableDevices = this.props.availableDevices;
+ const options = [];
+ const availableDevices = this.props.availableDevices;
// TODO: handle empty case when 0 devices - should display link to device creation.
// TODO: handle misconfigured device link
- for (let index in availableDevices) {
+ for (const index in availableDevices) {
options.push(
-
+
{PushConstants.DEVICE_MAP[availableDevices[index]]}
);
}
- let platformSelect = (
+ const platformSelect = (
+ placeHolder="Choose some platforms..."
+ >
{options}
);
- let nonEmptyConditions = this.state.filters.size !== 0 ? true : false;
- let audienceSize = PushUtils.formatCountDetails(this.state.audienceSize, this.state.approximate);
- let customFooter = (
+ const nonEmptyConditions = this.state.filters.size !== 0 ? true : false;
+ const audienceSize = PushUtils.formatCountDetails(
+ this.state.audienceSize,
+ this.state.approximate
+ );
+ const customFooter = (
- {AUDIENCE_SIZE_FETCHING_ENABLED ?
-
AUDIENCE SIZE
-
{audienceSize}
-
: null}
-
+ {AUDIENCE_SIZE_FETCHING_ENABLED ? (
+
+
AUDIENCE SIZE
+
{audienceSize}
+
+ ) : null}
+
+ onClick={this.props.primaryAction.bind(undefined, {
+ platforms: this.state.platforms,
+ name: this.state.audienceName,
+ filters: this.state.filters,
+ formattedFilters: filterFormatter(this.state.filters, this.props.schema),
+ saveForFuture: this.state.saveForFuture,
+ })}
+ />
);
- let futureUseSegment = [];
+ const futureUseSegment = [];
if (!this.props.disableNewSegment) {
if (PARSE_SERVER_SUPPORTS_SAVED_AUDIENCES) {
futureUseSegment.push(
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
);
}
@@ -220,8 +243,14 @@ export default class PushAudienceDialog extends React.Component {
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
);
}
} else {
@@ -229,43 +258,59 @@ export default class PushAudienceDialog extends React.Component {
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
);
}
return (
+ customFooter={customFooter}
+ >
}
- input={platformSelect} />
+ label={ }
+ input={platformSelect}
+ />
-
- {
- this.setState(
- { filters },
- this.fetchAudienceSize.bind(this)
- );
- }
- }
- renderRow={(props) => } />
+ {
+ this.setState({ filters }, this.fetchAudienceSize.bind(this));
+ }}
+ renderRow={props => }
+ />
-
-
+
+
{futureUseSegment}
0) || (this.state.errorMessage && this.state.errorMessage.length > 0))}
- color='red' >
+ show={Boolean(
+ (this.props.errorMessage && this.props.errorMessage.length > 0) ||
+ (this.state.errorMessage && this.state.errorMessage.length > 0)
+ )}
+ color="red"
+ >
{this.props.errorMessage || this.state.errorMessage}
@@ -274,9 +319,7 @@ export default class PushAudienceDialog extends React.Component {
}
PushAudienceDialog.propTypes = {
- editMode: PropTypes.bool.describe(
- 'Flag if true to be edit mode of dialog.'
- ),
+ editMode: PropTypes.bool.describe('Flag if true to be edit mode of dialog.'),
primaryAction: PropTypes.func.isRequired.describe(
'Primary callback triggered when submitting modal.'
),
@@ -296,4 +339,3 @@ PushAudienceDialog.propTypes = {
'List of all availableDevices devices for push notifications.'
),
};
-
diff --git a/src/components/PushAudiencesSelector/PushAudiencesBaseRow.react.js b/src/components/PushAudiencesSelector/PushAudiencesBaseRow.react.js
index 5b00f94080..dd6d5138e6 100644
--- a/src/components/PushAudiencesSelector/PushAudiencesBaseRow.react.js
+++ b/src/components/PushAudiencesSelector/PushAudiencesBaseRow.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import { NEW_SEGMENT_ID } from 'dashboard/Push/PushConstants';
-import { CurrentApp } from 'context/currentApp';
+import { CurrentApp } from 'context/currentApp';
export default class PushAudiencesBaseRow extends React.Component {
static contextType = CurrentApp;
@@ -19,32 +19,36 @@ export default class PushAudiencesBaseRow extends React.Component {
expandedView: false,
approximate: false,
isNewSegment: false,
- }
+ };
}
handleDetailsToggle(query, schema, evt) {
evt.preventDefault();
this.setState({
- expandedView : !this.state.expandedView
+ expandedView: !this.state.expandedView,
});
}
fetchPushSubscriberCount(context) {
- if (!context) { //so we don't break the PIG demo
+ if (!context) {
+ //so we don't break the PIG demo
return;
}
- let query = this.props.id === NEW_SEGMENT_ID ? this.props.query : null;
+ const query = this.props.id === NEW_SEGMENT_ID ? this.props.query : null;
//Added count fetch logic directly to component
- let {xhr, promise} = context.fetchPushSubscriberCount(this.props.id, query);
+ const { xhr, promise } = context.fetchPushSubscriberCount(this.props.id, query);
this.xhrHandle = xhr;
- promise.then(({ approximate, count }) => {
- this.setState({ approximate, count });
- }, () => {});
+ promise.then(
+ ({ approximate, count }) => {
+ this.setState({ approximate, count });
+ },
+ () => {}
+ );
}
componentWillMount() {
- this.fetchPushSubscriberCount.call(this,this.context);
+ this.fetchPushSubscriberCount.call(this, this.context);
if (this.props.id == NEW_SEGMENT_ID) {
this.setState({ isNewSegment: true });
}
diff --git a/src/components/PushAudiencesSelector/PushAudiencesOption.example.js b/src/components/PushAudiencesSelector/PushAudiencesOption.example.js
index 64441cd7a5..96fdc6db4f 100644
--- a/src/components/PushAudiencesSelector/PushAudiencesOption.example.js
+++ b/src/components/PushAudiencesSelector/PushAudiencesOption.example.js
@@ -10,24 +10,24 @@ import PushAudiencesOption from 'components/PushAudiencesSelector/PushAudiencesO
export const component = PushAudiencesOption;
-let mockData = {
+const mockData = {
id: '1',
name: 'Everyone',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
};
export const demos = [
{
- render() {
- return (
-
- );
- }
-}
+ render() {
+ return (
+
+ );
+ },
+ },
];
diff --git a/src/components/PushAudiencesSelector/PushAudiencesOption.react.js b/src/components/PushAudiencesSelector/PushAudiencesOption.react.js
index f91e5c1b6a..d93dd928a1 100644
--- a/src/components/PushAudiencesSelector/PushAudiencesOption.react.js
+++ b/src/components/PushAudiencesSelector/PushAudiencesOption.react.js
@@ -5,14 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as PushUtils from 'lib/PushUtils';
-import Icon from 'components/Icon/Icon.react';
-import PropTypes from 'lib/PropTypes';
-import RadioButton from 'components/RadioButton/RadioButton.react';
-import React from 'react';
-import styles from 'components/PushAudiencesSelector/PushAudiencesOption.scss';
+import * as PushUtils from 'lib/PushUtils';
+import Icon from 'components/Icon/Icon.react';
+import PropTypes from 'lib/PropTypes';
+import RadioButton from 'components/RadioButton/RadioButton.react';
+import React from 'react';
+import styles from 'components/PushAudiencesSelector/PushAudiencesOption.scss';
import { yearMonthDayFormatter } from 'lib/DateUtils';
-import PushAudiencesBaseRow from 'components/PushAudiencesSelector/PushAudiencesBaseRow.react';
+import PushAudiencesBaseRow from 'components/PushAudiencesSelector/PushAudiencesBaseRow.react';
const FORM_PREFIX = 'audience_radio';
@@ -28,11 +28,11 @@ export default class PushAudiencesOption extends PushAudiencesBaseRow {
expandedView: false,
approximate: false,
isNewSegment: false,
- }
+ };
}
handleChange() {
- if(this.props.onChange && !this.props.isDisabled){
+ if (this.props.onChange && !this.props.isDisabled) {
this.props.onChange.call(undefined, this.state.count);
} else {
return;
@@ -41,25 +41,31 @@ export default class PushAudiencesOption extends PushAudiencesBaseRow {
// pass count information to PushNew flow
componentWillUpdate(nextProps, nextState) {
- if ((nextState.count !== this.state.count || JSON.stringify(nextProps.query) !== JSON.stringify(this.props.query))
- && this.props.isChecked) {
+ if (
+ (nextState.count !== this.state.count ||
+ JSON.stringify(nextProps.query) !== JSON.stringify(this.props.query)) &&
+ this.props.isChecked
+ ) {
nextProps.onChange.call(undefined, nextState.count);
}
}
render() {
- let inputId=[FORM_PREFIX, this.props.id].join('_');
+ const inputId = [FORM_PREFIX, this.props.id].join('_');
- let detailsView = this.state.expandedView ?
- PushUtils.largeInfoBuilder(this.props.query, this.props.schema, styles) :
- (
-
- {PushUtils.shortInfoBuilder(this.props.query, this.props.schema)}
-
- );
+ const detailsView = this.state.expandedView ? (
+ PushUtils.largeInfoBuilder(this.props.query, this.props.schema, styles)
+ ) : (
+
+ {PushUtils.shortInfoBuilder(this.props.query, this.props.schema)}
+
+ );
return (
-
+
+ disabled={this.props.isDisabled}
+ />
- { this.props.icon ?
+ {this.props.icon ? (
-
-
:
- null }
-
{this.props.name}
+
+
+ ) : null}
+
+ {this.props.name}
+
{detailsView}
- {!this.state.isNewSegment ?
+ {!this.state.isNewSegment ? (
+ type="button"
+ className={[
+ styles.moreDetails,
+ !this.props.query ? styles.hideMoreDetails : '',
+ ].join(' ')}
+ onClick={this.handleDetailsToggle.bind(this, this.props.query, this.props.schema)}
+ >
{this.state.expandedView ? 'less details' : 'more details'}
- :
+
+ ) : (
+ })}
+ >
{'Edit audience'}
- }
+ )}
- {AUDIENCE_SIZE_FETCHING_ENABLED ?
- {PushUtils.formatCountDetails(this.state.count, this.state.approximate)}
-
: null}
- {AUDIENCE_CREATED_DATE_ENABLED ?
- {yearMonthDayFormatter(this.props.createdAt)}
-
: null}
+ {AUDIENCE_SIZE_FETCHING_ENABLED ? (
+
+ {PushUtils.formatCountDetails(this.state.count, this.state.approximate)}
+
+ ) : null}
+ {AUDIENCE_CREATED_DATE_ENABLED ? (
+
+ {yearMonthDayFormatter(this.props.createdAt)}
+
+ ) : null}
);
}
}
PushAudiencesOption.propTypes = {
- id: PropTypes.string.isRequired.describe(
- 'The id of the push audience option.'
- ),
- name: PropTypes.string.describe(
- 'The name of the push audience option.'
- ),
- createdAt: PropTypes.instanceOf(Date).describe(
- 'The size of the push audience option.'
- ),
+ id: PropTypes.string.isRequired.describe('The id of the push audience option.'),
+ name: PropTypes.string.describe('The name of the push audience option.'),
+ createdAt: PropTypes.instanceOf(Date).describe('The size of the push audience option.'),
query: PropTypes.object.describe(
'Key value pair of installation condition info for the specific audience'
),
- isChecked: PropTypes.bool.describe(
- 'Boolean describing if option is currently selected.'
- ),
- onChange: PropTypes.func.isRequired.describe(
- 'Callback that is executed on option change.'
- ),
- isDisabled: PropTypes.bool.describe(
- 'Boolean describing if option is disabled.'
- ),
- schema: PropTypes.object.describe(
- 'Schema of installation.'
- ),
+ isChecked: PropTypes.bool.describe('Boolean describing if option is currently selected.'),
+ onChange: PropTypes.func.isRequired.describe('Callback that is executed on option change.'),
+ isDisabled: PropTypes.bool.describe('Boolean describing if option is disabled.'),
+ schema: PropTypes.object.describe('Schema of installation.'),
onEditAudience: PropTypes.func.isRequired.describe(
'Callback that is executed on click of edit audience for new segment.'
),
- icon: PropTypes.string.describe(
- 'Specifiying icon for option. Used for "Everyone" case.'
- ),
+ icon: PropTypes.string.describe('Specifiying icon for option. Used for "Everyone" case.'),
};
diff --git a/src/components/PushAudiencesSelector/PushAudiencesSelector.example.js b/src/components/PushAudiencesSelector/PushAudiencesSelector.example.js
index 751aab69a5..93ef471f56 100644
--- a/src/components/PushAudiencesSelector/PushAudiencesSelector.example.js
+++ b/src/components/PushAudiencesSelector/PushAudiencesSelector.example.js
@@ -13,69 +13,71 @@ export const component = PushAudiencesSelector;
class PushAudiencesSelectorDemo extends React.Component {
constructor() {
super();
- this.state = { current: '1'};
+ this.state = { current: '1' };
}
handleChange(value) {
- this.setState({ current: value});
+ this.setState({ current: value });
}
render() {
return (
-
+
);
}
}
-let mockData = [
+const mockData = [
{
objectId: '1',
name: 'Everyone',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '2',
name: 'iOS Users in US',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
- {
+ {
objectId: '3',
name: 'Completed Checkout <30 days',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
- {
+ {
objectId: '4',
name: 'New Users',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '5',
name: 'Everyone',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '6',
name: 'iOS Users in US',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '7',
name: 'Completed Checkout <30 days',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
{
objectId: '8',
name: 'New Users',
- createdAt: new Date(1444757195683)
+ createdAt: new Date(1444757195683),
},
];
export const demos = [
{
render() {
- return (
-
- );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/PushAudiencesSelector/PushAudiencesSelector.react.js b/src/components/PushAudiencesSelector/PushAudiencesSelector.react.js
index a58f262ad4..970bde709a 100644
--- a/src/components/PushAudiencesSelector/PushAudiencesSelector.react.js
+++ b/src/components/PushAudiencesSelector/PushAudiencesSelector.react.js
@@ -5,55 +5,50 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as PushConstants from 'dashboard/Push/PushConstants.js';
-import PropTypes from 'lib/PropTypes';
+import * as PushConstants from 'dashboard/Push/PushConstants.js';
+import PropTypes from 'lib/PropTypes';
import PushAudiencesOption from 'components/PushAudiencesSelector/PushAudiencesOption.react';
-import React from 'react';
-import styles from 'components/PushAudiencesSelector/PushAudiencesSelector.scss';
-import { fromJS } from 'immutable';
+import React from 'react';
+import styles from 'components/PushAudiencesSelector/PushAudiencesSelector.scss';
+import { fromJS } from 'immutable';
const AUDIENCE_SIZE_FETCHING_ENABLED = true;
const AUDIENCE_CREATED_DATE_ENABLED = true;
-const PushAudiencesOptions = ({
- current,
- onChange,
- onEditAudience,
- schema,
- audiences,
-}) =>
- {audiences.map(({
- icon,
- objectId,
- name,
- query,
- createdAt,
- filters,
- }) => {
- const queryOrFilters = objectId === PushConstants.NEW_SEGMENT_ID ?
- filters.push(fromJS({
- field: 'deviceType',
- constraint: 'containedIn',
- array: query.deviceType['$in'],
- })) :
- query;
- return
onChange(objectId, queryOrFilters)}
- onEditAudience={onEditAudience}
- schema={schema}
- filters={filters} />
- })}
-
+const PushAudiencesOptions = ({ current, onChange, onEditAudience, schema, audiences }) => (
+
+ {audiences.map(({ icon, objectId, name, query, createdAt, filters }) => {
+ const queryOrFilters =
+ objectId === PushConstants.NEW_SEGMENT_ID
+ ? filters.push(
+ fromJS({
+ field: 'deviceType',
+ constraint: 'containedIn',
+ array: query.deviceType['$in'],
+ })
+ )
+ : query;
+ return (
+
onChange(objectId, queryOrFilters)}
+ onEditAudience={onEditAudience}
+ schema={schema}
+ filters={filters}
+ />
+ );
+ })}
+
+);
-let PushAudiencesSelector = ({
+const PushAudiencesSelector = ({
defaultAudience,
newSegment,
audiences,
@@ -62,49 +57,58 @@ let PushAudiencesSelector = ({
onChange,
onEditAudience,
schema,
-}) =>
-
-
-
- Audience
+}) => (
+
+
+
+
Audience
+ {AUDIENCE_SIZE_FETCHING_ENABLED ? (
+
Size
+ ) : null}
+ {AUDIENCE_CREATED_DATE_ENABLED ? (
+
Created
+ ) : null}
- {AUDIENCE_SIZE_FETCHING_ENABLED ?
- Size
-
: null}
- {AUDIENCE_CREATED_DATE_ENABLED ?
- Created
-
: null}
+ {children}
- {children}
-
+);
PushAudiencesSelector.propTypes = {
audiences: PropTypes.object.isRequired.describe('Immutable List of push audiences.'),
- defaultAudience: PropTypes.object.describe('Default push audience option. Not added to the store. Everyone.'),
- newSegment: PropTypes.object.describe('New segment (one time use) push audience option. Not added to the store.'),
+ defaultAudience: PropTypes.object.describe(
+ 'Default push audience option. Not added to the store. Everyone.'
+ ),
+ newSegment: PropTypes.object.describe(
+ 'New segment (one time use) push audience option. Not added to the store.'
+ ),
current: PropTypes.string.isRequired.describe('id of the currently selected row.'),
onChange: PropTypes.func.isRequired.describe('callback to be executed when option has changed'),
schema: PropTypes.object.describe('Schema of installation'),
- onEditAudience: PropTypes.func.isRequired.describe('Callback that is executed on click of edit audience for new segment.'),
+ onEditAudience: PropTypes.func.isRequired.describe(
+ 'Callback that is executed on click of edit audience for new segment.'
+ ),
};
export default PushAudiencesSelector;
diff --git a/src/components/PushCerts/CertsTable.react.js b/src/components/PushCerts/CertsTable.react.js
index db8db279fd..9c1524c6d8 100644
--- a/src/components/PushCerts/CertsTable.react.js
+++ b/src/components/PushCerts/CertsTable.react.js
@@ -11,11 +11,11 @@ import { dateStringUTC } from 'lib/DateUtils';
const MONTH_IN_MS = 1000 * 60 * 60 * 24 * 30;
-let CertsTable = ({ certs, onDelete, uploadPending }) => {
- let tableData = certs.map(c => {
+const CertsTable = ({ certs, onDelete, uploadPending }) => {
+ const tableData = certs.map(c => {
let color = '';
let expiresKeyColor = '';
- let isExpired = new Date(c.expiration) < Date.now();
+ const isExpired = new Date(c.expiration) < Date.now();
if (isExpired) {
expiresKeyColor = color = 'red';
} else if (new Date(c.expiration) - Date.now() < MONTH_IN_MS) {
@@ -37,15 +37,15 @@ let CertsTable = ({ certs, onDelete, uploadPending }) => {
key: isExpired ? 'Expired' : 'Expires',
keyColor: expiresKeyColor,
value: dateStringUTC(new Date(c.expiration)),
- }
- ]
+ },
+ ],
};
- })
+ });
if (uploadPending) {
tableData.unshift({
title: 'Processing File\u2026',
color: 'blue',
- notes:[
+ notes: [
{
key: 'Type',
value: 'Not Sure',
@@ -54,11 +54,11 @@ let CertsTable = ({ certs, onDelete, uploadPending }) => {
{
key: 'Expires',
value: 'TBD',
- }
+ },
],
});
}
- return
+ return
;
};
export default CertsTable;
diff --git a/src/components/PushCerts/PushCerts.example.js b/src/components/PushCerts/PushCerts.example.js
index 5375c06460..2a6876227c 100644
--- a/src/components/PushCerts/PushCerts.example.js
+++ b/src/components/PushCerts/PushCerts.example.js
@@ -7,7 +7,7 @@
*/
import { daysFrom } from 'lib/DateUtils';
import PushCerts from 'components/PushCerts/PushCerts.react';
-import React from 'react';
+import React from 'react';
export const component = PushCerts;
@@ -17,30 +17,49 @@ export const demos = [
{}}
- onDelete={()=>{}} />
- )
- }, {
- render: () => (
- {}}
- onDelete={()=>{}} />
- )
- }, {
+ onUpload={() => {}}
+ onDelete={() => {}}
+ />
+ ),
+ },
+ {
+ render: () => {}} onDelete={() => {}} />,
+ },
+ {
render: () => (
{}}
- onDelete={()=>{}} />
- )
+ error="Something happened, you might want to get that checked out."
+ onUpload={() => {}}
+ onDelete={() => {}}
+ />
+ ),
},
];
diff --git a/src/components/PushCerts/PushCerts.react.js b/src/components/PushCerts/PushCerts.react.js
index fcff72e3fb..685a7ac416 100644
--- a/src/components/PushCerts/PushCerts.react.js
+++ b/src/components/PushCerts/PushCerts.react.js
@@ -6,11 +6,11 @@
* the root directory of this source tree.
*/
import CertsTable from 'components/PushCerts/CertsTable.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FileInput from 'components/FileInput/FileInput.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import Label from 'components/Label/Label.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FileInput from 'components/FileInput/FileInput.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import Label from 'components/Label/Label.react';
import PropTypes from 'lib/PropTypes';
import React from 'react';
@@ -21,21 +21,46 @@ class PushCerts extends React.Component {
}
render() {
- let renderTable = this.props.uploadPending || (this.props.certs && this.props.certs.length > 0);
+ const renderTable =
+ this.props.uploadPending || (this.props.certs && this.props.certs.length > 0);
return (
+ legend="Apple Push Certificates"
+ description={'You may upload up to 6 push certificates. This limit cannot be raised.'}
+ >
}
- input={ } />
- {renderTable || this.props.error ? {this.props.error} : null}
- {renderTable ?
+ label={
+
+ }
+ input={
+
+ }
+ />
+ {renderTable || this.props.error ? (
+
+ {this.props.error}
+
+ ) : null}
+ {renderTable ? (
}
- input={ } /> : null}
+ label={
+
+ }
+ input={ }
+ />
+ ) : null}
);
}
@@ -45,12 +70,8 @@ PushCerts.propTypes = {
certs: PropTypes.arrayOf(PropTypes.object).describe(
'An array of push cert metadata. Each should include the following fields: id, bundle, type, expiresAt'
),
- uploadPending: PropTypes.bool.describe(
- 'A boolean that indicates whether an upload is pending'
- ),
- error: PropTypes.string.describe(
- 'An error message to display in the form'
- ),
+ uploadPending: PropTypes.bool.describe('A boolean that indicates whether an upload is pending'),
+ error: PropTypes.string.describe('An error message to display in the form'),
onUpload: PropTypes.func.isRequired.describe(
'A function called when a cert file is selected. The File is passed to the handler as the only parameter.'
),
diff --git a/src/components/PushExperimentDropdown/PushExperimentDropdown.example.js b/src/components/PushExperimentDropdown/PushExperimentDropdown.example.js
index f36be936e6..a0aaf5e03d 100644
--- a/src/components/PushExperimentDropdown/PushExperimentDropdown.example.js
+++ b/src/components/PushExperimentDropdown/PushExperimentDropdown.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import PushExperimentDropdown from 'components/PushExperimentDropdown/PushExperimentDropdown.react';
export const component = PushExperimentDropdown;
@@ -20,11 +20,15 @@ class PushExperimentDropdownDemo extends React.Component {
return (
this.setState({ color })}
- options={[{key: 'Group A (winner)', style: {color: 'green'} }, {key: 'Group B (loser)', style: {color: 'red'}}]} />
+ onChange={color => this.setState({ color })}
+ options={[
+ { key: 'Group A (winner)', style: { color: 'green' } },
+ { key: 'Group B (loser)', style: { color: 'red' } },
+ ]}
+ />
);
}
}
@@ -35,6 +39,6 @@ export const demos = [
- )
+ ),
},
];
diff --git a/src/components/PushExperimentDropdown/PushExperimentDropdown.react.js b/src/components/PushExperimentDropdown/PushExperimentDropdown.react.js
index ec4b14e9d9..7135e10977 100644
--- a/src/components/PushExperimentDropdown/PushExperimentDropdown.react.js
+++ b/src/components/PushExperimentDropdown/PushExperimentDropdown.react.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Popover from 'components/Popover/Popover.react';
+import Popover from 'components/Popover/Popover.react';
import PropTypes from 'lib/PropTypes';
-import Position from 'lib/Position';
-import React from 'react';
-import styles from 'components/PushExperimentDropdown/PushExperimentDropdown.scss';
+import Position from 'lib/Position';
+import React from 'react';
+import styles from 'components/PushExperimentDropdown/PushExperimentDropdown.scss';
export default class PushExperimentDropdown extends React.Component {
constructor() {
super();
- this.state = {
+ this.state = {
open: false,
selected: false,
};
@@ -29,29 +29,47 @@ export default class PushExperimentDropdown extends React.Component {
select(value, e) {
e.stopPropagation();
- this.setState({
- open: false,
- selected: true,
- }, () => {
- this.props.onChange(value);
- });
+ this.setState(
+ {
+ open: false,
+ selected: true,
+ },
+ () => {
+ this.props.onChange(value);
+ }
+ );
}
render() {
- let widthStyle = { width: this.props.width || 140 };
- let styles = this.styles;
- let color = this.props.color;
+ const widthStyle = { width: this.props.width || 140 };
+ const styles = this.styles;
+ const color = this.props.color;
let content = (
- this.setState({ open: true })}>
-
{!this.state.selected && this.props.placeholder && this.props.value === undefined ? this.props.placeholder : this.props.value}
+
this.setState({ open: true })}
+ >
+
+ {!this.state.selected && this.props.placeholder && this.props.value === undefined
+ ? this.props.placeholder
+ : this.props.value}
+
);
if (this.state.open) {
- let position = Position.inWindow(this.dropdownRef.current);
+ const position = Position.inWindow(this.dropdownRef.current);
content = (
-
this.setState({ open: false })}>
+ this.setState({ open: false })}
+ >
- {this.props.options.map(({key, style}) =>
{key}
)}
+ {this.props.options.map(({ key, style }) => (
+
+ {key}
+
+ ))}
);
@@ -65,25 +83,13 @@ export default class PushExperimentDropdown extends React.Component {
}
PushExperimentDropdown.propTypes = {
- color: PropTypes.string.describe(
- 'Determines the color of the dropdown.'
- ),
- value: PropTypes.string.isRequired.describe(
- 'The current value of the dropdown.'
- ),
+ color: PropTypes.string.describe('Determines the color of the dropdown.'),
+ value: PropTypes.string.isRequired.describe('The current value of the dropdown.'),
options: PropTypes.arrayOf(PropTypes.object).isRequired.describe(
'An array of options available in the dropdown.'
),
- onChange: PropTypes.func.isRequired.describe(
- 'A function called when the dropdown is changed.'
- ),
- width: PropTypes.string.describe(
- 'An optional width override.'
- ),
- placeHolder: PropTypes.string.describe(
- 'Placeholder text used in place of default selection.'
- ),
- styles: PropTypes.object.describe(
- 'Styles override used to provide dropdown with differnt skin.'
- ),
+ onChange: PropTypes.func.isRequired.describe('A function called when the dropdown is changed.'),
+ width: PropTypes.string.describe('An optional width override.'),
+ placeHolder: PropTypes.string.describe('Placeholder text used in place of default selection.'),
+ styles: PropTypes.object.describe('Styles override used to provide dropdown with differnt skin.'),
};
diff --git a/src/components/PushOpenRate/PushOpenRate.example.js b/src/components/PushOpenRate/PushOpenRate.example.js
index 8e1c313094..70b5b624be 100644
--- a/src/components/PushOpenRate/PushOpenRate.example.js
+++ b/src/components/PushOpenRate/PushOpenRate.example.js
@@ -13,62 +13,64 @@ export const component = PushOpenRate;
export const demos = [
{
name: 'Normal Push <10%',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'Normal Push >10%',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'Normal Push Integer Percent',
- render: () => (
-
- )
+ render: () => ,
},
{
name: 'Push Experiment A Wins',
render: () => (
- )
+ ),
},
{
name: 'Push Experiment B Wins',
render: () => (
- )
+ ),
},
];
diff --git a/src/components/PushOpenRate/PushOpenRate.react.js b/src/components/PushOpenRate/PushOpenRate.react.js
index b591e33873..d3d83b07e0 100644
--- a/src/components/PushOpenRate/PushOpenRate.react.js
+++ b/src/components/PushOpenRate/PushOpenRate.react.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/PushOpenRate/PushOpenRate.scss';
+import React from 'react';
+import styles from 'components/PushOpenRate/PushOpenRate.scss';
function getRateString(rateNum) {
let rateStr;
@@ -24,23 +24,24 @@ function getRateString(rateNum) {
return rateStr;
}
-let PushOpenRate = ({
- numOpened,
- numSent,
- color,
- isExperiment = false,
- isWinner = false,
- customColor,
- }) => {
- let rateNum = numOpened / numSent * 100;
- if(isNaN(rateNum)){ //check for case when numSent is 0
+const PushOpenRate = ({
+ numOpened,
+ numSent,
+ color,
+ isExperiment = false,
+ isWinner = false,
+ customColor,
+}) => {
+ let rateNum = (numOpened / numSent) * 100;
+ if (isNaN(rateNum)) {
+ //check for case when numSent is 0
rateNum = 0;
}
/* eslint-disable no-unused-vars */
- let rateStr = getRateString(rateNum);
+ const rateStr = getRateString(rateNum);
/* eslint-enable */
- let customStyles = {
+ const customStyles = {
standardColor: {},
inverseColor: {},
};
@@ -60,16 +61,24 @@ let PushOpenRate = ({
return (
- {isExperiment ?
-
{isWinner ? 'WINNER' : ''}
- : null}
-
- { /*
{rateStr}%
*/ }
+ {isExperiment ? (
+
+ {isWinner ? 'WINNER' : ''}
+
+ ) : null}
+
+ {/*
{rateStr}%
*/}
N/A
Open Rate
- { /*
{numOpened}
*/ }
+ {/*
{numOpened}
*/}
N/A
Push Opens
@@ -84,19 +93,11 @@ let PushOpenRate = ({
export default PushOpenRate;
PushOpenRate.propTypes = {
- numOpened: PropTypes.number.isRequired.describe(
- 'Number of pushes opened.'
- ),
- numSent: PropTypes.number.isRequired.describe(
- 'Number of pushes sent'
- ),
+ numOpened: PropTypes.number.isRequired.describe('Number of pushes opened.'),
+ numSent: PropTypes.number.isRequired.describe('Number of pushes sent'),
color: PropTypes.oneOf(['blue', 'yellow', 'pink']).isRequired.describe(
'Color of text and circle'
),
- isExperiment: PropTypes.bool.describe(
- 'Whether or not this is an A/B test group'
- ),
- isWinner: PropTypes.bool.describe(
- 'Whether or not this group won the A/B test'
- ),
+ isExperiment: PropTypes.bool.describe('Whether or not this is an A/B test group'),
+ isWinner: PropTypes.bool.describe('Whether or not this group won the A/B test'),
};
diff --git a/src/components/PushPreview/PushPreview.react.js b/src/components/PushPreview/PushPreview.react.js
index 8c1c7b90bb..fd66250834 100644
--- a/src/components/PushPreview/PushPreview.react.js
+++ b/src/components/PushPreview/PushPreview.react.js
@@ -5,35 +5,34 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import SegmentSelect from 'components/SegmentSelect/SegmentSelect.react';
-import styles from 'components/PushPreview/PushPreview.scss';
-import VisiblePreview from 'components/PushPreview/VisiblePreview.react';
-import { CurrentApp } from 'context/currentApp';
-import {
- getDateMethod,
- MONTHS,
- pad,
- } from 'lib/DateUtils';
+import React from 'react';
+import SegmentSelect from 'components/SegmentSelect/SegmentSelect.react';
+import styles from 'components/PushPreview/PushPreview.scss';
+import VisiblePreview from 'components/PushPreview/VisiblePreview.react';
+import { CurrentApp } from 'context/currentApp';
+import { getDateMethod, MONTHS, pad } from 'lib/DateUtils';
-let Row = ({ label, content }) => (
+const Row = ({ label, content }) => (
);
-let timeString = (time, isLocal) => {
+const timeString = (time, isLocal) => {
if (time && time.constructor === Date) {
return (
- {MONTHS[time[getDateMethod(isLocal, 'getMonth')]()].substr(0, 3) + ' ' + time[getDateMethod(isLocal, 'getDate')]()}
+ {MONTHS[time[getDateMethod(isLocal, 'getMonth')]()].substr(0, 3) +
+ ' ' +
+ time[getDateMethod(isLocal, 'getDate')]()}
at
- {time[getDateMethod(isLocal, 'getHours')]()}:{pad(time[getDateMethod(isLocal, 'getMinutes')]())}
+ {time[getDateMethod(isLocal, 'getHours')]()}:
+ {pad(time[getDateMethod(isLocal, 'getMinutes')]())}
);
}
-}
+};
export default class PushPreview extends React.Component {
static contextType = CurrentApp;
@@ -47,18 +46,19 @@ export default class PushPreview extends React.Component {
}
render() {
- let pushState = this.props.pushState;
- let isExperiment = !!pushState.exp_enable;
+ const pushState = this.props.pushState;
+ const isExperiment = !!pushState.exp_enable;
let audienceName = 'Everyone';
let count = -1;
if (pushState.audience_id === 'new_segment') {
audienceName = 'New Segment';
- } else if (pushState.audience_id !== 'everyone' &&
- this.props.audiences &&
- this.props.audiences.data &&
- this.props.audiences.data.get('audiences')
+ } else if (
+ pushState.audience_id !== 'everyone' &&
+ this.props.audiences &&
+ this.props.audiences.data &&
+ this.props.audiences.data.get('audiences')
) {
- this.props.audiences.data.get('audiences').forEach((a) => {
+ this.props.audiences.data.get('audiences').forEach(a => {
if (a.objectId === pushState.audience_id) {
audienceName = a.name;
count = a.size;
@@ -69,20 +69,20 @@ export default class PushPreview extends React.Component {
let messagePreview = (
Message
-
|
- {pushState.increment_badge ?
|
: null}
+
|
+ {pushState.increment_badge ?
|
: null}
);
if (isExperiment && pushState.exp_type === 'message') {
messagePreview = (
A/B Test
-
|
-
|
-
|
-
|
- {pushState.increment_badge ?
|
: null}
-
|
+
|
+
|
+
|
+
|
+ {pushState.increment_badge ?
|
: null}
+
);
}
@@ -97,9 +97,16 @@ export default class PushPreview extends React.Component {
let timePreview = (
);
//TODO: clarify use of UTC or GMT as GMT is time zone and UTC is standard
@@ -107,13 +114,19 @@ export default class PushPreview extends React.Component {
timePreview = (
A/B Test
-
|
-
|
-
|
-
|
-
|
-
|
-
|
+
|
+
|
+
|
+
|
+
|
+
|
+
);
}
@@ -123,7 +136,10 @@ export default class PushPreview extends React.Component {
}
let previewTime = new Date();
if (isExperiment && pushState.exp_type === 'time') {
- previewTime = this.state.currentTest === 'Group A' ? pushState.push_time_1_iso : pushState.push_time_2_iso;
+ previewTime =
+ this.state.currentTest === 'Group A'
+ ? pushState.push_time_1_iso
+ : pushState.push_time_2_iso;
} else if (pushState.push_time_type !== 'now') {
previewTime = pushState.push_time_iso;
}
@@ -135,7 +151,8 @@ export default class PushPreview extends React.Component {
message={previewMessage}
time={previewTime || new Date()}
appName={this.context.name}
- fade={isExperiment} />
+ fade={isExperiment}
+ />
);
if (!isExperiment && pushState.data_type === 'json') {
previewContent = null;
@@ -151,27 +168,34 @@ export default class PushPreview extends React.Component {
Audience
-
-1 ? ` (${count} devices)` : '')} />
+ -1 ? ` (${count} devices)` : '')}
+ />
{messagePreview}
{timePreview}
{previewContent ||
No Preview
}
- {previewContent ?
+ {previewContent ? (
this.setState({ currentPreview })} />
-
: null}
- {isExperiment ?
+ onChange={currentPreview => this.setState({ currentPreview })}
+ />
+
+ ) : null}
+ {isExperiment ? (
this.setState({ currentTest })} />
-
: null}
+ onChange={currentTest => this.setState({ currentTest })}
+ />
+
+ ) : null}
);
diff --git a/src/components/PushPreview/VisiblePreview.react.js b/src/components/PushPreview/VisiblePreview.react.js
index 43dec06b9a..4e501fff33 100644
--- a/src/components/PushPreview/VisiblePreview.react.js
+++ b/src/components/PushPreview/VisiblePreview.react.js
@@ -6,22 +6,33 @@
* the root directory of this source tree.
*/
import * as DateUtils from 'lib/DateUtils';
-import React from 'react';
-import styles from 'components/PushPreview/PushPreview.scss';
+import React from 'react';
+import styles from 'components/PushPreview/PushPreview.scss';
-let VisiblePreview = ({ type, message, time, appName, fade, isLocal }) => {
+const VisiblePreview = ({ type, message, time, appName, fade, isLocal }) => {
let timeString = time[DateUtils.getDateMethod(isLocal, 'getHours')]() + ':';
if (time.getUTCMinutes() < 10) {
timeString += '0';
}
timeString += time[DateUtils.getDateMethod(isLocal, 'getMinutes')]();
- let dateString = DateUtils.WEEKDAYS[time[DateUtils.getDateMethod(isLocal, 'getDay')]()] + ', ' + DateUtils.MONTHS[time[DateUtils.getDateMethod(isLocal, 'getMonth')]()] + ' ' + time[DateUtils.getDateMethod(isLocal, 'getDate')]();
+ const dateString =
+ DateUtils.WEEKDAYS[time[DateUtils.getDateMethod(isLocal, 'getDay')]()] +
+ ', ' +
+ DateUtils.MONTHS[time[DateUtils.getDateMethod(isLocal, 'getMonth')]()] +
+ ' ' +
+ time[DateUtils.getDateMethod(isLocal, 'getDate')]();
let notificationTime = null;
if (type === 'android') {
notificationTime = {timeString}
;
}
if (type === 'ios') {
- notificationTime = {DateUtils.WEEKDAYS[time[DateUtils.getDateMethod(isLocal, 'getDay')]()].substr(0, 3) + ' ' + timeString}
;
+ notificationTime = (
+
+ {DateUtils.WEEKDAYS[time[DateUtils.getDateMethod(isLocal, 'getDay')]()].substr(0, 3) +
+ ' ' +
+ timeString}
+
+ );
}
return (
diff --git a/src/components/RadioButton/RadioButton.example.js b/src/components/RadioButton/RadioButton.example.js
index 635dc348f9..5a0a863a67 100644
--- a/src/components/RadioButton/RadioButton.example.js
+++ b/src/components/RadioButton/RadioButton.example.js
@@ -14,33 +14,25 @@ export const demos = [
{
name: 'Unchecked State',
render() {
- return (
-
- );
+ return
;
},
},
{
name: 'Checked State',
render() {
- return (
-
- );
- }
+ return ;
+ },
},
{
name: 'Disabled State [styles tbd]',
render() {
- return (
-
- );
- }
+ return ;
+ },
},
{
name: 'Disabled Checked State [styles tbd]',
render() {
- return (
-
- );
- }
- }
+ return ;
+ },
+ },
];
diff --git a/src/components/RadioButton/RadioButton.react.js b/src/components/RadioButton/RadioButton.react.js
index 8ce909cfcb..ac4603c355 100644
--- a/src/components/RadioButton/RadioButton.react.js
+++ b/src/components/RadioButton/RadioButton.react.js
@@ -5,19 +5,19 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import styles from 'components/RadioButton/RadioButton.scss';
+import React from 'react';
+import styles from 'components/RadioButton/RadioButton.scss';
-let RadioButton = (props) => {
+const RadioButton = props => {
const parentClassName = props.parentClassName || '';
props = Object.assign({}, props);
delete props.parentClassName;
return (
-
+
);
-}
+};
export default RadioButton;
diff --git a/src/components/Range/Range.example.js b/src/components/Range/Range.example.js
index d3508b265b..3281ae747b 100644
--- a/src/components/Range/Range.example.js
+++ b/src/components/Range/Range.example.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Range from 'components/Range/Range.react';
-import React from 'react';
+import Range from 'components/Range/Range.react';
+import React from 'react';
class RangeWrapper extends React.Component {
constructor() {
@@ -15,7 +15,13 @@ class RangeWrapper extends React.Component {
}
render() {
- return this.setState({ value })} />;
+ return (
+ this.setState({ value })}
+ />
+ );
}
}
@@ -24,35 +30,37 @@ export const component = Range;
export const demos = [
{
name: 'Basic Range',
- render: () => (
-
- )
- }, {
+ render: () => ,
+ },
+ {
name: 'Range with tracker',
- render: () => (
-
- ),
- }, {
+ render: () => ,
+ },
+ {
name: 'Step by 10',
- render: () => (
-
- ),
- }, {
+ render: () => ,
+ },
+ {
name: 'Different colors',
- render: () => (
-
- ),
- }, {
+ render: () => ,
+ },
+ {
name: 'Tracker units',
- render: () => (
-
- ),
- }, {
+ render: () => ,
+ },
+ {
name: 'Complex tracker units',
render: () => (
- {
- return value + 'req/s & ' + Math.floor((value-10)/20) + ' jobs';
- }} min={0} max={100} width={200} step={10} />
+ {
+ return value + 'req/s & ' + Math.floor((value - 10) / 20) + ' jobs';
+ }}
+ min={0}
+ max={100}
+ width={200}
+ step={10}
+ />
),
},
];
diff --git a/src/components/Range/Range.react.js b/src/components/Range/Range.react.js
index 65b8a9ae26..22464fef4b 100644
--- a/src/components/Range/Range.react.js
+++ b/src/components/Range/Range.react.js
@@ -6,9 +6,9 @@
* the root directory of this source tree.
*/
import fieldStyles from 'components/Field/Field.scss';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/Range/Range.scss';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/Range/Range.scss';
const DEFAULT_COLOR = '#fd9539';
@@ -30,15 +30,16 @@ export default class Range extends React.Component {
}
buildGradient() {
- let fillLevel = this.props.value / (this.props.max - this.props.min) * 100 + '%';
- let fillColor = this.props.color || DEFAULT_COLOR;
+ const fillLevel = (this.props.value / (this.props.max - this.props.min)) * 100 + '%';
+ const fillColor = this.props.color || DEFAULT_COLOR;
return `linear-gradient(90deg, ${fillColor}, ${fillColor} ${fillLevel}, #e0e0ea ${fillLevel}, #e0e0ea)`;
}
render() {
let trackerStyle = {};
if (this.state.width) {
- let left = this.props.value / (this.props.max - this.props.min) * (this.state.width - 24) + 11;
+ const left =
+ (this.props.value / (this.props.max - this.props.min)) * (this.state.width - 24) + 11;
trackerStyle = { left };
}
if (this.props.color) {
@@ -46,19 +47,18 @@ export default class Range extends React.Component {
trackerStyle.borderTopColor = this.props.color;
}
let tracker = null;
- let unitsText = (typeof this.props.units === 'function') ?
- this.props.units(this.props.value) :
- this.props.value + (this.props.units || '');
+ const unitsText =
+ typeof this.props.units === 'function'
+ ? this.props.units(this.props.value)
+ : this.props.value + (this.props.units || '');
if (this.props.track && this.state.width) {
tracker = (
-
+
{unitsText}
);
}
- let wrapperStyle = {};
+ const wrapperStyle = {};
if (this.props.width) {
wrapperStyle.width = this.props.width;
}
@@ -66,40 +66,32 @@ export default class Range extends React.Component {
+ className={[styles.range, fieldStyles.input].join(' ')}
+ >
{tracker}
+ onChange={this.changeValue.bind(this)}
+ />
);
}
}
Range.propTypes = {
- min: PropTypes.number.isRequired.describe(
- 'The minimum value on the slider'
- ),
- max: PropTypes.number.isRequired.describe(
- 'The maximum value on the slider'
- ),
+ min: PropTypes.number.isRequired.describe('The minimum value on the slider'),
+ max: PropTypes.number.isRequired.describe('The maximum value on the slider'),
step: PropTypes.number.describe(
'Adjusts the granularity of the slider. If this is set, the slider will only move in increments of this size.'
),
- value: PropTypes.number.isRequired.describe(
- 'The current value of this controlled input'
- ),
- onChange: PropTypes.func.isRequired.describe(
- 'A function called whenever the input is adjusted'
- ),
- width: PropTypes.number.describe(
- 'The physical width, in pixels, of the slider track'
- ),
+ value: PropTypes.number.isRequired.describe('The current value of this controlled input'),
+ onChange: PropTypes.func.isRequired.describe('A function called whenever the input is adjusted'),
+ width: PropTypes.number.describe('The physical width, in pixels, of the slider track'),
units: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).describe(
'A string displayed as after the number in the tracker, or a function that accepts the value of the tracker, and returns the string to be displayed in the tracker.'
),
diff --git a/src/components/ReleaseInfo/ReleaseInfo.js b/src/components/ReleaseInfo/ReleaseInfo.js
index 235be9733e..3fe74edebe 100644
--- a/src/components/ReleaseInfo/ReleaseInfo.js
+++ b/src/components/ReleaseInfo/ReleaseInfo.js
@@ -7,7 +7,7 @@
*/
import howLongAgo from 'lib/howLongAgo';
-let ReleaseInfo = ({ release }) => {
+const ReleaseInfo = ({ release }) => {
if (!release) {
return '';
}
@@ -18,7 +18,7 @@ let ReleaseInfo = ({ release }) => {
' \u2022 ',
howLongAgo(release.deployedAt),
' \u2022 SDK Version: ',
- release.parseVersion
+ release.parseVersion,
].join('');
};
diff --git a/src/components/SaveButton/SaveButton.example.js b/src/components/SaveButton/SaveButton.example.js
index f7e55af774..84289f885d 100644
--- a/src/components/SaveButton/SaveButton.example.js
+++ b/src/components/SaveButton/SaveButton.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import SaveButton from 'components/SaveButton/SaveButton.react';
export const component = SaveButton;
@@ -23,9 +23,10 @@ class SaveDemo extends React.Component {
handleClick() {
this.setState({ saveState: SaveButton.States.SAVING });
setTimeout(() => {
- let next = this.state.nextState === SaveButton.States.SUCCEEDED ?
- SaveButton.States.FAILED :
- SaveButton.States.SUCCEEDED;
+ const next =
+ this.state.nextState === SaveButton.States.SUCCEEDED
+ ? SaveButton.States.FAILED
+ : SaveButton.States.SUCCEEDED;
this.setState({ saveState: this.state.nextState, nextState: next });
setTimeout(() => {
@@ -43,22 +44,23 @@ export const demos = [
{
render: () => (
-
+
-
+
-
+
-
- )
- }, {
+ ),
+ },
+ {
name: 'Clickable demo',
- render: () =>
- }
+ render: () =>
,
+ },
];
diff --git a/src/components/SaveButton/SaveButton.react.js b/src/components/SaveButton/SaveButton.react.js
index e8172fa5d9..f590066b80 100644
--- a/src/components/SaveButton/SaveButton.react.js
+++ b/src/components/SaveButton/SaveButton.react.js
@@ -11,7 +11,7 @@ import PropTypes from 'lib/PropTypes';
import React from 'react';
import styles from 'components/SaveButton/SaveButton.scss';
-let SaveButton = ({
+const SaveButton = ({
state = SaveButton.States.WAITING,
onClick,
waitingText = 'Save changes',
@@ -39,26 +39,34 @@ let SaveButton = ({
color = 'red';
break;
}
- let className = state === SaveButton.States.FAILED ? styles.shake : null;
- return
- ;
+ const className = state === SaveButton.States.FAILED ? styles.shake : null;
+ return (
+
+
+
+ );
};
SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED']);
-let {...forwardedButtonProps} = Button.propTypes;
+const { ...forwardedButtonProps } = Button.propTypes;
delete forwardedButtonProps.value;
SaveButton.propTypes = {
- state: PropTypes.string.describe('SaveButton.States.(SAVING|SUCCEEDED|FAILED|WAITING). Defaults to WAITING.'),
- onClick: PropTypes.func.describe('Click handler. Actived if button is clicked while enabled and in WAITING state.'),
+ state: PropTypes.string.describe(
+ 'SaveButton.States.(SAVING|SUCCEEDED|FAILED|WAITING). Defaults to WAITING.'
+ ),
+ onClick: PropTypes.func.describe(
+ 'Click handler. Actived if button is clicked while enabled and in WAITING state.'
+ ),
waitingText: PropTypes.string.describe('Text for WAITING state. Defaults to "Save changes".'),
savingText: PropTypes.string.describe('Text for SAVING state. Defaults to "Saving\u2025".'),
failedText: PropTypes.string.describe('Text for FAILED state. Defaults to "Save failed".'),
diff --git a/src/components/ScrollHint/ScrollHint.react.js b/src/components/ScrollHint/ScrollHint.react.js
index 8933679914..466e211697 100644
--- a/src/components/ScrollHint/ScrollHint.react.js
+++ b/src/components/ScrollHint/ScrollHint.react.js
@@ -5,27 +5,24 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
- import React from 'react'
- import styles from 'components/ScrollHint/ScrollHint.scss'
+import React from 'react';
+import styles from 'components/ScrollHint/ScrollHint.scss';
- export default class ScrollHint extends React.Component {
- constructor() {
- super();
- this.state = { active: false };
- }
+export default class ScrollHint extends React.Component {
+ constructor() {
+ super();
+ this.state = { active: false };
+ }
- toggle(active) {
- this.setState({ active });
- }
+ toggle(active) {
+ this.setState({ active });
+ }
- render() {
- const { active } = this.state;
+ render() {
+ const { active } = this.state;
- const classes = [
- styles.scrollHint,
- active ? styles.active: undefined
- ].join(' ');
+ const classes = [styles.scrollHint, active ? styles.active : undefined].join(' ');
- return
;
- }
- }
+ return
;
+ }
+}
diff --git a/src/components/SegmentSelect/SegmentSelect.example.js b/src/components/SegmentSelect/SegmentSelect.example.js
index afe6db5cff..a8f08e2891 100644
--- a/src/components/SegmentSelect/SegmentSelect.example.js
+++ b/src/components/SegmentSelect/SegmentSelect.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import SegmentSelect from 'components/SegmentSelect/SegmentSelect.react';
export const component = SegmentSelect;
@@ -14,7 +14,7 @@ class Demo extends React.Component {
constructor() {
super();
this.state = {
- current: null
+ current: null,
};
}
@@ -23,15 +23,14 @@ class Demo extends React.Component {
this.setState({ current })} />
+ onChange={current => this.setState({ current })}
+ />
);
}
}
export const demos = [
{
- render: () => (
-
- )
- }
+ render: () => ,
+ },
];
diff --git a/src/components/SegmentSelect/SegmentSelect.react.js b/src/components/SegmentSelect/SegmentSelect.react.js
index f638539100..708708b596 100644
--- a/src/components/SegmentSelect/SegmentSelect.react.js
+++ b/src/components/SegmentSelect/SegmentSelect.react.js
@@ -6,20 +6,21 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/SegmentSelect/SegmentSelect.scss';
+import React from 'react';
+import styles from 'components/SegmentSelect/SegmentSelect.scss';
-let SegmentSelect = ({ values, current, onChange }) => (
+const SegmentSelect = ({ values, current, onChange }) => (
- {values.map((v) => (
+ {values.map(v => (
{
if (current !== v) {
- onChange(v)
+ onChange(v);
}
- }}>
+ }}
+ >
{v}
))}
@@ -32,10 +33,8 @@ SegmentSelect.propTypes = {
values: PropTypes.arrayOf(PropTypes.string).isRequired.describe(
'An array of strings that can be selected'
),
- current: PropTypes.string.describe(
- 'The currently selected value.'
- ),
+ current: PropTypes.string.describe('The currently selected value.'),
onChange: PropTypes.func.isRequired.describe(
'A function that is called whenever a new segment is selected. The selected value is passed as the only parameter.'
- )
+ ),
};
diff --git a/src/components/Sidebar/AppName.react.js b/src/components/Sidebar/AppName.react.js
index 93053923a6..a5f0eca30c 100644
--- a/src/components/Sidebar/AppName.react.js
+++ b/src/components/Sidebar/AppName.react.js
@@ -6,9 +6,7 @@ const AppName = ({ name, onClick, onPinClick }) => (
diff --git a/src/components/Sidebar/AppsMenu.react.js b/src/components/Sidebar/AppsMenu.react.js
index cbee4edd80..9ab134565c 100644
--- a/src/components/Sidebar/AppsMenu.react.js
+++ b/src/components/Sidebar/AppsMenu.react.js
@@ -5,25 +5,34 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AppBadge from 'components/AppBadge/AppBadge.react';
-import AppName from 'components/Sidebar/AppName.react';
-import html from 'lib/htmlString';
-import { Link } from 'react-router-dom';
-import React from 'react';
-import styles from 'components/Sidebar/Sidebar.scss';
-import baseStyles from 'stylesheets/base.scss';
+import AppBadge from 'components/AppBadge/AppBadge.react';
+import AppName from 'components/Sidebar/AppName.react';
+import html from 'lib/htmlString';
+import { Link } from 'react-router-dom';
+import React from 'react';
+import styles from 'components/Sidebar/Sidebar.scss';
+import baseStyles from 'stylesheets/base.scss';
const AppsMenu = ({ apps, current, height, onSelect, onPinClick }) => (
-
+
All Apps
- {apps.map((app) => {
+ {apps.map(app => {
if (app.slug === current.slug) {
return null;
}
return (
-
+
{app.name}
diff --git a/src/components/Sidebar/Pin.react.js b/src/components/Sidebar/Pin.react.js
index 95f3b2f1fd..21c4eb2d7f 100644
--- a/src/components/Sidebar/Pin.react.js
+++ b/src/components/Sidebar/Pin.react.js
@@ -5,7 +5,7 @@ import styles from 'components/Sidebar/Sidebar.scss';
const Pin = ({ onClick }) => (
-
+
);
diff --git a/src/components/Sidebar/Sidebar.react.js b/src/components/Sidebar/Sidebar.react.js
index 80764af925..79bbf1fcce 100644
--- a/src/components/Sidebar/Sidebar.react.js
+++ b/src/components/Sidebar/Sidebar.react.js
@@ -5,19 +5,19 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AppsManager from 'lib/AppsManager';
-import AppsMenu from 'components/Sidebar/AppsMenu.react';
-import AppName from 'components/Sidebar/AppName.react';
+import AppsManager from 'lib/AppsManager';
+import AppsMenu from 'components/Sidebar/AppsMenu.react';
+import AppName from 'components/Sidebar/AppName.react';
import isInsidePopover from 'lib/isInsidePopover';
-import Pin from 'components/Sidebar/Pin.react';
+import Pin from 'components/Sidebar/Pin.react';
import React, { useEffect, useState, useContext } from 'react';
-import SidebarHeader from 'components/Sidebar/SidebarHeader.react';
+import SidebarHeader from 'components/Sidebar/SidebarHeader.react';
import SidebarSection from 'components/Sidebar/SidebarSection.react';
import SidebarSubItem from 'components/Sidebar/SidebarSubItem.react';
-import styles from 'components/Sidebar/Sidebar.scss';
+import styles from 'components/Sidebar/Sidebar.scss';
import { CurrentApp } from 'context/currentApp';
-import Icon from 'components/Icon/Icon.react';
-let mountPath = window.PARSE_DASHBOARD_PATH;
+import Icon from 'components/Icon/Icon.react';
+const mountPath = window.PARSE_DASHBOARD_PATH;
const Sidebar = ({
prefix,
@@ -29,17 +29,17 @@ const Sidebar = ({
section,
appSelector,
primaryBackgroundColor,
- secondaryBackgroundColor
+ secondaryBackgroundColor,
}) => {
const currentApp = useContext(CurrentApp);
const collapseWidth = 980;
- const [ appsMenuOpen, setAppsMenuOpen ] = useState(false);
- const [ collapsed, setCollapsed ] = useState(false);
- const [ fixed, setFixed ] = useState(true);
+ const [appsMenuOpen, setAppsMenuOpen] = useState(false);
+ const [collapsed, setCollapsed] = useState(false);
+ const [fixed, setFixed] = useState(true);
const [dashboardUser, setDashboardUser] = useState('');
fetch(mountPath).then(response => {
- setDashboardUser(response.headers.get('username'))
- })
+ setDashboardUser(response.headers.get('username'));
+ });
let currentWidth = window.innerWidth;
const windowResizeHandler = () => {
@@ -56,14 +56,14 @@ const Sidebar = ({
}
// Update window width
currentWidth = window.innerWidth;
- }
+ };
useEffect(() => {
window.addEventListener('resize', windowResizeHandler);
return () => {
window.removeEventListener('resize', windowResizeHandler);
- }
+ };
});
const sidebarClasses = [styles.sidebar];
@@ -82,7 +82,7 @@ const Sidebar = ({
}
return (
- {subsections.map(({name, link}) => {
+ {subsections.map(({ name, link }) => {
const active = subsection === name;
return (
+ active={active}
+ >
{active ? children : null}
);
})}
);
- }
+ };
const onPinClick = () => {
if (fixed) {
@@ -113,34 +114,38 @@ const Sidebar = ({
let sidebarContent;
if (appsMenuOpen) {
- const apps = [].concat(AppsManager.apps()).sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
+ const apps = []
+ .concat(AppsManager.apps())
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
sidebarContent = (
setAppsMenuOpen(false)} />
+ onSelect={() => setAppsMenuOpen(false)}
+ />
);
} else {
- const topContent = collapsed
- ?
- : appSelector && (
+ const topContent = collapsed ? (
+
+ ) : (
+ (appSelector && (
-
setAppsMenuOpen(true)} onPinClick={onPinClick} />
+ setAppsMenuOpen(true)}
+ onPinClick={onPinClick}
+ />
- ) || undefined;
+ )) ||
+ undefined
+ );
sidebarContent = (
<>
{topContent}
- {sections.map(({
- name,
- icon,
- style,
- link,
- subsections,
- }) => {
+ {sections.map(({ name, icon, style, link, subsections }) => {
const active = name === section;
return (
+ >
{!collapsed && active ? _subMenu(subsections) : null}
);
})}
>
- )
+ );
}
return (
setCollapsed(false)
- : undefined
- }
+ onMouseEnter={!fixed && collapsed ? () => setCollapsed(false) : undefined}
onMouseLeave={
!collapsed && !fixed
- ? (e => {
+ ? e => {
if (!isInsidePopover(e.relatedTarget)) {
setAppsMenuOpen(false);
setCollapsed(true);
}
- })
+ }
: undefined
}
>
{sidebarContent}
- {dashboardUser &&
}
+ {dashboardUser && (
+
+ )}
);
-}
+};
export default Sidebar;
diff --git a/src/components/Sidebar/SidebarAction.js b/src/components/Sidebar/SidebarAction.js
index 0fb1cb5c68..d17a9d6455 100644
--- a/src/components/Sidebar/SidebarAction.js
+++ b/src/components/Sidebar/SidebarAction.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/Sidebar/Sidebar.scss';
export default class SidebarAction {
@@ -16,9 +16,7 @@ export default class SidebarAction {
renderButton() {
return (
-
+
{this.text}
);
diff --git a/src/components/Sidebar/SidebarHeader.react.js b/src/components/Sidebar/SidebarHeader.react.js
index 03c3b82b2d..c609ff1cff 100644
--- a/src/components/Sidebar/SidebarHeader.react.js
+++ b/src/components/Sidebar/SidebarHeader.react.js
@@ -5,47 +5,39 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
+import Icon from 'components/Icon/Icon.react';
import { Link } from 'react-router-dom';
-import React from 'react';
-import styles from 'components/Sidebar/Sidebar.scss';
+import React from 'react';
+import styles from 'components/Sidebar/Sidebar.scss';
// get the package.json environment variable
const version = process.env.version;
export default class SidebarHeader extends React.Component {
constructor() {
super();
- this.state = { };
+ this.state = {};
}
render() {
const { isCollapsed = false, dashboardUser } = this.props;
- const headerContent = isCollapsed
- ? (
-
-
-
- )
- : (
- <>
-
-
-
-
-
-
- Parse Dashboard {version}
-
- {dashboardUser}
-
-
-
-
- >
- )
- return (
-
- {headerContent}
+ const headerContent = isCollapsed ? (
+
+
+ ) : (
+ <>
+
+
+
+
+
+
+ Parse Dashboard {version}
+
{dashboardUser}
+
+
+
+ >
);
+ return
{headerContent}
;
}
}
diff --git a/src/components/Sidebar/SidebarSection.react.js b/src/components/Sidebar/SidebarSection.react.js
index fb86c00c60..09a9d6317f 100644
--- a/src/components/Sidebar/SidebarSection.react.js
+++ b/src/components/Sidebar/SidebarSection.react.js
@@ -5,25 +5,39 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Icon from 'components/Icon/Icon.react';
+import Icon from 'components/Icon/Icon.react';
import { Link } from 'react-router-dom';
-import React from 'react';
-import styles from 'components/Sidebar/Sidebar.scss';
+import React from 'react';
+import styles from 'components/Sidebar/Sidebar.scss';
-let SidebarSection = ({ active, children, name, link, icon, style, primaryBackgroundColor, secondaryBackgroundColor, isCollapsed }) => {
- let classes = [styles.section];
+const SidebarSection = ({
+ active,
+ children,
+ name,
+ link,
+ icon,
+ style,
+ primaryBackgroundColor,
+ secondaryBackgroundColor,
+ isCollapsed,
+}) => {
+ const classes = [styles.section];
if (active) {
classes.push(styles.active);
}
let iconContent = null;
if (icon) {
- iconContent =
;
+ iconContent =
;
}
if (isCollapsed) {
classes.push(styles.collapsed);
return (
-
@@ -31,11 +45,27 @@ let SidebarSection = ({ active, children, name, link, icon, style, primaryBackgr
}
return (
- {active ?
-
{iconContent}{name}
:
-
{iconContent}
{name} }
+ {active ? (
+
+ {iconContent}
+ {name}
+
+ ) : (
+
+ {iconContent}
+
{name}
+
+ )}
- {children ?
{children}
: null}
+ {children ? (
+
+ {children}
+
+ ) : null}
);
};
diff --git a/src/components/Sidebar/SidebarSubItem.react.js b/src/components/Sidebar/SidebarSubItem.react.js
index 72962893a9..3017466b60 100644
--- a/src/components/Sidebar/SidebarSubItem.react.js
+++ b/src/components/Sidebar/SidebarSubItem.react.js
@@ -6,10 +6,10 @@
* the root directory of this source tree.
*/
import { Link } from 'react-router-dom';
-import React from 'react';
-import styles from 'components/Sidebar/Sidebar.scss';
+import React from 'react';
+import styles from 'components/Sidebar/Sidebar.scss';
-let SidebarSubItem = ({ active, name, action, link, children }) => {
+const SidebarSubItem = ({ active, name, action, link, children }) => {
if (active) {
return (
@@ -17,18 +17,14 @@ let SidebarSubItem = ({ active, name, action, link, children }) => {
{name}
{action ? action.renderButton() : null}
-
- {children}
-
+
{children}
);
}
return (
-
+
{name}
diff --git a/src/components/SliderWrap/SliderWrap.example.js b/src/components/SliderWrap/SliderWrap.example.js
index b03c0278ce..29f77673ce 100644
--- a/src/components/SliderWrap/SliderWrap.example.js
+++ b/src/components/SliderWrap/SliderWrap.example.js
@@ -6,17 +6,17 @@
* the root directory of this source tree.
*/
import { Directions } from 'lib/Constants';
-import React from 'react';
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import React from 'react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-let contentStyle = {
+const contentStyle = {
background: '#67c0ff',
color: '#ffffff',
width: '200px',
height: '60px',
lineHeight: '60px',
fontSize: '16px',
- textAlign: 'center'
+ textAlign: 'center',
};
class Toggler extends React.Component {
@@ -27,20 +27,18 @@ class Toggler extends React.Component {
toggle() {
this.setState({
- expanded: !this.state.expanded
+ expanded: !this.state.expanded,
});
}
render() {
- let { children, ...otherProps } = this.props;
+ const { children, ...otherProps } = this.props;
return (
);
@@ -55,10 +53,16 @@ export const demos = [
Open your mind
Closed for renovations
- To the right, to the right
- In the box to the left
- Zooooooom
+
+ To the right, to the right
+
+
+ In the box to the left
+
+
+ Zooooooom
+
- )
- }
+ ),
+ },
];
diff --git a/src/components/SliderWrap/SliderWrap.react.js b/src/components/SliderWrap/SliderWrap.react.js
index 8fd7886e67..959f33e32c 100644
--- a/src/components/SliderWrap/SliderWrap.react.js
+++ b/src/components/SliderWrap/SliderWrap.react.js
@@ -6,8 +6,8 @@
* the root directory of this source tree.
*/
import { Directions } from 'lib/Constants';
-import React from 'react';
-import styles from 'components/SliderWrap/SliderWrap.scss';
+import React from 'react';
+import styles from 'components/SliderWrap/SliderWrap.scss';
export default class SliderWrap extends React.Component {
constructor() {
@@ -23,22 +23,23 @@ export default class SliderWrap extends React.Component {
}
_getMetric() {
- if (this.props.direction === Directions.LEFT ||
- this.props.direction === Directions.RIGHT) {
+ if (this.props.direction === Directions.LEFT || this.props.direction === Directions.RIGHT) {
return 'width';
}
- if (!this.props.direction ||
- this.props.direction === Directions.UP ||
- this.props.direction === Directions.DOWN) {
+ if (
+ !this.props.direction ||
+ this.props.direction === Directions.UP ||
+ this.props.direction === Directions.DOWN
+ ) {
return 'height';
}
return 'both';
}
render() {
- let style = {};
- let metric = this._getMetric();
- let node = this.metricsRef.current;
+ const style = {};
+ const metric = this._getMetric();
+ const node = this.metricsRef.current;
if (!this.props.expanded || !node) {
if (metric === 'width' || metric === 'both') {
style.width = '0px';
@@ -59,7 +60,11 @@ export default class SliderWrap extends React.Component {
}
return (
-
diff --git a/src/components/SlowQueriesFilter/SlowQueriesFilter.react.js b/src/components/SlowQueriesFilter/SlowQueriesFilter.react.js
index d1c52a02d2..5155791890 100644
--- a/src/components/SlowQueriesFilter/SlowQueriesFilter.react.js
+++ b/src/components/SlowQueriesFilter/SlowQueriesFilter.react.js
@@ -6,20 +6,20 @@
* the root directory of this source tree.
*/
import ChromeDropdown from 'components/ChromeDropdown/ChromeDropdown.react';
-import Icon from 'components/Icon/Icon.react';
-import Popover from 'components/Popover/Popover.react';
-import Position from 'lib/Position';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/SlowQueriesFilter/SlowQueriesFilter.scss';
+import Icon from 'components/Icon/Icon.react';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import styles from 'components/SlowQueriesFilter/SlowQueriesFilter.scss';
export default class SlowQueriesFilter extends React.Component {
constructor() {
super();
this.state = {
- open: false
- }
+ open: false,
+ };
this.wrapRef = React.createRef();
}
@@ -31,12 +31,12 @@ export default class SlowQueriesFilter extends React.Component {
}
render() {
- let { className, os, version } = this.props;
+ const { className, os, version } = this.props;
let popover = null;
- let active = className || os || version;
+ const active = className || os || version;
if (this.state.open) {
- let position = Position.inDocument(this.wrapRef.current);
- let popoverStyle = [styles.popover];
+ const position = Position.inDocument(this.wrapRef.current);
+ const popoverStyle = [styles.popover];
if (active) {
popoverStyle.push(styles.active);
}
@@ -44,7 +44,7 @@ export default class SlowQueriesFilter extends React.Component {
this.setState({ open: false })}>
-
+
Filter
@@ -53,31 +53,34 @@ export default class SlowQueriesFilter extends React.Component {
color={active ? 'blue' : 'purple'}
value={className || 'Class'}
options={this.props.classNameOptions}
- onChange={className => this.props.onChange({ className })} />
+ onChange={className => this.props.onChange({ className })}
+ />
this.props.onChange({ os })} />
+ onChange={os => this.props.onChange({ os })}
+ />
this.props.onChange({ version })} />
+ onChange={version => this.props.onChange({ version })}
+ />
);
}
- let buttonStyle = [styles.entry];
+ const buttonStyle = [styles.entry];
if (active) {
buttonStyle.push(styles.active);
}
return (
this.setState({ open: true })}>
-
+
Filter
{popover}
@@ -87,25 +90,11 @@ export default class SlowQueriesFilter extends React.Component {
}
SlowQueriesFilter.propTypes = {
- className: PropTypes.string.describe(
- 'Filtered class name.'
- ),
- os: PropTypes.string.describe(
- 'Filtered OS.'
- ),
- version: PropTypes.string.describe(
- 'Filtered app version.'
- ),
- classNameOptions: PropTypes.arrayOf(PropTypes.string).describe(
- 'Options for class names.'
- ),
- osOptions: PropTypes.arrayOf(PropTypes.string).describe(
- 'Options for OSes.'
- ),
- versionOptions: PropTypes.arrayOf(PropTypes.string).describe(
- 'Options for app versions.'
- ),
- onChange: PropTypes.func.isRequired.describe(
- 'Function to be called when the filter is changed.'
- )
-}
+ className: PropTypes.string.describe('Filtered class name.'),
+ os: PropTypes.string.describe('Filtered OS.'),
+ version: PropTypes.string.describe('Filtered app version.'),
+ classNameOptions: PropTypes.arrayOf(PropTypes.string).describe('Options for class names.'),
+ osOptions: PropTypes.arrayOf(PropTypes.string).describe('Options for OSes.'),
+ versionOptions: PropTypes.arrayOf(PropTypes.string).describe('Options for app versions.'),
+ onChange: PropTypes.func.isRequired.describe('Function to be called when the filter is changed.'),
+};
diff --git a/src/components/StatusIndicator/StatusIndicator.example.js b/src/components/StatusIndicator/StatusIndicator.example.js
index 9ae22e015c..be3b9f4c86 100644
--- a/src/components/StatusIndicator/StatusIndicator.example.js
+++ b/src/components/StatusIndicator/StatusIndicator.example.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import StatusIndicator from 'components/StatusIndicator/StatusIndicator.react';
export const component = StatusIndicator;
@@ -13,26 +13,14 @@ export const component = StatusIndicator;
export const demos = [
{
name: 'Positive',
- render: () => (
-
- )
+ render: () =>
,
},
{
name: 'Negative',
- render: () => (
-
- )
+ render: () =>
,
},
{
name: 'In Progress',
- render: () => (
-
- )
- }
+ render: () =>
,
+ },
];
diff --git a/src/components/StatusIndicator/StatusIndicator.react.js b/src/components/StatusIndicator/StatusIndicator.react.js
index 6238e3f765..7232c80776 100644
--- a/src/components/StatusIndicator/StatusIndicator.react.js
+++ b/src/components/StatusIndicator/StatusIndicator.react.js
@@ -6,21 +6,17 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/StatusIndicator/StatusIndicator.scss';
+import React from 'react';
+import styles from 'components/StatusIndicator/StatusIndicator.scss';
-let StatusIndicator = ({ text, color }) => {
+const StatusIndicator = ({ text, color }) => {
color = color || 'blue';
- return (
-
- {text}
-
- );
+ return
{text} ;
};
StatusIndicator.propTypes = {
text: PropTypes.string.isRequired,
- color: PropTypes.oneOf(['blue', 'green', 'red'])
+ color: PropTypes.oneOf(['blue', 'green', 'red']),
};
export default StatusIndicator;
diff --git a/src/components/StringEditor/StringEditor.react.js b/src/components/StringEditor/StringEditor.react.js
index 6141d33b7c..d36fdb296b 100644
--- a/src/components/StringEditor/StringEditor.react.js
+++ b/src/components/StringEditor/StringEditor.react.js
@@ -13,7 +13,7 @@ export default class StringEditor extends React.Component {
super();
this.state = {
- value: props.value || ''
+ value: props.value || '',
};
this.checkExternalClick = this.checkExternalClick.bind(this);
@@ -49,14 +49,14 @@ export default class StringEditor extends React.Component {
}
render() {
- let classes = [styles.editor];
- let onChange = this.props.readonly ? () => {} : (e) => this.setState({ value: e.target.value });
+ const classes = [styles.editor];
+ const onChange = this.props.readonly ? () => {} : e => this.setState({ value: e.target.value });
if (this.props.readonly) {
- classes.push(styles.readonly)
+ classes.push(styles.readonly);
}
if (this.props.multiline) {
- var style = { minWidth: this.props.minWidth };
+ const style = { minWidth: this.props.minWidth };
if (this.props.resizable) {
style.resize = 'both';
}
@@ -66,7 +66,8 @@ export default class StringEditor extends React.Component {
ref={this.inputRef}
value={this.state.value}
onChange={onChange}
- style={style} />
+ style={style}
+ />
);
}
@@ -76,7 +77,8 @@ export default class StringEditor extends React.Component {
ref={this.inputRef}
value={this.state.value}
onChange={onChange}
- disabled={this.props.readonly} />
+ disabled={this.props.readonly}
+ />
);
}
diff --git a/src/components/SuggestionsList/SuggestionsList.react.js b/src/components/SuggestionsList/SuggestionsList.react.js
index 6b0268e404..9837425995 100644
--- a/src/components/SuggestionsList/SuggestionsList.react.js
+++ b/src/components/SuggestionsList/SuggestionsList.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Popover from 'components/Popover/Popover.react';
-import React from 'react';
-import styles from 'components/SuggestionsList/SuggestionsList.scss';
+import Popover from 'components/Popover/Popover.react';
+import React from 'react';
+import styles from 'components/SuggestionsList/SuggestionsList.scss';
export default class Suggestion extends React.Component {
constructor() {
@@ -15,7 +15,7 @@ export default class Suggestion extends React.Component {
this.state = {
activeSuggestion: 0,
open: false,
- position: null
+ position: null,
};
this.popoverRef = React.createRef();
@@ -27,7 +27,7 @@ export default class Suggestion extends React.Component {
}
setPosition(position) {
- this.popoverRef.current && this.popoverRef.current.setPosition(position);
+ this.popoverRef.current && this.popoverRef.current.setPosition(position);
}
close() {
@@ -35,35 +35,45 @@ export default class Suggestion extends React.Component {
}
render() {
- const {
- position,
- onExternalClick,
- suggestions,
- suggestionsStyle,
- activeSuggestion,
- onClick} = this.props;
+ const {
+ position,
+ onExternalClick,
+ suggestions,
+ suggestionsStyle,
+ suggestionsItemStyle,
+ activeSuggestion,
+ onClick,
+ onMouseDown,
+ } = this.props;
return (
-
- {suggestions.map((suggestion, index) => {
- let className;
- if (index === activeSuggestion) {
- className = styles.active;
- }
- return (
-
- {suggestion}
-
- );
- })}
-
-
+ fixed={false}
+ position={position}
+ ref={this.popoverRef}
+ onExternalClick={onExternalClick}
+ data-popover-type="inner"
+ >
+
+ {suggestions.map((suggestion, index) => {
+ let className;
+ if (index === activeSuggestion) {
+ className = styles.active;
+ }
+ return (
+
+ {suggestion}
+
+ );
+ })}
+
+
);
}
}
diff --git a/src/components/Table/TableHeader.react.js b/src/components/Table/TableHeader.react.js
index b3b8aa3807..8c3431a25c 100644
--- a/src/components/Table/TableHeader.react.js
+++ b/src/components/Table/TableHeader.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import styles from 'components/Table/Table.scss';
-let TableHeader = ({ width, ...props }) => {
- let style = {};
+const TableHeader = ({ width, ...props }) => {
+ const style = {};
if (width !== undefined) {
style.width = width + '%';
}
return
;
-}
+};
export default TableHeader;
diff --git a/src/components/TextInput/TextInput.example.js b/src/components/TextInput/TextInput.example.js
index 993ee875b4..d6a800d724 100644
--- a/src/components/TextInput/TextInput.example.js
+++ b/src/components/TextInput/TextInput.example.js
@@ -5,11 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import Label from 'components/Label/Label.react';
-import styles from './TextInput.example.scss';
+import React from 'react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import Label from 'components/Label/Label.react';
+import styles from './TextInput.example.scss';
import TextInput from 'components/TextInput/TextInput.react';
class Wrapper extends React.Component {
@@ -26,24 +26,36 @@ export const demos = [
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={ } />
+ label={ }
+ input={ }
+ />
}
- input={
-
-
} />
+ label={ }
+ input={
+
+
+
+ }
+ />
- )
- }
+ ),
+ },
];
diff --git a/src/components/TextInput/TextInput.react.js b/src/components/TextInput/TextInput.react.js
index 6ab9df19a5..4aa72f3b76 100644
--- a/src/components/TextInput/TextInput.react.js
+++ b/src/components/TextInput/TextInput.react.js
@@ -15,7 +15,9 @@ class TextInput extends React.Component {
if (props.multiline !== this.props.multiline) {
const node = props.forwardedRef.current;
node.focus();
- if (this.props.value) node.setSelectionRange(this.props.value.length, this.props.value.length);
+ if (this.props.value) {
+ node.setSelectionRange(this.props.value.length, this.props.value.length);
+ }
}
}
@@ -33,7 +35,7 @@ class TextInput extends React.Component {
}
render() {
- let classes = [styles.text_input];
+ const classes = [styles.text_input];
if (this.props.monospace) {
classes.push(styles.monospace);
}
@@ -45,11 +47,14 @@ class TextInput extends React.Component {
disabled={!!this.props.disabled}
className={classes.join(' ')}
rows={this.props.rows && this.props.rows > 3 ? this.props.rows : null}
- style={this.props.rows && this.props.rows > 3 ? null : {height: this.props.height || 80}}
+ style={
+ this.props.rows && this.props.rows > 3 ? null : { height: this.props.height || 80 }
+ }
placeholder={this.props.placeholder}
value={this.props.value}
onChange={this.changeValue.bind(this)}
- onBlur={this.updateValue.bind(this)} />
+ onBlur={this.updateValue.bind(this)}
+ />
);
}
return (
@@ -59,11 +64,12 @@ class TextInput extends React.Component {
type={this.props.hidden ? 'password' : 'text'}
disabled={!!this.props.disabled}
className={classes.join(' ')}
- style={{height: this.props.height || 80}}
+ style={{ height: this.props.height || 80 }}
placeholder={this.props.placeholder}
value={this.props.value}
onChange={this.changeValue.bind(this)}
- onBlur={this.updateValue.bind(this)} />
+ onBlur={this.updateValue.bind(this)}
+ />
);
}
}
@@ -72,27 +78,17 @@ TextInput.propTypes = {
monospace: PropTypes.bool.describe(
'Determines whether the input is formatted with a monospace font'
),
- disabled: PropTypes.bool.describe(
- 'Determines whether the input is disabled'
- ),
- hidden: PropTypes.bool.describe(
- 'Determines whether the contents are hidden (password field)'
- ),
+ disabled: PropTypes.bool.describe('Determines whether the input is disabled'),
+ hidden: PropTypes.bool.describe('Determines whether the contents are hidden (password field)'),
multiline: PropTypes.bool.describe(
'Determines whether the input is a multiline input (
- )
- }
+ ),
+ },
];
diff --git a/src/components/Tooltip/Tooltip.react.js b/src/components/Tooltip/Tooltip.react.js
index 4d16daf374..e5759d7e46 100644
--- a/src/components/Tooltip/Tooltip.react.js
+++ b/src/components/Tooltip/Tooltip.react.js
@@ -6,17 +6,15 @@
* the root directory of this source tree.
*/
import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import styles from 'components/Tooltip/Tooltip.scss';
+import React from 'react';
+import styles from 'components/Tooltip/Tooltip.scss';
-let Tooltip = ({ value, children }) => {
+const Tooltip = ({ value, children }) => {
return (
{children}
@@ -27,10 +25,6 @@ let Tooltip = ({ value, children }) => {
export default Tooltip;
Tooltip.propTypes = {
- value: PropTypes.node.isRequired.describe(
- 'The tooltip text.'
- ),
- children: PropTypes.node.describe(
- 'The content that should reveal a tooltip when hovered.'
- )
+ value: PropTypes.node.isRequired.describe('The tooltip text.'),
+ children: PropTypes.node.describe('The content that should reveal a tooltip when hovered.'),
};
diff --git a/src/components/TrackVisibility/TrackVisibility.example.js b/src/components/TrackVisibility/TrackVisibility.example.js
index a1ea0661d4..c0347d6647 100644
--- a/src/components/TrackVisibility/TrackVisibility.example.js
+++ b/src/components/TrackVisibility/TrackVisibility.example.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
+import React from 'react';
+import TrackVisibility from 'components/TrackVisibility/TrackVisibility.react';
export const component = TrackVisibility;
@@ -28,29 +28,24 @@ class DemoTrackVisibility extends React.Component {
this.observer = new IntersectionObserver(callback, {
root: this.ref.current,
- threshold: thresholds
+ threshold: thresholds,
});
this.state = {
- visibility: 0
+ visibility: 0,
};
}
render() {
-
return (
{'Yellow block is ' + this.state.visibility + '% visible'}
-
- {'Scroll down'}
-
+
{'Scroll down'}
-
+
-
- {'Scroll up'}
-
+
{'Scroll up'}
);
@@ -58,8 +53,6 @@ class DemoTrackVisibility extends React.Component {
}
export const demos = [
{
- render: () => (
-
- )
- }
+ render: () =>
,
+ },
];
diff --git a/src/dashboard/Account/AccountLinkField.react.js b/src/dashboard/Account/AccountLinkField.react.js
index 6cc7c9e143..e5362950f0 100644
--- a/src/dashboard/Account/AccountLinkField.react.js
+++ b/src/dashboard/Account/AccountLinkField.react.js
@@ -23,21 +23,31 @@ export class AccountLinkField extends React.Component {
render() {
return (
-
}
- input={ this.modifyRef.current.submit()} />}/>
+ input={
+ this.modifyRef.current.submit()}
+ />
+ }
+ />
);
}
@@ -46,10 +56,8 @@ export class AccountLinkField extends React.Component {
export default AccountLinkField;
AccountLinkField.PropTypes = {
- serviceName: PropTypes.string.isRequired.describe(
- 'Text to show on button'
- ),
+ serviceName: PropTypes.string.isRequired.describe('Text to show on button'),
metadata: PropTypes.object.isRequired.describe(
'Data from server with keys: linked, authorize_url, and deauthorize_url'
- )
+ ),
};
diff --git a/src/dashboard/Account/AccountOverview.react.js b/src/dashboard/Account/AccountOverview.react.js
index 2fce5c9ca8..6cc68b9851 100644
--- a/src/dashboard/Account/AccountOverview.react.js
+++ b/src/dashboard/Account/AccountOverview.react.js
@@ -5,25 +5,25 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AccountLinkField from 'dashboard/Account/AccountLinkField.react.js'
-import AccountManager from 'lib/AccountManager';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormButton from 'components/FormButton/FormButton.react';
-import FormModal from 'components/FormModal/FormModal.react';
-import FormTable from 'components/FormTable/FormTable.react';
-import getSiteDomain from 'lib/getSiteDomain';
-import KeyField from 'components/KeyField/KeyField.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import MoneyInput from 'components/MoneyInput/MoneyInput.react';
-import React from 'react';
+import AccountLinkField from 'dashboard/Account/AccountLinkField.react.js';
+import AccountManager from 'lib/AccountManager';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormButton from 'components/FormButton/FormButton.react';
+import FormModal from 'components/FormModal/FormModal.react';
+import FormTable from 'components/FormTable/FormTable.react';
+import getSiteDomain from 'lib/getSiteDomain';
+import KeyField from 'components/KeyField/KeyField.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import MoneyInput from 'components/MoneyInput/MoneyInput.react';
+import React from 'react';
import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
-import styles from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { dateStringUTC } from 'lib/DateUtils';
+import styles from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { dateStringUTC } from 'lib/DateUtils';
const DEFAULT_LABEL_WIDTH = 56;
const XHR_KEY = 'AccountOverview';
@@ -55,7 +55,7 @@ export default class AccountOverview extends React.Component {
componentDidMount() {
this.mounted = true;
- AccountManager.fetchLinkedAccounts(XHR_KEY).then((linkedAccounts) => {
+ AccountManager.fetchLinkedAccounts(XHR_KEY).then(linkedAccounts => {
if (this.mounted) {
this.setState({ linkedAccounts });
}
@@ -66,98 +66,118 @@ export default class AccountOverview extends React.Component {
AccountManager.abortFetch(XHR_KEY);
}
- renderForm({fields}) {
- let accountInfoFields =
-
- }
- input={ {
- this.setState({showChangePasswordModal: true});
- }} />
- } />
- ;
-
- let linkedAccountsFields = this.state.linkedAccounts !== null ?
+ renderForm({ fields }) {
+ const accountInfoFields = (
-
-
-
-
- : null;
+ legend="Account Info"
+ description="Update the personal information linked to this account."
+ >
+
+ }
+ input={
+
{
+ this.setState({ showChangePasswordModal: true });
+ }}
+ />
+ }
+ />
+
+ );
- let accountKeysFields =
- }
- input={ {
- this.setState({showAccountKeyModal: true});
- }} />
- } />
- { AccountManager.currentUser().account_keys.length > 0 ? }
- input={ ({
- title: key.name,
- onDelete: () => {
- this.setState({
- accountKeyIdToDelete: key.id,
- showDeleteAccountKeyModal: true,
- });
- },
- //TODO do fancy colors for (almost) expired keys, like push certs
- color: 'green',
- notes: [
- {
- key: 'Key',
- value: '\u2022\u2022' + key.token,
- strong: true,
- },
- {
- key: 'Expires',
- value: dateStringUTC(new Date(key.expiresAt)),
- },
- ],
- }))} />
- } /> : null}
- ;
+ const linkedAccountsFields =
+ this.state.linkedAccounts !== null ? (
+
+
+
+
+
+
+ ) : null;
- let billingInfoFields =
- {fields.accountCredit > 0 ?
- }
- input={ {}} />
- } /> : null}
- { /* TODO
+ const accountKeysFields = (
+
+ }
+ input={
+ {
+ this.setState({ showAccountKeyModal: true });
+ }}
+ />
+ }
+ />
+ {AccountManager.currentUser().account_keys.length > 0 ? (
+ }
+ input={
+ ({
+ title: key.name,
+ onDelete: () => {
+ this.setState({
+ accountKeyIdToDelete: key.id,
+ showDeleteAccountKeyModal: true,
+ });
+ },
+ //TODO do fancy colors for (almost) expired keys, like push certs
+ color: 'green',
+ notes: [
+ {
+ key: 'Key',
+ value: '\u2022\u2022' + key.token,
+ strong: true,
+ },
+ {
+ key: 'Expires',
+ value: dateStringUTC(new Date(key.expiresAt)),
+ },
+ ],
+ }))}
+ />
+ }
+ />
+ ) : null}
+
+ );
+
+ const billingInfoFields = (
+
+ {fields.accountCredit > 0 ? (
+
+ }
+ input={ {}} />}
+ />
+ ) : null}
+ {/* TODO
}
@@ -186,203 +206,254 @@ export default class AccountOverview extends React.Component {
setField('ccSecurityCode', value.substring(0,4));
}} />
} />
- */ }
- }
- input={ {
- window.open(`${getSiteDomain()}/credit_card/new`);
- }} />
- } />
- }
- input={ {
- window.open(`${getSiteDomain()}/account/plan`);
- }} />
- } />
-
- ;
+ */}
+ }
+ input={
+ {
+ window.open(`${getSiteDomain()}/credit_card/new`);
+ }}
+ />
+ }
+ />
+ }
+ input={
+ {
+ window.open(`${getSiteDomain()}/account/plan`);
+ }}
+ />
+ }
+ />
+
+ );
- let changePasswordModal = {
- return AccountManager.resetPasswordAndEmailAndName(this.state.currentPassword, this.state.newPassword, this.state.email, this.state.name);
- }}
- onClose={() => {
- this.setState({showChangePasswordModal: false});
- }}
- submitText='Change'
- inProgressText={'Changing\u2026'}
- clearFields={() => {
- this.setState({
- currentPassword: '',
- newPassword: '',
- });
- }}
- enabled={this.state.currentPassword.length > 0 && (this.state.newPassword.length > 0 || this.state.email.length > 0)}>
- }
- input={ {
- this.setState({
- name: value,
- });
- }} />
- } />
-
+ const changePasswordModal = (
+ {
+ return AccountManager.resetPasswordAndEmailAndName(
+ this.state.currentPassword,
+ this.state.newPassword,
+ this.state.email,
+ this.state.name
+ );
+ }}
+ onClose={() => {
+ this.setState({ showChangePasswordModal: false });
+ }}
+ submitText="Change"
+ inProgressText={'Changing\u2026'}
+ clearFields={() => {
+ this.setState({
+ currentPassword: '',
+ newPassword: '',
+ });
+ }}
+ enabled={
+ this.state.currentPassword.length > 0 &&
+ (this.state.newPassword.length > 0 || this.state.email.length > 0)
}
- input={ {
- this.setState({
- email: value,
- });
- }} />
- } />
+ >
+ }
+ input={
+ {
+ this.setState({
+ name: value,
+ });
+ }}
+ />
+ }
+ />
+
+ }
+ input={
+ {
+ this.setState({
+ email: value,
+ });
+ }}
+ />
+ }
+ />
- }
- labelWidth={DEFAULT_LABEL_WIDTH}
- input={ {
- this.setState({newPassword: newValue});
- }}
- hidden={true} />} />
- }
- input={ {
- this.setState({currentPassword: newValue});
- }}
- hidden={true} />} />
- ;
+ }
+ labelWidth={DEFAULT_LABEL_WIDTH}
+ input={
+ {
+ this.setState({ newPassword: newValue });
+ }}
+ hidden={true}
+ />
+ }
+ />
+ }
+ input={
+ {
+ this.setState({ currentPassword: newValue });
+ }}
+ hidden={true}
+ />
+ }
+ />
+
+ );
- let accountKeyModal = {
- return AccountManager.createAccountKey(this.state.accountKeyName);
- }}
- onSuccess={newKey => {
- this.setState({
- showNewAccountKeyModal: true,
- newAccountKey: newKey.token,
- });
- }}
- onClose={() => {
- this.setState({showAccountKeyModal: false});
- }}
- submitText='Create'
- inProgressText={'Creating\u2026'}
- clearFields={() => {
- this.setState({accountKeyName: ''});
- }}
- enabled={this.state.accountKeyName.length > 0}>
- }
- input={ {
- this.setState({accountKeyName: newValue});
- }}
- placeholder='Work Laptop' /> } />
-
+ const accountKeyModal = (
+ {
+ return AccountManager.createAccountKey(this.state.accountKeyName);
+ }}
+ onSuccess={newKey => {
+ this.setState({
+ showNewAccountKeyModal: true,
+ newAccountKey: newKey.token,
+ });
+ }}
+ onClose={() => {
+ this.setState({ showAccountKeyModal: false });
+ }}
+ submitText="Create"
+ inProgressText={'Creating\u2026'}
+ clearFields={() => {
+ this.setState({ accountKeyName: '' });
+ }}
+ enabled={this.state.accountKeyName.length > 0}
+ >
+ }
+ input={
+ {
+ this.setState({ accountKeyName: newValue });
+ }}
+ placeholder="Work Laptop"
+ />
+ }
+ />
+
+ );
- let newAccountKeyModal = this.state.showNewAccountKeyModal ? {
- this.setState({
- showNewAccountKeyModal: false,
- newAccountKey: '',
- });
- }}
- buttonsInCenter={true}>
-
- {this.state.newAccountKey}
-
- : null;
+ const newAccountKeyModal = this.state.showNewAccountKeyModal ? (
+ {
+ this.setState({
+ showNewAccountKeyModal: false,
+ newAccountKey: '',
+ });
+ }}
+ buttonsInCenter={true}
+ >
+ {this.state.newAccountKey}
+
+ ) : null;
- let deleteAccountKeyModal = {
- return AccountManager.deleteAccountKeyById(this.state.accountKeyIdToDelete);
- }}
- onClose={() => {
- this.setState({showDeleteAccountKeyModal: false});
- }}
- clearFields={() => {
- this.setState({accountKeyIdToDelete: 0});
- }} />
+ const deleteAccountKeyModal = (
+ {
+ return AccountManager.deleteAccountKeyById(this.state.accountKeyIdToDelete);
+ }}
+ onClose={() => {
+ this.setState({ showDeleteAccountKeyModal: false });
+ }}
+ clearFields={() => {
+ this.setState({ accountKeyIdToDelete: 0 });
+ }}
+ />
+ );
- return
- {accountInfoFields}
- {accountKeysFields}
- {linkedAccountsFields}
- {billingInfoFields}
-
- {changePasswordModal}
- {accountKeyModal}
- {newAccountKeyModal}
- {deleteAccountKeyModal}
-
;
+ return (
+
+ {accountInfoFields}
+ {accountKeysFields}
+ {linkedAccountsFields}
+ {billingInfoFields}
+
+ {changePasswordModal}
+ {accountKeyModal}
+ {newAccountKeyModal}
+ {deleteAccountKeyModal}
+
+ );
}
render() {
- let user = AccountManager.currentUser();
- let initialFields = {
+ const user = AccountManager.currentUser();
+ const initialFields = {
accountCredit: user.stripe_credit,
cc_last4: user.cc_last4, //null means user has not added a credit card
ccSecurityCode: '',
ccExp: '',
- }
- return renderFlowFooterChanges(changes, initialFields, accountOverviewFooterSettings)}
- onSubmit={() => {
- let promiseList = [];
- /* eslint-disable */
- if (changes.cc !== undefined) {
- //TODO change credit card number
+ };
+ return (
+
+ renderFlowFooterChanges(changes, initialFields, accountOverviewFooterSettings)
}
- /* eslint-enable */
- return Promise.all(promiseList);
- }}
- renderForm={this.renderForm.bind(this)} />;
+ onSubmit={() => {
+ const promiseList = [];
+ /* eslint-disable */
+ if (changes.cc !== undefined) {
+ //TODO change credit card number
+ }
+ /* eslint-enable */
+ return Promise.all(promiseList);
+ }}
+ renderForm={this.renderForm.bind(this)}
+ />
+ );
}
}
-let accountOverviewFooterSettings = {
+const accountOverviewFooterSettings = {
cc: {
friendlyName: 'credit card information',
},
-}
+};
diff --git a/src/dashboard/AccountView.react.js b/src/dashboard/AccountView.react.js
index 1a44591f63..a031e99b09 100644
--- a/src/dashboard/AccountView.react.js
+++ b/src/dashboard/AccountView.react.js
@@ -11,16 +11,14 @@ import styles from 'dashboard/Dashboard.scss';
export default class AccountView extends React.Component {
render() {
- let sidebar = buildAccountSidebar({
+ const sidebar = buildAccountSidebar({
section: this.props.section,
- subsection: this.props.subsection
+ subsection: this.props.subsection,
});
return (
-
- {this.props.children}
-
+
{this.props.children}
{sidebar}
);
diff --git a/src/dashboard/Analytics/AnalyticsConstants.js b/src/dashboard/Analytics/AnalyticsConstants.js
index c40e24a3b8..7ec0b3d0b5 100644
--- a/src/dashboard/Analytics/AnalyticsConstants.js
+++ b/src/dashboard/Analytics/AnalyticsConstants.js
@@ -15,63 +15,64 @@ export const PresetQueries = [
query: {
endpoint: 'audience',
audienceType: 'daily_installations',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Daily Active Users',
query: {
endpoint: 'audience',
audienceType: 'daily_users',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Weekly Active Installations',
query: {
endpoint: 'audience',
audienceType: 'weekly_installations',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Weekly Active Users',
query: {
endpoint: 'audience',
audienceType: 'weekly_users',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Monthly Active Installations',
query: {
endpoint: 'audience',
audienceType: 'monthly_installations',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Monthly Active Users',
query: {
endpoint: 'audience',
audienceType: 'monthly_users',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
- }
- ]
- }, {
+ nonComposable: true,
+ },
+ ],
+ },
+ {
// TODO everything here should use real explorer instead.
// But I'm not confident since the result of explorer != result of legacy endpoint.
name: 'Events',
@@ -80,56 +81,56 @@ export const PresetQueries = [
name: 'API Requests',
query: {
endpoint: 'api_request',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Analytics Requests',
query: {
endpoint: 'analytics_request',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'File Requests',
query: {
endpoint: 'file_request',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Push Notifications',
query: {
endpoint: 'push',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'App Opens',
query: {
endpoint: 'app_opened',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Push Opens',
query: {
endpoint: 'app_opened_from_push',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
- }
- ]
- }
+ nonComposable: true,
+ },
+ ],
+ },
];
diff --git a/src/dashboard/Analytics/Explorer/Explorer.react.js b/src/dashboard/Analytics/Explorer/Explorer.react.js
index 728493d05d..0182fcfdae 100644
--- a/src/dashboard/Analytics/Explorer/Explorer.react.js
+++ b/src/dashboard/Analytics/Explorer/Explorer.react.js
@@ -5,33 +5,33 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { ActionTypes } from 'lib/stores/AnalyticsQueryStore';
-import * as AnalyticsConstants from 'dashboard/Analytics/AnalyticsConstants';
-import Button from 'components/Button/Button.react';
-import CategoryList from 'components/CategoryList/CategoryList.react';
-import Chart from 'components/Chart/Chart.react';
-import { ChartColorSchemes } from 'lib/Constants';
-import DashboardView from 'dashboard/DashboardView.react';
-import DateRange from 'components/DateRange/DateRange.react';
-import { Directions } from 'lib/Constants';
-import EmptyState from 'components/EmptyState/EmptyState.react';
+import { ActionTypes } from 'lib/stores/AnalyticsQueryStore';
+import * as AnalyticsConstants from 'dashboard/Analytics/AnalyticsConstants';
+import Button from 'components/Button/Button.react';
+import CategoryList from 'components/CategoryList/CategoryList.react';
+import Chart from 'components/Chart/Chart.react';
+import { ChartColorSchemes } from 'lib/Constants';
+import DashboardView from 'dashboard/DashboardView.react';
+import DateRange from 'components/DateRange/DateRange.react';
+import { Directions } from 'lib/Constants';
+import EmptyState from 'components/EmptyState/EmptyState.react';
import ExplorerActiveChartButton from 'components/ExplorerActiveChartButton/ExplorerActiveChartButton.react';
-import ExplorerMenuButton from 'components/ExplorerMenuButton/ExplorerMenuButton.react';
-import Icon from 'components/Icon/Icon.react';
-import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import Parse from 'parse';
-import prettyNumber from 'lib/prettyNumber';
-import React from 'react';
-import styles from 'dashboard/Analytics/Explorer/Explorer.scss';
-import stylesTable from 'components/Table/Table.scss';
-import subscribeTo from 'lib/subscribeTo';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import baseStyles from 'stylesheets/base.scss';
+import ExplorerMenuButton from 'components/ExplorerMenuButton/ExplorerMenuButton.react';
+import Icon from 'components/Icon/Icon.react';
+import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import Parse from 'parse';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import styles from 'dashboard/Analytics/Explorer/Explorer.scss';
+import stylesTable from 'components/Table/Table.scss';
+import subscribeTo from 'lib/subscribeTo';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import baseStyles from 'stylesheets/base.scss';
import { withRouter } from 'lib/withRouter';
-let buildFriendlyName = (query) => {
- let name = [query.source];
+const buildFriendlyName = query => {
+ const name = [query.source];
if (query.groups && query.groups.length > 0) {
name.push('grouped by');
name.push(...query.groups);
@@ -49,31 +49,27 @@ class Explorer extends DashboardView {
this.displaySize = {
width: 800,
- height: 400
+ height: 400,
};
- let date = new Date();
+ const date = new Date();
this.state = {
activeQueries: [],
dateRange: {
- start: new Date(
- date.getFullYear(),
- date.getMonth() - 1,
- date.getDate()
- ),
- end: date
+ start: new Date(date.getFullYear(), date.getMonth() - 1, date.getDate()),
+ end: date,
},
loading: false,
- mutated: false
+ mutated: false,
};
this.xhrHandles = [];
this.displayRef = React.createRef();
}
componentDidMount() {
- let display = this.displayRef.current;
+ const display = this.displayRef.current;
this.displaySize = {
width: display.offsetWidth,
- height: display.offsetHeight
+ height: display.offsetHeight,
};
}
@@ -95,20 +91,20 @@ class Explorer extends DashboardView {
}
getCustomQueriesFromProps(props) {
- let customQueries = props.customQueries.data.get('queries');
+ const customQueries = props.customQueries.data.get('queries');
return (customQueries && customQueries.toArray()) || [];
}
handleQueryToggle(index, active) {
- let activeQueries = this.state.activeQueries;
+ const activeQueries = this.state.activeQueries;
activeQueries[index].enabled = active;
this.setState({ activeQueries: activeQueries });
}
handleQuerySave(query) {
// Push new save result
- let activeQueries = this.state.activeQueries;
- let existingQueryIndex = activeQueries.findIndex((activeQuery) => {
+ const activeQueries = this.state.activeQueries;
+ const existingQueryIndex = activeQueries.findIndex(activeQuery => {
if (query.localId) {
return query.localId === activeQuery.localId;
}
@@ -135,30 +131,30 @@ class Explorer extends DashboardView {
// Update the state to trigger rendering pipeline.
this.setState({
activeQueries,
- mutated: true
+ mutated: true,
});
}
handleQuerySelect(query) {
- let activeQueries = this.state.activeQueries;
+ const activeQueries = this.state.activeQueries;
query.enabled = true;
activeQueries.push(query);
this.setState({
activeQueries,
- mutated: true
+ mutated: true,
});
}
handleQueryDelete(query) {
this.props.customQueries.dispatch(ActionTypes.DELETE, {
query: {
- objectId: query.objectId
- }
+ objectId: query.objectId,
+ },
});
}
handleRunQuery() {
- let promises = [];
+ const promises = [];
this.xhrHandles = [];
this.setState({ loading: true });
this.state.activeQueries.forEach((query, i) => {
@@ -166,38 +162,39 @@ class Explorer extends DashboardView {
let xhr = null;
if (query.preset && query.nonComposable) {
// A preset query, DAU, MAU, DAI
- let payload = {
+ const payload = {
...query.query,
from: this.state.dateRange.start.getTime() / 1000,
- to: this.state.dateRange.end.getTime() / 1000
+ to: this.state.dateRange.end.getTime() / 1000,
};
- let abortableRequest = this.context.getAnalyticsTimeSeries(payload);
- promise = abortableRequest.promise.then((result) => {
- let activeQueries = this.state.activeQueries;
- activeQueries[i].result = result.map((point) => (
- [Parse._decode('date', point[0]).getTime(), point[1]]
- ));
+ const abortableRequest = this.context.getAnalyticsTimeSeries(payload);
+ promise = abortableRequest.promise.then(result => {
+ const activeQueries = this.state.activeQueries;
+ activeQueries[i].result = result.map(point => [
+ Parse._decode('date', point[0]).getTime(),
+ point[1],
+ ]);
this.setState({ activeQueries });
});
xhr = abortableRequest.xhr;
} else {
// Custom query
- let payload = this.buildCustomQueryPayload(query);
+ const payload = this.buildCustomQueryPayload(query);
promise = this.props.customQueries.dispatch(ActionTypes.FETCH, payload).then(() => {
let activeQueries = this.state.activeQueries;
// Update the result based on store in background.
- let customQueries = this.getCustomQueriesFromProps(this.props);
- activeQueries = activeQueries.map((query) => {
+ const customQueries = this.getCustomQueriesFromProps(this.props);
+ activeQueries = activeQueries.map(query => {
let serverResult = null;
if (query.objectId) {
// We have predefined custom query.
- serverResult = customQueries.find((customQuery) => {
+ serverResult = customQueries.find(customQuery => {
return customQuery.objectId === query.objectId;
});
} else if (query.localId) {
// We're in the middle of custom query creation.
- serverResult = customQueries.find((customQuery) => {
+ serverResult = customQueries.find(customQuery => {
return customQuery.localId === query.localId;
});
}
@@ -208,7 +205,7 @@ class Explorer extends DashboardView {
return {
...query,
- result: serverResult.result
+ result: serverResult.result,
};
});
@@ -220,19 +217,21 @@ class Explorer extends DashboardView {
promises.push(promise);
this.xhrHandles.push(xhr);
});
- Promise.all(promises).then(() => this.setState({
- loading: false,
- mutated: false
- }));
+ Promise.all(promises).then(() =>
+ this.setState({
+ loading: false,
+ mutated: false,
+ })
+ );
}
handleDownload() {
const csvDeclaration = 'data:text/csv;charset=utf-8,';
- let csvRows = this.state.activeQueries.map((query) => {
+ const csvRows = this.state.activeQueries.map(query => {
switch (this.props.params.displayType) {
case 'chart':
- let columns = ['time'];
- let csvValues = [];
+ const columns = ['time'];
+ const csvValues = [];
// Transform:
// {
// foo: [[123, bar], [456, baz]]
@@ -240,8 +239,8 @@ class Explorer extends DashboardView {
// }
// into
// [[time, foo, a], [123, bar, b], [456, baz, c]]
- for (let key in query.result) {
- let result = query.result[key];
+ for (const key in query.result) {
+ const result = query.result[key];
columns.push(key);
result.forEach((value, index) => {
if (csvValues[index] === undefined) {
@@ -268,9 +267,7 @@ class Explorer extends DashboardView {
// ]
// into
// [[foo, a], [bar, b], [baz, c]]
- csvArray = csvArray.concat(query.result.map((result) => (
- keys.map((key) => result[key])
- )));
+ csvArray = csvArray.concat(query.result.map(result => keys.map(key => result[key])));
return csvArray.join('\n');
}
@@ -280,126 +277,137 @@ class Explorer extends DashboardView {
}
buildCustomQueryPayload(query) {
- let queryWithoutResult = { ...query };
+ const queryWithoutResult = { ...query };
queryWithoutResult.result = undefined;
- let payload = {
+ const payload = {
...queryWithoutResult,
type: this.props.params.displayType,
from: this.state.dateRange.start.getTime(),
- to: this.state.dateRange.end.getTime()
- }
+ to: this.state.dateRange.end.getTime(),
+ };
return {
query: {
- ...payload
- }
+ ...payload,
+ },
};
}
renderSidebar() {
- let current = this.props.params.displayType || '';
+ const current = this.props.params.displayType || '';
return (
-
+
);
}
renderContent() {
- let { displayType } = this.props.params;
- let isTimeSeries = displayType === 'chart';
- let explorerQueries = this.getCustomQueriesFromProps(this.props);
- let savedQueries = explorerQueries.filter((query) => query.type === displayType && query.isSaved);
- let recentQueries = explorerQueries.filter((query) => query.type === displayType && !query.isSaved);
+ const { displayType } = this.props.params;
+ const isTimeSeries = displayType === 'chart';
+ const explorerQueries = this.getCustomQueriesFromProps(this.props);
+ const savedQueries = explorerQueries.filter(
+ query => query.type === displayType && query.isSaved
+ );
+ const recentQueries = explorerQueries.filter(
+ query => query.type === displayType && !query.isSaved
+ );
let queries = [];
if (isTimeSeries) {
// We don't allow preset queries on Table/JSON
queries = queries.concat(AnalyticsConstants.PresetQueries);
}
- queries = queries.concat({
- name: 'Saved Queries',
- children: savedQueries,
- emptyMessage: 'You have not saved any queries yet.'
- }, {
- name: 'Recent Queries',
- children: recentQueries,
- emptyMessage: 'You have no recent custom queries yet.'
- });
+ queries = queries.concat(
+ {
+ name: 'Saved Queries',
+ children: savedQueries,
+ emptyMessage: 'You have not saved any queries yet.',
+ },
+ {
+ name: 'Recent Queries',
+ children: recentQueries,
+ emptyMessage: 'You have no recent custom queries yet.',
+ }
+ );
- let toolbar = (
-
+ const toolbar = (
+
-
+ style={{ borderRight: '1px solid #66637a' }}
+ >
+
FAQ
-
+ className={styles.toolbarAction}
+ >
+
Download
);
- let activeQueryViews = this.state.activeQueries.map((query, i) => (
+ const activeQueryViews = this.state.activeQueries.map((query, i) => (
{
- let activeQueries = this.state.activeQueries;
+ const activeQueries = this.state.activeQueries;
activeQueries.splice(i, 1);
this.setState({ activeQueries, mutated: true });
}}
isTimeSeries={isTimeSeries}
query={query}
color={ChartColorSchemes[i]}
- queries={queries} />
+ queries={queries}
+ />
));
activeQueryViews.push(
-
+
+ value="Add query"
+ queries={queries}
+ />
);
- let header = (
-
- {activeQueryViews}
-
- );
+ const header =
{activeQueryViews}
;
- let footer = (
+ const footer = (
(this.setState({ dateRange: newValue, mutated: true }))}
- align={Directions.RIGHT} />
+ onChange={newValue => this.setState({ dateRange: newValue, mutated: true })}
+ align={Directions.RIGHT}
+ />
+ value="Run query"
+ />
);
@@ -409,13 +417,14 @@ class Explorer extends DashboardView {
currentDisplay = (
+ icon="analytics-outline"
+ description={'Use the "Add query" button above to visualize your data.'}
+ />
);
} else {
switch (displayType) {
case 'chart':
- let chartData = {};
+ const chartData = {};
this.state.activeQueries.forEach((query, i) => {
if (!query.result || Object.keys(query.result).length === 0) {
return;
@@ -427,16 +436,16 @@ class Explorer extends DashboardView {
if (Array.isArray(query.result)) {
chartData[query.name] = {
color: ChartColorSchemes[i],
- points: query.result
- }
+ points: query.result,
+ };
} else {
let index = 0;
- for (let key in query.result) {
+ for (const key in query.result) {
chartData[query.name + ' ' + key] = {
color: ChartColorSchemes[i],
points: query.result[key],
- index: index++
- }
+ index: index++,
+ };
}
}
});
@@ -448,7 +457,8 @@ class Explorer extends DashboardView {
width={this.displaySize.width}
height={this.displaySize.height}
data={chartData}
- formatter={(value, label) => (`${label} ${prettyNumber(value, 3)}`)} />
+ formatter={(value, label) => `${label} ${prettyNumber(value, 3)}`}
+ />
);
}
break;
@@ -465,16 +475,17 @@ class Explorer extends DashboardView {
return null;
}
- let width = Math.floor(100 / query.result[0].length);
- let headers = query.result[0].map((header) => (
+ const width = Math.floor(100 / query.result[0].length);
+ const headers = query.result[0].map(header => (
+ style={{ display: 'table-cell' }}
+ >
{header}
));
- let rows = [];
+ const rows = [];
for (let i = 1; i < query.result.length; ++i) {
rows.push(
@@ -482,7 +493,8 @@ class Explorer extends DashboardView {
+ width={`${width}%`}
+ >
{value}
))}
@@ -495,13 +507,9 @@ class Explorer extends DashboardView {
-
- {headers}
-
+ {headers}
-
- {rows}
-
+ {rows}
@@ -518,17 +526,13 @@ class Explorer extends DashboardView {
return null;
}
- return (
-
- );
+ return ;
});
break;
}
}
- let content = (
+ const content = (
{currentDisplay}
diff --git a/src/dashboard/Analytics/Overview/Overview.react.js b/src/dashboard/Analytics/Overview/Overview.react.js
index 1848f055ff..5373055aeb 100644
--- a/src/dashboard/Analytics/Overview/Overview.react.js
+++ b/src/dashboard/Analytics/Overview/Overview.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import DonutChart from 'components/DonutChart/DonutChart.react';
-import Icon from 'components/Icon/Icon.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react'
-import prettyNumber from 'lib/prettyNumber'
-import React from 'react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import styles from 'dashboard/Analytics/Overview/Overview.scss';
+import Button from 'components/Button/Button.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import DonutChart from 'components/DonutChart/DonutChart.react';
+import Icon from 'components/Icon/Icon.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import styles from 'dashboard/Analytics/Overview/Overview.scss';
import { yearMonthDayFormatter } from 'lib/DateUtils';
const AUDIENCE_META = [
@@ -21,42 +21,42 @@ const AUDIENCE_META = [
[
{
label: 'Daily active users',
- key: 'dailyActiveUsers'
+ key: 'dailyActiveUsers',
},
{
label: 'Weekly active users',
- key: 'weeklyActiveUsers'
+ key: 'weeklyActiveUsers',
},
{
label: 'Monthly active users',
- key: 'monthlyActiveUsers'
+ key: 'monthlyActiveUsers',
},
{
label: 'Total users',
key: 'totalUsers',
- hideArrow: true
- }
+ hideArrow: true,
+ },
],
// Installations
[
{
label: 'Daily active installs',
- key: 'dailyActiveInstallations'
+ key: 'dailyActiveInstallations',
},
{
label: 'Weekly active installs',
- key: 'weeklyActiveInstallations'
+ key: 'weeklyActiveInstallations',
},
{
label: 'Monthly active installs',
- key: 'monthlyActiveInstallations'
+ key: 'monthlyActiveInstallations',
},
{
label: 'Total installs',
key: 'totalInstallations',
- hideArrow: true
- }
- ]
+ hideArrow: true,
+ },
+ ],
];
const BILLING_META = [
@@ -74,7 +74,7 @@ const BILLING_META = [
label: 'Data transfer',
key: 'billingDataTransfer',
units: 'TB',
- }
+ },
];
export default class Overview extends DashboardView {
@@ -95,8 +95,8 @@ export default class Overview extends DashboardView {
totalInstallations: {},
billingFileStorage: {},
billingDatabasetorage: {},
- billingDataTransfer: {}
- }
+ billingDataTransfer: {},
+ };
}
componentWillMount() {
@@ -110,15 +110,15 @@ export default class Overview extends DashboardView {
}
componentWillUnmount() {
- AUDIENCE_META.forEach((metaGroup) => {
- metaGroup.forEach((meta) => {
+ AUDIENCE_META.forEach(metaGroup => {
+ metaGroup.forEach(meta => {
if (this.state[meta.key].xhr) {
this.state[meta.key].xhr.abort();
}
});
});
- BILLING_META.forEach((meta) => {
+ BILLING_META.forEach(meta => {
if (this.state[meta.key].xhr) {
this.state[meta.key].xhr.abort();
}
@@ -126,26 +126,26 @@ export default class Overview extends DashboardView {
}
fetchOverview(app) {
- let overview = app.getAnalyticsOverview(new Date());
+ const overview = app.getAnalyticsOverview(new Date());
this.setState(overview);
- for (let key in overview) {
- let item = overview[key];
+ for (const key in overview) {
+ const item = overview[key];
item.promise.then(
- (value) => {
+ value => {
this.setState({
[key]: {
promise: item.promise,
- value: value
- }
+ value: value,
+ },
});
},
- (error) => {
+ error => {
this.setState({
[key]: {
promise: item.promise,
- error: error
- }
+ error: error,
+ },
});
}
);
@@ -153,19 +153,15 @@ export default class Overview extends DashboardView {
}
renderContent() {
- let toolbar = (
-
- );
- let infoContainerStyle = { padding: '12px 16px' };
+ const toolbar =
;
+ const infoContainerStyle = { padding: '12px 16px' };
- let audienceViews = AUDIENCE_META.map((metaGroup) => (
- metaGroup.map((meta) => {
- let obj = this.state[meta.key];
+ const audienceViews = AUDIENCE_META.map(metaGroup =>
+ metaGroup.map(meta => {
+ const obj = this.state[meta.key];
let number = 0;
let increasing = true;
- let loading = obj.value === undefined && obj.error === undefined;
+ const loading = obj.value === undefined && obj.error === undefined;
if (obj.value !== undefined) {
// If it's an array, means it's in [current, 1 week ago, 2 weeks ago] format.
@@ -179,12 +175,14 @@ export default class Overview extends DashboardView {
let content = null;
if (obj.error !== undefined) {
- content =
Cannot fetch data
+ content =
Cannot fetch data
;
} else {
content = (
{prettyNumber(number, 3)}
- {meta.hideArrow ? null : }
+ {meta.hideArrow ? null : (
+
+ )}
);
}
@@ -200,13 +198,13 @@ export default class Overview extends DashboardView {
);
})
- ));
+ );
- let billingViews = BILLING_META.map((meta) => {
- let obj = this.state[meta.key];
+ const billingViews = BILLING_META.map(meta => {
+ const obj = this.state[meta.key];
let total = 0;
let limit = 1;
- let loading = obj.value === undefined && obj.error === undefined;
+ const loading = obj.value === undefined && obj.error === undefined;
if (obj.value !== undefined) {
total = obj.value.total;
@@ -214,7 +212,7 @@ export default class Overview extends DashboardView {
}
if (obj.error !== undefined) {
- content =
Cannot fetch data
+ content =
Cannot fetch data
;
} else {
content = (
+ isMonochrome={true}
+ />
);
}
@@ -242,24 +241,26 @@ export default class Overview extends DashboardView {
- {this.state.error !== undefined ?
-
-
- There is an issue with your app!
-
+ {this.state.error !== undefined ? (
- {this.state.error}
+
+
There is an issue with your app!
+
{this.state.error}
+
{
+ /* TODO (hallucinogen): where should I direct everyone to? */
+ }}
+ primary={true}
+ color="red"
+ value="How do I fix it?"
+ />
-
{/* TODO (hallucinogen): where should I direct everyone to? */}}
- primary={true}
- color='red'
- value='How do I fix it?' />
- :
-
-
Your app is healthy!
-
- }
+ ) : (
+
+
+
Your app is healthy!
+
+ )}
@@ -268,9 +269,7 @@ export default class Overview extends DashboardView {
-
- {audienceViews[0]}
-
+
{audienceViews[0]}
@@ -278,9 +277,7 @@ export default class Overview extends DashboardView {
-
- {audienceViews[1]}
-
+
{audienceViews[1]}
@@ -288,9 +285,7 @@ export default class Overview extends DashboardView {
-
- {billingViews}
-
+
{billingViews}
);
diff --git a/src/dashboard/Analytics/Performance/Performance.react.js b/src/dashboard/Analytics/Performance/Performance.react.js
index b515f4c1ac..02362a65e3 100644
--- a/src/dashboard/Analytics/Performance/Performance.react.js
+++ b/src/dashboard/Analytics/Performance/Performance.react.js
@@ -5,19 +5,19 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Chart from 'components/Chart/Chart.react';
-import { ChartColorSchemes } from 'lib/Constants';
-import DashboardView from 'dashboard/DashboardView.react';
-import DateRange from 'components/DateRange/DateRange.react';
-import { Directions } from 'lib/Constants';
+import Button from 'components/Button/Button.react';
+import Chart from 'components/Chart/Chart.react';
+import { ChartColorSchemes } from 'lib/Constants';
+import DashboardView from 'dashboard/DashboardView.react';
+import DateRange from 'components/DateRange/DateRange.react';
+import { Directions } from 'lib/Constants';
import ExplorerActiveChartButton from 'components/ExplorerActiveChartButton/ExplorerActiveChartButton.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import Parse from 'parse';
-import React from 'react';
-import styles from 'dashboard/Analytics/Performance/Performance.scss';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import baseStyles from 'stylesheets/base.scss';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import Parse from 'parse';
+import React from 'react';
+import styles from 'dashboard/Analytics/Performance/Performance.scss';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import baseStyles from 'stylesheets/base.scss';
const PERFORMANCE_QUERIES = [
{
@@ -25,79 +25,75 @@ const PERFORMANCE_QUERIES = [
query: {
endpoint: 'performance',
performanceType: 'total_requests',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Request Limit',
query: {
endpoint: 'performance',
performanceType: 'request_limit',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Dropped Requests',
query: {
endpoint: 'performance',
performanceType: 'dropped_requests',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
+ nonComposable: true,
},
{
name: 'Served Requests',
query: {
endpoint: 'performance',
performanceType: 'served_requests',
- stride: 'day'
+ stride: 'day',
},
preset: true,
- nonComposable: true
- }
+ nonComposable: true,
+ },
];
export default class Performance extends DashboardView {
constructor() {
super();
this.section = 'Analytics';
- this.subsection = 'Performance'
+ this.subsection = 'Performance';
this.displaySize = {
width: 800,
- height: 400
+ height: 400,
};
- let date = new Date();
+ const date = new Date();
this.state = {
dateRange: {
- start: new Date(
- date.getFullYear(),
- date.getMonth(),
- date.getDate() - 1
- ),
- end: date
+ start: new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1),
+ end: date,
},
loading: true,
performanceData: PERFORMANCE_QUERIES.map(() => ({})),
activeQueries: PERFORMANCE_QUERIES.map(() => true),
// If dateRange is modified, we should set mutated to true
// and re-style "Run query" button
- mutated: false
+ mutated: false,
};
this.xhrHandles = [];
this.displayRef = React.createRef();
}
componentDidMount() {
- let display = this.displayRef;
+ const display = this.displayRef;
this.displaySize = {
width: display.offsetWidth,
- height: display.offsetHeight
+ height: display.offsetHeight,
};
}
@@ -116,29 +112,31 @@ export default class Performance extends DashboardView {
}
handleQueryToggle(index, active) {
- let activeQueries = this.state.activeQueries;
+ const activeQueries = this.state.activeQueries;
activeQueries[index] = active;
this.setState({ activeQueries: activeQueries });
}
handleRunQuery(app) {
this.setState({
- loading: true
+ loading: true,
});
- let promises = [];
+ const promises = [];
this.xhrHandles = [];
PERFORMANCE_QUERIES.forEach((query, index) => {
- let { promise, xhr } = app.getAnalyticsTimeSeries({
+ const res = app.getAnalyticsTimeSeries({
...query.query,
from: this.state.dateRange.start.getTime() / 1000,
- to: this.state.dateRange.end.getTime() / 1000
+ to: this.state.dateRange.end.getTime() / 1000,
});
- promise = promise.then((result) => {
- let performanceData = this.state.performanceData;
+ let promise = res.promise;
+ const xhr = res.xhr;
+ promise = promise.then(result => {
+ const performanceData = this.state.performanceData;
performanceData[index] = result;
this.setState({
- performanceData: performanceData
+ performanceData: performanceData,
});
});
@@ -148,19 +146,15 @@ export default class Performance extends DashboardView {
Promise.all(promises).then(() => {
this.setState({
loading: false,
- mutated: false
+ mutated: false,
});
});
}
renderContent() {
- let toolbar = (
-
- );
+ const toolbar = ;
- let header = (
+ const header = (
{PERFORMANCE_QUERIES.map((query, i) => (
@@ -169,31 +163,34 @@ export default class Performance extends DashboardView {
query={query}
color={ChartColorSchemes[i]}
queries={[]}
- disableDropdown={true} />
+ disableDropdown={true}
+ />
))}
);
- let footer = (
+ const footer = (
(this.setState({ dateRange: newValue, mutated: true }))}
- align={Directions.RIGHT} />
+ onChange={newValue => this.setState({ dateRange: newValue, mutated: true })}
+ align={Directions.RIGHT}
+ />
+ value="Run query"
+ />
);
- let chartData = {};
+ const chartData = {};
this.state.performanceData.forEach((data, i) => {
if (!this.state.activeQueries[i]) {
return null;
@@ -201,27 +198,25 @@ export default class Performance extends DashboardView {
if (Array.isArray(data)) {
// Handle Request Limit
- let points = data.map((point) => (
- [Parse._decode('date', point[0]).getTime(), point[1]]
- ));
+ const points = data.map(point => [Parse._decode('date', point[0]).getTime(), point[1]]);
chartData[PERFORMANCE_QUERIES[i].name] = {
color: ChartColorSchemes[i],
- points: points
+ points: points,
};
} else {
let points = [];
- for (let key in data.cached) {
- let cachedPoints = data.cached[key];
- points = points.concat(cachedPoints.map((point) => (
- [Parse._decode('date', point[0]).getTime(), point[1]]
- )));
+ for (const key in data.cached) {
+ const cachedPoints = data.cached[key];
+ points = points.concat(
+ cachedPoints.map(point => [Parse._decode('date', point[0]).getTime(), point[1]])
+ );
}
if (points.length > 0) {
chartData[PERFORMANCE_QUERIES[i].name] = {
color: ChartColorSchemes[i],
- points: points
+ points: points,
};
}
}
@@ -229,14 +224,11 @@ export default class Performance extends DashboardView {
let chart = null;
if (Object.keys(chartData).length > 0) {
chart = (
-
+
);
}
- let content = (
+ const content = (
diff --git a/src/dashboard/Analytics/Retention/Retention.react.js b/src/dashboard/Analytics/Retention/Retention.react.js
index 51c4b216c4..7ec64e1355 100644
--- a/src/dashboard/Analytics/Retention/Retention.react.js
+++ b/src/dashboard/Analytics/Retention/Retention.react.js
@@ -5,50 +5,50 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import ChromeDatePicker from 'components/ChromeDatePicker/ChromeDatePicker.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import * as DateUtils from 'lib/DateUtils';
-import EmptyState from 'components/EmptyState/EmptyState.react';
+import Button from 'components/Button/Button.react';
+import ChromeDatePicker from 'components/ChromeDatePicker/ChromeDatePicker.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import * as DateUtils from 'lib/DateUtils';
+import EmptyState from 'components/EmptyState/EmptyState.react';
import englishOrdinalIndicator from 'lib/englishOrdinalIndicator';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import prettyNumber from 'lib/prettyNumber';
-import React from 'react';
-import styles from 'dashboard/Analytics/Retention/Retention.scss';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import Tooltip from 'components/Tooltip/Tooltip.react';
-import baseStyles from 'stylesheets/base.scss';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import styles from 'dashboard/Analytics/Retention/Retention.scss';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import Tooltip from 'components/Tooltip/Tooltip.react';
+import baseStyles from 'stylesheets/base.scss';
const RETENTION_DAYS = [1, 2, 3, 4, 5, 6, 7, 8, 14, 21, 28];
const REVERSED_RETENTION_DAYS = RETENTION_DAYS.slice().reverse();
-let retentionChartColor = percent => {
+const retentionChartColor = percent => {
let red, blue, green;
if (percent > 50) {
- red = 23 + ( ( percent - 50 ) * 2 ) * 11 / 100;
- green = 166 - ( ( percent - 50 ) * 2 ) * 166 / 100;
- blue = 255;
+ red = 23 + ((percent - 50) * 2 * 11) / 100;
+ green = 166 - ((percent - 50) * 2 * 166) / 100;
+ blue = 255;
} else {
- red = 228 - ( percent * 2 ) * 205 / 100;
- green = 233 - ( percent * 2 ) * 67 / 100;
- blue = 237 + ( percent * 2 ) * 18 / 100;
+ red = 228 - (percent * 2 * 205) / 100;
+ green = 233 - (percent * 2 * 67) / 100;
+ blue = 237 + (percent * 2 * 18) / 100;
}
//return without decimals since css doesn't allow them
return 'rgb(' + red.toFixed(0) + ', ' + green.toFixed(0) + ', ' + blue.toFixed(0) + ')';
-}
+};
export default class Retention extends DashboardView {
constructor() {
super();
this.section = 'Analytics';
- this.subsection = 'Retention'
+ this.subsection = 'Retention';
this.xhrHandles = [];
this.state = {
retentions: null,
loading: true,
mutated: false,
- date: new Date()
+ date: new Date(),
};
}
@@ -57,7 +57,7 @@ export default class Retention extends DashboardView {
}
componentWillUnmount() {
- this.xhrHandles.forEach((xhr) => xhr.abort());
+ this.xhrHandles.forEach(xhr => xhr.abort());
}
componentWillReceiveProps(nextProps, nextContext) {
@@ -68,9 +68,9 @@ export default class Retention extends DashboardView {
fetchRetention(app) {
this.setState({ loading: true }, () => {
- let { promise, xhr } = app.getAnalyticsRetention(this.state.date);
+ const { promise, xhr } = app.getAnalyticsRetention(this.state.date);
promise.then(
- (result) => this.setState({ retentions: result.content, loading: false }),
+ result => this.setState({ retentions: result.content, loading: false }),
() => this.setState({ retentions: null, loading: false })
);
this.xhrHandles = [xhr];
@@ -82,27 +82,36 @@ export default class Retention extends DashboardView {
let active = 0;
// Somehow it's possible to miss some data. Probably a backend issue, but it's
// not easily reproducible locally.
- let dayData = this.state.retentions['days_old_' + daysAgo] && this.state.retentions['days_old_' + daysAgo]['day_' + day];
+ const dayData =
+ this.state.retentions['days_old_' + daysAgo] &&
+ this.state.retentions['days_old_' + daysAgo]['day_' + day];
if (dayData) {
total = dayData.total;
active = dayData.active;
}
- let percentage = (active / (total || 1) * 100).toFixed(1);
- let color = retentionChartColor(percentage);
- let style = {
+ const percentage = ((active / (total || 1)) * 100).toFixed(1);
+ const color = retentionChartColor(percentage);
+ const style = {
backgroundColor: color,
- borderColor: color
+ borderColor: color,
};
- let monthDayPretty = DateUtils.monthDayStringUTC(DateUtils.daysFrom(this.state.date, day - daysAgo));
+ const monthDayPretty = DateUtils.monthDayStringUTC(
+ DateUtils.daysFrom(this.state.date, day - daysAgo)
+ );
return (
-
- {active} of {total} users who signed up on {monthDayPretty} were still active on their {englishOrdinalIndicator(day)} day
+
+ {active} of {total} users who signed up on {monthDayPretty} were
+ still active on their {englishOrdinalIndicator(day)} day
+
+ }
+ >
+
+ {percentage}%
- )}>
- {percentage}%
);
@@ -111,11 +120,13 @@ export default class Retention extends DashboardView {
renderRetentionAverage(day) {
let total = 0;
let active = 0;
- RETENTION_DAYS.forEach((daysAgo) => {
+ RETENTION_DAYS.forEach(daysAgo => {
if (daysAgo < day) {
return;
}
- let dayData = this.state.retentions['days_old_' + daysAgo] && this.state.retentions['days_old_' + daysAgo]['day_' + day];
+ const dayData =
+ this.state.retentions['days_old_' + daysAgo] &&
+ this.state.retentions['days_old_' + daysAgo]['day_' + day];
// Somehow it's possible to miss some data. Probably a backend issue, but it's
// not easily reproducible locally.
if (dayData) {
@@ -123,12 +134,13 @@ export default class Retention extends DashboardView {
active += dayData.active;
}
});
- let percentage = (active / (total || 1) * 100).toFixed(1);
+ const percentage = ((active / (total || 1)) * 100).toFixed(1);
return (
+ style={{ textAlign: 'center' }}
+ >
{percentage}%
);
@@ -136,21 +148,21 @@ export default class Retention extends DashboardView {
renderDayAndTotalUser(daysAgo) {
// We can assume this.state.retentions has correct data here. Otherwise let it crash.
- let dayData = this.state.retentions['days_old_' + daysAgo]['day_' + daysAgo];
- let date = DateUtils.daysFrom(this.state.date, -daysAgo);
- let formattedDate = DateUtils.monthDayStringUTC(date);
- let formattedDateSplit = formattedDate.split(' ');
- let formattedDateMonth = formattedDateSplit[0];
- let formattedDateDay = formattedDateSplit[1];
+ const dayData = this.state.retentions['days_old_' + daysAgo]['day_' + daysAgo];
+ const date = DateUtils.daysFrom(this.state.date, -daysAgo);
+ const formattedDate = DateUtils.monthDayStringUTC(date);
+ const formattedDateSplit = formattedDate.split(' ');
+ const formattedDateMonth = formattedDateSplit[0];
+ const formattedDateDay = formattedDateSplit[1];
return (
-
- {(daysAgo === 28 || formattedDateDay === '1' ? formattedDateMonth : '')}
+
+ {daysAgo === 28 || formattedDateDay === '1' ? formattedDateMonth : ''}
{formattedDateDay}
- {(daysAgo === 28 || formattedDateDay === '1' ? 'Users ' : '')}
+ {daysAgo === 28 || formattedDateDay === '1' ? 'Users ' : ''}
{prettyNumber(dayData.total)}
@@ -158,12 +170,7 @@ export default class Retention extends DashboardView {
}
renderContent() {
- let toolbar = (
-
-
- );
+ const toolbar =
;
let chart = null;
let footer = null;
@@ -172,42 +179,44 @@ export default class Retention extends DashboardView {
chart = (
window.location = 'https://parse.com/apps/quickstart'} />
+ icon="analytics-outline"
+ description={
+ 'Once you start tracking user signups, we\'ll chart your user retention here.'
+ }
+ cta="Get started with Users"
+ action={() => (window.location = 'https://parse.com/apps/quickstart')}
+ />
);
} else {
chart = (
-
+
Still active after
- {RETENTION_DAYS.map((day) => (
+ {RETENTION_DAYS.map(day => (
+ style={{ textAlign: 'center' }}
+ >
{day}
))}
-
+
Average
- {RETENTION_DAYS.map((day) => (
- this.renderRetentionAverage(day)
- ))}
+ {RETENTION_DAYS.map(day => this.renderRetentionAverage(day))}
- {REVERSED_RETENTION_DAYS.map((daysAgo) => {
+ {REVERSED_RETENTION_DAYS.map(daysAgo => {
return (
- { daysAgo === 28 ? 'Signed up' : '' }
+ {daysAgo === 28 ? 'Signed up' : ''}
{this.renderDayAndTotalUser(daysAgo)}
- {RETENTION_DAYS.map((day) => {
+ {RETENTION_DAYS.map(day => {
// Only render until daysAgo
if (day > daysAgo) {
return null;
@@ -228,23 +237,23 @@ export default class Retention extends DashboardView {
(this.setState({ date: newValue, mutated: true }))} />
+ onChange={newValue => this.setState({ date: newValue, mutated: true })}
+ />
+ value="Refresh chart"
+ />
);
}
- let content = (
+ const content = (
-
- {chart}
-
+ {chart}
{footer}
);
diff --git a/src/dashboard/Analytics/SlowQueries/SlowQueries.react.js b/src/dashboard/Analytics/SlowQueries/SlowQueries.react.js
index 606c9e05e1..c7c16931a3 100644
--- a/src/dashboard/Analytics/SlowQueries/SlowQueries.react.js
+++ b/src/dashboard/Analytics/SlowQueries/SlowQueries.react.js
@@ -6,22 +6,31 @@
* the root directory of this source tree.
*/
import * as AnalyticsQueryStore from 'lib/stores/AnalyticsQueryStore';
-import * as SchemaStore from 'lib/stores/SchemaStore';
-import Button from 'components/Button/Button.react';
-import DateRange from 'components/DateRange/DateRange.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import FlowFooter from 'components/FlowFooter/FlowFooter.react';
-import Icon from 'components/Icon/Icon.react';
-import React from 'react';
-import SlowQueriesFilter from 'components/SlowQueriesFilter/SlowQueriesFilter.react';
-import styles from 'dashboard/Analytics/SlowQueries/SlowQueries.scss';
-import subscribeTo from 'lib/subscribeTo';
-import TableHeader from 'components/Table/TableHeader.react';
-import TableView from 'dashboard/TableView.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { Directions } from 'lib/Constants';
-
-const SLOW_QUERIES_HEADERS = ['Class', 'Normalized Query', 'Count', 'Slow%', 'Timeouts', 'Scanned (Avg)', 'Median (ms)', 'P90 (ms)'];
+import * as SchemaStore from 'lib/stores/SchemaStore';
+import Button from 'components/Button/Button.react';
+import DateRange from 'components/DateRange/DateRange.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import FlowFooter from 'components/FlowFooter/FlowFooter.react';
+import Icon from 'components/Icon/Icon.react';
+import React from 'react';
+import SlowQueriesFilter from 'components/SlowQueriesFilter/SlowQueriesFilter.react';
+import styles from 'dashboard/Analytics/SlowQueries/SlowQueries.scss';
+import subscribeTo from 'lib/subscribeTo';
+import TableHeader from 'components/Table/TableHeader.react';
+import TableView from 'dashboard/TableView.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { Directions } from 'lib/Constants';
+
+const SLOW_QUERIES_HEADERS = [
+ 'Class',
+ 'Normalized Query',
+ 'Count',
+ 'Slow%',
+ 'Timeouts',
+ 'Scanned (Avg)',
+ 'Median (ms)',
+ 'P90 (ms)',
+];
const TABLE_WIDTH = [15, 25, 7, 8, 10, 15, 11, 9];
const APP_VERSIONS_EXPLORER_QUERY = {
@@ -29,10 +38,10 @@ const APP_VERSIONS_EXPLORER_QUERY = {
limit: 1000,
source: 'API Event',
groups: ['OS', 'App Display Version'],
- localId: 'slow_query_app_version_query'
+ localId: 'slow_query_app_version_query',
};
-let formatQuery = (query) => {
+const formatQuery = query => {
return query;
};
@@ -45,22 +54,18 @@ class SlowQueries extends TableView {
this.section = 'Analytics';
this.subsection = 'Slow Queries';
- let date = new Date();
+ const date = new Date();
this.state = {
slowQueries: [],
loading: true,
mutated: false,
dateRange: {
- start: new Date(
- date.getFullYear(),
- date.getMonth(),
- date.getDate() - 1
- ),
- end: date
+ start: new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1),
+ end: date,
},
className: undefined,
os: undefined,
- version: undefined
+ version: undefined,
};
this.xhrHandles = [];
}
@@ -83,28 +88,39 @@ class SlowQueries extends TableView {
fetchDropdownData(props) {
props.schema.dispatch(SchemaStore.ActionTypes.FETCH);
- let payload = {
+ const payload = {
...APP_VERSIONS_EXPLORER_QUERY,
from: this.state.dateRange.start.getTime(),
- to: this.state.dateRange.end.getTime()
+ to: this.state.dateRange.end.getTime(),
};
if (window.DEVELOPMENT) {
payload.appID = 16155;
}
props.customQueries.dispatch(AnalyticsQueryStore.ActionTypes.FETCH, {
query: {
- ...payload
- }
+ ...payload,
+ },
});
}
fetchSlowQueries(app) {
- let { className, os, version, dateRange } = this.state;
+ const { className, os, version, dateRange } = this.state;
this.setState({ loading: true }, () => {
- let { promise, xhr } = app.getAnalyticsSlowQueries(className, os, version, dateRange.start, dateRange.end);
+ const { promise, xhr } = app.getAnalyticsSlowQueries(
+ className,
+ os,
+ version,
+ dateRange.start,
+ dateRange.end
+ );
promise.then(
- (result) => this.setState({ slowQueries: result || [], loading: false, mutated: false }),
+ result =>
+ this.setState({
+ slowQueries: result || [],
+ loading: false,
+ mutated: false,
+ }),
() => this.setState({ slowQueries: [], loading: false, mutated: false })
);
this.xhrHandles = [xhr];
@@ -121,14 +137,18 @@ class SlowQueries extends TableView {
renderToolbar() {
// Get app versions using Explorer endpoint
- let queries = this.props.customQueries.data.get('queries') || [];
- let appVersionExplorerQuery = queries.find((query) => query.localId === APP_VERSIONS_EXPLORER_QUERY.localId);
- let appVersions = {};
+ const queries = this.props.customQueries.data.get('queries') || [];
+ const appVersionExplorerQuery = queries.find(
+ query => query.localId === APP_VERSIONS_EXPLORER_QUERY.localId
+ );
+ const appVersions = {};
if (appVersionExplorerQuery && appVersionExplorerQuery.result) {
- appVersionExplorerQuery.result.forEach((value) => {
- let os = value['OS'];
- let version = value['App Display Version'];
- if (os === null || version === null) return;
+ appVersionExplorerQuery.result.forEach(value => {
+ const os = value['OS'];
+ const version = value['App Display Version'];
+ if (os === null || version === null) {
+ return;
+ }
if (Object.prototype.hasOwnProperty.call(appVersions, os)) {
appVersions[os].push(version);
} else {
@@ -144,7 +164,7 @@ class SlowQueries extends TableView {
// Get class names using Schema endpoint
let classOptions = ['Class'];
- let classList = this.props.schema.data.get('classes');
+ const classList = this.props.schema.data.get('classes');
if (classList && !classList.isEmpty()) {
classOptions = Object.keys(classList.toObject());
}
@@ -160,15 +180,19 @@ class SlowQueries extends TableView {
classNameOptions={classOptions}
osOptions={osOptions}
versionOptions={appVersions[this.state.os] || ['Version']}
- onChange={(newValue) => this.setState({
- ...newValue,
- mutated: true
- })} />
+ onChange={newValue =>
+ this.setState({
+ ...newValue,
+ mutated: true,
+ })
+ }
+ />
-
+ className={styles.toolbarAction}
+ >
+
Download
@@ -176,9 +200,7 @@ class SlowQueries extends TableView {
}
return (
-
+
{actions}
);
@@ -186,7 +208,9 @@ class SlowQueries extends TableView {
renderHeaders() {
return SLOW_QUERIES_HEADERS.map((header, index) => (
- {header}
+
+ {header}
+
));
}
@@ -198,7 +222,9 @@ class SlowQueries extends TableView {
return (
{TABLE_WIDTH.map((width, index) => (
- {index === 1 ? formatQuery(query[index]) : query[index]}
+
+ {index === 1 ? formatQuery(query[index]) : query[index]}
+
))}
);
@@ -207,34 +233,37 @@ class SlowQueries extends TableView {
renderEmpty() {
return (
window.location = 'http://docs.parseplatform.org/rest/guide/#queries'} />
+ icon="gears"
+ cta="Get started with Query"
+ action={() => (window.location = 'http://docs.parseplatform.org/rest/guide/#queries')}
+ />
);
}
renderExtras() {
return (
(this.setState({ dateRange: newValue, mutated: true }))}
- align={Directions.RIGHT} />
+ onChange={newValue => this.setState({ dateRange: newValue, mutated: true })}
+ align={Directions.RIGHT}
+ />
- )}
- primary={(
+ }
+ primary={
- )}
- />
+ value="Run query"
+ />
+ }
+ />
);
}
}
diff --git a/src/dashboard/AppData.react.js b/src/dashboard/AppData.react.js
index de33aa0703..1bc2c6de60 100644
--- a/src/dashboard/AppData.react.js
+++ b/src/dashboard/AppData.react.js
@@ -5,12 +5,11 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import AppSelector from 'dashboard/AppSelector.react';
-import AppsManager from 'lib/AppsManager';
+import React from 'react';
+import AppSelector from 'dashboard/AppSelector.react';
+import AppsManager from 'lib/AppsManager';
import { CurrentApp } from 'context/currentApp';
-import { Outlet, useNavigate , useParams} from 'react-router-dom';
-
+import { Outlet, useNavigate, useParams } from 'react-router-dom';
function AppData() {
const navigate = useNavigate();
@@ -21,7 +20,7 @@ function AppData() {
}
// Find by name to catch edge cases around escaping apostrophes in URLs
- let current = AppsManager.findAppBySlugOrName(params.appId);
+ const current = AppsManager.findAppBySlugOrName(params.appId);
if (current) {
current.setParseKeys();
diff --git a/src/dashboard/AppSelector.react.js b/src/dashboard/AppSelector.react.js
index aba0f84ca7..840cf6f8e0 100644
--- a/src/dashboard/AppSelector.react.js
+++ b/src/dashboard/AppSelector.react.js
@@ -6,27 +6,27 @@
* the root directory of this source tree.
*/
import AppsManager from 'lib/AppsManager';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import React from 'react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import React from 'react';
import { withRouter } from 'lib/withRouter';
@withRouter
class AppSelector extends React.Component {
constructor(props) {
super(props);
- let apps = AppsManager.apps();
- let latestApp = apps[apps.length - 1];
+ const apps = AppsManager.apps();
+ const latestApp = apps[apps.length - 1];
this.state = {
- slug: latestApp.slug
+ slug: latestApp.slug,
};
}
handleConfirm() {
- let newPath = this.location.pathname.replace(/\/_(\/|$)/, '/' + this.state.slug + '/');
+ const newPath = this.location.pathname.replace(/\/_(\/|$)/, '/' + this.state.slug + '/');
this.props.navigate(newPath);
}
@@ -35,26 +35,28 @@ class AppSelector extends React.Component {
}
render() {
- let apps = AppsManager.apps();
+ const apps = AppsManager.apps();
return (
+ onCancel={this.handleCancel}
+ >
}
+ label={ }
input={
- this.setState({ slug })}>
- {apps.map((app) => (
- {app.name}
+ this.setState({ slug })}>
+ {apps.map(app => (
+
+ {app.name}
+
))}
- } />
+ }
+ />
);
}
diff --git a/src/dashboard/Apps/AppsIndex.react.js b/src/dashboard/Apps/AppsIndex.react.js
index 4b1d5efbd5..17629e578e 100644
--- a/src/dashboard/Apps/AppsIndex.react.js
+++ b/src/dashboard/Apps/AppsIndex.react.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AppsManager from 'lib/AppsManager';
-import FlowFooter from 'components/FlowFooter/FlowFooter.react';
-import html from 'lib/htmlString';
-import Icon from 'components/Icon/Icon.react';
+import AppsManager from 'lib/AppsManager';
+import FlowFooter from 'components/FlowFooter/FlowFooter.react';
+import html from 'lib/htmlString';
+import Icon from 'components/Icon/Icon.react';
import joinWithFinal from 'lib/joinWithFinal';
-import LiveReload from 'components/LiveReload/LiveReload.react';
-import prettyNumber from 'lib/prettyNumber';
-import React from 'react';
-import styles from 'dashboard/Apps/AppsIndex.scss';
-import baseStyles from 'stylesheets/base.scss';
-import AppBadge from 'components/AppBadge/AppBadge.react';
+import LiveReload from 'components/LiveReload/LiveReload.react';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import styles from 'dashboard/Apps/AppsIndex.scss';
+import baseStyles from 'stylesheets/base.scss';
+import AppBadge from 'components/AppBadge/AppBadge.react';
import { withRouter } from 'lib/withRouter';
import { useNavigate } from 'react-router-dom';
@@ -29,30 +29,34 @@ function dash(value, content) {
return content;
}
/* eslint-disable no-unused-vars */
-let CloningNote = ({ app, clone_status, clone_progress }) => {
-/* eslint-enable */
+const CloningNote = ({ app, clone_status, clone_progress }) => {
+ /* eslint-enable */
if (clone_status === 'failed') {
//TODO: add a way to delete failed clones, like in old dash
- return Clone failed
+ return Clone failed
;
}
- let progress = {
- let currentAppProgress = data.find(({ appId }) => appId === app.applicationId);
- let progressStr = currentAppProgress ? currentAppProgress.progress.toString() : '0';
- return {progressStr} ;
- }}/>
- return Cloning is {progress}% complete
+ const progress = (
+ {
+ const currentAppProgress = data.find(({ appId }) => appId === app.applicationId);
+ const progressStr = currentAppProgress ? currentAppProgress.progress.toString() : '0';
+ return {progressStr} ;
+ }}
+ />
+ );
+ return Cloning is {progress}% complete
;
};
-let CountsSection = ({ className, title, children }) =>
-
+const CountsSection = ({ className, title, children }) => (
+
+);
-let Metric = (props) => {
+const Metric = props => {
return (
{props.number}
@@ -61,34 +65,45 @@ let Metric = (props) => {
);
};
-let AppCard = ({
- app,
- icon,
-}) => {
+const AppCard = ({ app, icon }) => {
const navigate = useNavigate();
- let canBrowse = app.serverInfo.error ? null : () => navigate(html`/apps/${app.slug}/browser`);
- let versionMessage = app.serverInfo.error ?
-
Server not reachable: {app.serverInfo.error.toString()}
:
+ const canBrowse = app.serverInfo.error ? null : () => navigate(html`/apps/${app.slug}/browser`);
+ const versionMessage = app.serverInfo.error ? (
- Server URL: {app.serverURL || 'unknown'}
- Server version: {app.serverInfo.parseServerVersion || 'unknown'}
-
;
-
- return
-
- {icon ? : }
-
-
-
{app.name}
- {versionMessage}
+ Server not reachable:
{app.serverInfo.error.toString()}
-
-
-
-
-
-
-}
+ ) : (
+
+ Server URL: {app.serverURL || 'unknown'}
+ Server version:{' '}
+ {app.serverInfo.parseServerVersion || 'unknown'}
+
+ );
+
+ return (
+
+
+ {icon ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ );
+};
@withRouter
class AppsIndex extends React.Component {
@@ -126,14 +141,14 @@ class AppsIndex extends React.Component {
}
render() {
- let search = this.state.search.toLowerCase();
- let apps = AppsManager.apps();
+ const search = this.state.search.toLowerCase();
+ const apps = AppsManager.apps();
if (apps.length === 0) {
return (
-
+
You don't have any apps
@@ -142,29 +157,36 @@ class AppsIndex extends React.Component {
}
let upgradePrompt = null;
if (this.props.newFeaturesInLatestVersion.length > 0) {
- let newFeaturesNodes = this.props.newFeaturesInLatestVersion.map(feature =>
- {feature}
- );
- upgradePrompt =
- Upgrade to the latest version of Parse Dashboard to get access to: {joinWithFinal('', newFeaturesNodes, ', ', ' and ')}.
-
+ const newFeaturesNodes = this.props.newFeaturesInLatestVersion.map(feature => (
+
{feature}
+ ));
+ upgradePrompt = (
+
+ Upgrade to the{' '}
+
+ latest version
+ {' '}
+ of Parse Dashboard to get access to: {joinWithFinal('', newFeaturesNodes, ', ', ' and ')}.
+
+ );
}
return (
-
+
+ placeholder="Start typing to filter…"
+ />
{apps.map(app =>
- app.name.toLowerCase().indexOf(search) > -1 ?
- :
- null
+ app.name.toLowerCase().indexOf(search) > -1 ? (
+
+ ) : null
)}
{upgradePrompt}
diff --git a/src/dashboard/Dashboard.js b/src/dashboard/Dashboard.js
index 516133dead..e30a1824f8 100644
--- a/src/dashboard/Dashboard.js
+++ b/src/dashboard/Dashboard.js
@@ -5,53 +5,54 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AccountOverview from './Account/AccountOverview.react';
-import AccountView from './AccountView.react';
-import AnalyticsOverview from './Analytics/Overview/Overview.react';
-import ApiConsole from './Data/ApiConsole/ApiConsole.react';
-import AppData from './AppData.react';
-import AppsIndex from './Apps/AppsIndex.react';
-import AppsManager from 'lib/AppsManager';
-import Browser from './Data/Browser/Browser.react';
-import CloudCode from './Data/CloudCode/CloudCode.react';
-import Config from './Data/Config/Config.react';
-import Explorer from './Analytics/Explorer/Explorer.react';
-import FourOhFour from 'components/FourOhFour/FourOhFour.react';
-import GeneralSettings from './Settings/GeneralSettings.react';
-import GraphQLConsole from './Data/ApiConsole/GraphQLConsole.react';
-import HostingSettings from './Settings/HostingSettings.react';
-import Icon from 'components/Icon/Icon.react';
-import JobEdit from 'dashboard/Data/Jobs/JobEdit.react';
-import Jobs from './Data/Jobs/Jobs.react';
-import JobsData from 'dashboard/Data/Jobs/JobsData.react';
-import Loader from 'components/Loader/Loader.react';
-import Logs from './Data/Logs/Logs.react';
-import Migration from './Data/Migration/Migration.react';
-import ParseApp from 'lib/ParseApp';
-import Performance from './Analytics/Performance/Performance.react';
+import AccountOverview from './Account/AccountOverview.react';
+import AccountView from './AccountView.react';
+import AnalyticsOverview from './Analytics/Overview/Overview.react';
+import ApiConsole from './Data/ApiConsole/ApiConsole.react';
+import AppData from './AppData.react';
+import AppsIndex from './Apps/AppsIndex.react';
+import AppsManager from 'lib/AppsManager';
+import Browser from './Data/Browser/Browser.react';
+import CloudCode from './Data/CloudCode/CloudCode.react';
+import Config from './Data/Config/Config.react';
+import Explorer from './Analytics/Explorer/Explorer.react';
+import FourOhFour from 'components/FourOhFour/FourOhFour.react';
+import GeneralSettings from './Settings/GeneralSettings.react';
+import GraphQLConsole from './Data/ApiConsole/GraphQLConsole.react';
+import HostingSettings from './Settings/HostingSettings.react';
+import Icon from 'components/Icon/Icon.react';
+import JobEdit from 'dashboard/Data/Jobs/JobEdit.react';
+import Jobs from './Data/Jobs/Jobs.react';
+import JobsData from 'dashboard/Data/Jobs/JobsData.react';
+import Loader from 'components/Loader/Loader.react';
+import Logs from './Data/Logs/Logs.react';
+import Migration from './Data/Migration/Migration.react';
+import ParseApp from 'lib/ParseApp';
+import Performance from './Analytics/Performance/Performance.react';
import PushAudiencesIndex from './Push/PushAudiencesIndex.react';
-import PushDetails from './Push/PushDetails.react';
-import PushIndex from './Push/PushIndex.react';
-import PushNew from './Push/PushNew.react';
-import PushSettings from './Settings/PushSettings.react';
-import React from 'react';
-import RestConsole from './Data/ApiConsole/RestConsole.react';
-import Retention from './Analytics/Retention/Retention.react';
-import SchemaOverview from './Data/Browser/SchemaOverview.react';
-import SecuritySettings from './Settings/SecuritySettings.react';
-import SettingsData from './Settings/SettingsData.react';
-import SlowQueries from './Analytics/SlowQueries/SlowQueries.react';
-import styles from 'dashboard/Apps/AppsIndex.scss';
-import UsersSettings from './Settings/UsersSettings.react';
-import Webhooks from './Data/Webhooks/Webhooks.react';
-import { AsyncStatus } from 'lib/Constants';
-import baseStyles from 'stylesheets/base.scss';
-import { get } from 'lib/AJAX';
-import { setBasePath } from 'lib/AJAX';
+import PushDetails from './Push/PushDetails.react';
+import PushIndex from './Push/PushIndex.react';
+import PushNew from './Push/PushNew.react';
+import PushSettings from './Settings/PushSettings.react';
+import React from 'react';
+import RestConsole from './Data/ApiConsole/RestConsole.react';
+import Retention from './Analytics/Retention/Retention.react';
+import SchemaOverview from './Data/Browser/SchemaOverview.react';
+import SecuritySettings from './Settings/SecuritySettings.react';
+import SettingsData from './Settings/SettingsData.react';
+import SlowQueries from './Analytics/SlowQueries/SlowQueries.react';
+import styles from 'dashboard/Apps/AppsIndex.scss';
+import UsersSettings from './Settings/UsersSettings.react';
+import Webhooks from './Data/Webhooks/Webhooks.react';
+import { AsyncStatus } from 'lib/Constants';
+import baseStyles from 'stylesheets/base.scss';
+import { get } from 'lib/AJAX';
+import { setBasePath } from 'lib/AJAX';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import Playground from './Data/Playground/Playground.react';
import DashboardSettings from './Settings/DashboardSettings/DashboardSettings.react';
+import Security from './Settings/Security/Security.react';
const ShowSchemaOverview = false; //In progress features. Change false to true to work on this feature.
@@ -62,10 +63,10 @@ class Empty extends React.Component {
}
const AccountSettingsPage = () => (
-
-
-
- );
+
+
+
+);
const PARSE_DOT_COM_SERVER_INFO = {
features: {
@@ -97,11 +98,11 @@ const PARSE_DOT_COM_SERVER_INFO = {
delete: true,
},
playground: {
- evalCode: true
- }
+ evalCode: true,
+ },
},
parseServerVersion: 'Parse.com',
-}
+};
export default class Dashboard extends React.Component {
constructor(props) {
@@ -117,90 +118,104 @@ export default class Dashboard extends React.Component {
}
componentDidMount() {
- get('/parse-dashboard-config.json').then(({ apps, newFeaturesInLatestVersion = [] }) => {
- this.setState({ newFeaturesInLatestVersion });
- let appInfoPromises = apps.map(app => {
- if (app.serverURL.startsWith('https://api.parse.com/1')) {
- //api.parse.com doesn't have feature availability endpoint, fortunately we know which features
- //it supports and can hard code them
- app.serverInfo = PARSE_DOT_COM_SERVER_INFO;
- return Promise.resolve(app);
- } else {
- app.serverInfo = {}
- return new ParseApp(app).apiRequest(
- 'GET',
- 'serverInfo',
- {},
- { useMasterKey: true }
- ).then(serverInfo => {
- app.serverInfo = serverInfo;
- return app;
- }, error => {
- if (error.code === 100) {
- app.serverInfo = {
- error: 'unable to connect to server',
- features: {},
- parseServerVersion: 'unknown'
- }
- return Promise.resolve(app);
- } else if (error.code === 107) {
- app.serverInfo = {
- error: 'server version too low',
- features: {},
- parseServerVersion: 'unknown'
- }
- return Promise.resolve(app);
- } else {
- app.serverInfo = {
- error: error.message || 'unknown error',
- features: {},
- parseServerVersion: 'unknown'
- }
- return Promise.resolve(app);
- }
+ get('/parse-dashboard-config.json')
+ .then(({ apps, newFeaturesInLatestVersion = [] }) => {
+ this.setState({ newFeaturesInLatestVersion });
+ const appInfoPromises = apps.map(app => {
+ if (app.serverURL.startsWith('https://api.parse.com/1')) {
+ //api.parse.com doesn't have feature availability endpoint, fortunately we know which features
+ //it supports and can hard code them
+ app.serverInfo = PARSE_DOT_COM_SERVER_INFO;
+ return Promise.resolve(app);
+ } else {
+ app.serverInfo = {};
+ return new ParseApp(app)
+ .apiRequest('GET', 'serverInfo', {}, { useMasterKey: true })
+ .then(
+ serverInfo => {
+ app.serverInfo = serverInfo;
+ return app;
+ },
+ error => {
+ if (error.code === 100) {
+ app.serverInfo = {
+ error: 'unable to connect to server',
+ features: {},
+ parseServerVersion: 'unknown',
+ };
+ return Promise.resolve(app);
+ } else if (error.code === 107) {
+ app.serverInfo = {
+ error: 'server version too low',
+ features: {},
+ parseServerVersion: 'unknown',
+ };
+ return Promise.resolve(app);
+ } else {
+ app.serverInfo = {
+ error: error.message || 'unknown error',
+ features: {},
+ parseServerVersion: 'unknown',
+ };
+ return Promise.resolve(app);
+ }
+ }
+ );
+ }
+ });
+ return Promise.all(appInfoPromises);
+ })
+ .then(
+ function (resolvedApps) {
+ resolvedApps.forEach(app => {
+ AppsManager.addApp(app);
});
- }
- });
- return Promise.all(appInfoPromises);
- }).then(function(resolvedApps) {
- resolvedApps.forEach(app => {
- AppsManager.addApp(app);
+ this.setState({ configLoadingState: AsyncStatus.SUCCESS });
+ }.bind(this)
+ )
+ .catch(({ error }) => {
+ this.setState({
+ configLoadingError: error,
+ configLoadingState: AsyncStatus.FAILED,
+ });
});
- this.setState({ configLoadingState: AsyncStatus.SUCCESS });
- }.bind(this)).catch(({ error }) => {
- this.setState({
- configLoadingError: error,
- configLoadingState: AsyncStatus.FAILED
- });
- });
}
render() {
if (this.state.configLoadingState === AsyncStatus.PROGRESS) {
- return
;
+ return (
+
+
+
+ );
}
if (this.state.configLoadingError && this.state.configLoadingError.length > 0) {
- return
-
-
-
+ return (
+
+
+
+
+
+ {/* use non-breaking hyphen for the error message to keep the filename on one line */}
+
+ {this.state.configLoadingError.replace(/-/g, '\u2011')}
+
- {/* use non-breaking hyphen for the error message to keep the filename on one line */}
-
{this.state.configLoadingError.replace(/-/g, '\u2011')}
-
+ );
}
const AppsIndexPage = () => (
-
-
+
+
);
const SettingsRoute = (
}>
} />
+ } />
} />
} />
} />
@@ -208,96 +223,86 @@ export default class Dashboard extends React.Component {
} />
} />
- )
+ );
const JobsRoute = (
}>
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
- )
+ );
const AnalyticsRoute = (
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- )
+ );
const BrowserRoute = ShowSchemaOverview ? SchemaOverview : Browser;
const ApiConsoleRoute = (
}>
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
- )
+ );
const AppRoute = (
}>
- } />
+ } />
- } />
+ } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
-
- {JobsRoute}
-
+ {JobsRoute}
- } />
- } />
+ } />
+ } />
- } />
+ } />
-
- {ApiConsoleRoute}
-
+ {ApiConsoleRoute}
- } />
+ } />
- } />
- } />
+ } />
+ } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+ } />
+ } />
{/* Unused routes... */}
-
- {AnalyticsRoute}
-
+ {AnalyticsRoute}
-
- {SettingsRoute}
-
+ {SettingsRoute}
- )
+ );
const Index = (
} />
-
- {AppRoute}
-
+ {AppRoute}
- )
+ );
return (
@@ -305,13 +310,11 @@ export default class Dashboard extends React.Component {
Parse Dashboard
-
- {Index}
-
- } />
- } />
- } />
- } />
+ {Index}
+ } />
+ } />
+ } />
+ } />
);
diff --git a/src/dashboard/DashboardView.react.js b/src/dashboard/DashboardView.react.js
index b224ac6778..00ceb39223 100644
--- a/src/dashboard/DashboardView.react.js
+++ b/src/dashboard/DashboardView.react.js
@@ -5,12 +5,12 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Sidebar from 'components/Sidebar/Sidebar.react';
-import styles from 'dashboard/Dashboard.scss';
-import Icon from 'components/Icon/Icon.react';
-import baseStyles from 'stylesheets/base.scss';
-import Button from 'components/Button/Button.react';
+import React from 'react';
+import Sidebar from 'components/Sidebar/Sidebar.react';
+import styles from 'dashboard/Dashboard.scss';
+import Icon from 'components/Icon/Icon.react';
+import baseStyles from 'stylesheets/base.scss';
+import Button from 'components/Button/Button.react';
import { CurrentApp } from 'context/currentApp';
export default class DashboardView extends React.Component {
@@ -44,7 +44,7 @@ export default class DashboardView extends React.Component {
if (typeof this.renderSidebar === 'function') {
sidebarChildren = this.renderSidebar();
}
- let appSlug = this.context ? this.context.slug : '';
+ const appSlug = this.context ? this.context.slug : '';
if (!this.context.hasCheckedForMigraton) {
this.context.getMigrations().promise.then(
@@ -53,9 +53,9 @@ export default class DashboardView extends React.Component {
);
}
- let features = this.context.serverInfo.features;
+ const features = this.context.serverInfo.features;
- let coreSubsections = [];
+ const coreSubsections = [];
if (
features.schemas &&
features.schemas.addField &&
@@ -97,10 +97,7 @@ export default class DashboardView extends React.Component {
});
}
- if (
- features.logs &&
- Object.keys(features.logs).some((key) => features.logs[key])
- ) {
+ if (features.logs && Object.keys(features.logs).some(key => features.logs[key])) {
coreSubsections.push({
name: 'Logs',
link: '/logs',
@@ -133,7 +130,7 @@ export default class DashboardView extends React.Component {
link: '/migration',
});
}
- let pushSubsections = [];
+ const pushSubsections = [];
if (features.push && features.push.immediatePush) {
pushSubsections.push({
@@ -156,7 +153,7 @@ export default class DashboardView extends React.Component {
});
}
- let analyticsSidebarSections = [];
+ const analyticsSidebarSections = [];
//These analytics pages may never make it into parse server
/*
@@ -203,6 +200,13 @@ export default class DashboardView extends React.Component {
link: '/settings/dashboard'
}];
+ if (this.context.enableSecurityChecks) {
+ settingsSections.push({
+ name: 'Security',
+ link: '/settings/security',
+ })
+ }
+
// Settings - nothing remotely like this in parse-server yet. Maybe it will arrive soon.
/*
if (features.generalSettings) {
@@ -240,7 +244,7 @@ export default class DashboardView extends React.Component {
});
}*/
- let appSidebarSections = [];
+ const appSidebarSections = [];
if (coreSubsections.length > 0) {
appSidebarSections.push({
@@ -279,7 +283,7 @@ export default class DashboardView extends React.Component {
});
}
- let sidebar = (
+ const sidebar = (
@@ -322,22 +321,12 @@ export default class DashboardView extends React.Component {
-
+
{this.context.serverInfo.error.replace(/-/g, '\u2011')}
-
location.reload()}
- />
+ location.reload()} />
);
diff --git a/src/dashboard/Data/ApiConsole/ApiConsole.react.js b/src/dashboard/Data/ApiConsole/ApiConsole.react.js
index 513e26c683..dda7c0dbfc 100644
--- a/src/dashboard/Data/ApiConsole/ApiConsole.react.js
+++ b/src/dashboard/Data/ApiConsole/ApiConsole.react.js
@@ -29,7 +29,7 @@ class ApiConsole extends DashboardView {
categories={[
{ name: 'REST Console', id: 'rest' },
{ name: 'GraphQL Console', id: 'graphql' },
- { name: 'JS Console', id: 'js_console' }
+ { name: 'JS Console', id: 'js_console' },
]}
/>
);
diff --git a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js
index 6a5a007193..90e575b922 100644
--- a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js
+++ b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js
@@ -21,39 +21,37 @@ export default class GraphQLConsole extends Component {
content = (
+ icon="info-solid"
+ />
);
} else {
const parseHeaders = {
'X-Parse-Application-Id': applicationId,
- 'X-Parse-Master-Key': masterKey
- }
+ 'X-Parse-Master-Key': masterKey,
+ };
if (clientKey) {
- parseHeaders['X-Parse-Client-Key'] = clientKey
+ parseHeaders['X-Parse-Client-Key'] = clientKey;
}
content = (
{
- const data = await fetch(
- graphQLServerURL,
- {
+ fetcher={async (graphQLParams, { headers }) => {
+ const data = await fetch(graphQLServerURL, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
- ...headers
+ ...headers,
},
body: JSON.stringify(graphQLParams),
- },
- );
- return data.json().catch(() => data.text());
+ });
+ return data.json().catch(() => data.text());
}}
/>
);
@@ -61,10 +59,8 @@ export default class GraphQLConsole extends Component {
return (
<>
-
-
- {content}
-
+
+ {content}
>
);
}
diff --git a/src/dashboard/Data/ApiConsole/RestConsole.react.js b/src/dashboard/Data/ApiConsole/RestConsole.react.js
index 20bb20019e..02d9304e36 100644
--- a/src/dashboard/Data/ApiConsole/RestConsole.react.js
+++ b/src/dashboard/Data/ApiConsole/RestConsole.react.js
@@ -5,31 +5,31 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import fieldStyle from 'components/Field/Field.scss';
-import FlowFooter from 'components/FlowFooter/FlowFooter.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import generateCurl from 'dashboard/Data/ApiConsole/generateCurl';
-import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import Parse from 'parse';
+import Button from 'components/Button/Button.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import fieldStyle from 'components/Field/Field.scss';
+import FlowFooter from 'components/FlowFooter/FlowFooter.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import generateCurl from 'dashboard/Data/ApiConsole/generateCurl';
+import JsonPrinter from 'components/JsonPrinter/JsonPrinter.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import Parse from 'parse';
import React, { Component } from 'react';
-import request from 'dashboard/Data/ApiConsole/request';
-import styles from 'dashboard/Data/ApiConsole/ApiConsole.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import request from 'dashboard/Data/ApiConsole/request';
+import styles from 'dashboard/Data/ApiConsole/ApiConsole.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
import { CurrentApp } from 'context/currentApp';
export default class RestConsole extends Component {
static contextType = CurrentApp;
constructor() {
- super()
+ super();
this.state = {
method: 'GET',
@@ -38,7 +38,7 @@ export default class RestConsole extends Component {
runAsIdentifier: '',
sessionToken: null,
parameters: '',
- response: {results:[]},
+ response: { results: [] },
fetchingUser: false,
inProgress: false,
error: false,
@@ -52,50 +52,77 @@ export default class RestConsole extends Component {
return;
}
Parse.Query.or(
- new Parse.Query(Parse.User).equalTo('username', this.state.runAsIdentifier ),
- new Parse.Query(Parse.User).equalTo('objectId', this.state.runAsIdentifier )
- ).first({ useMasterKey: true }).then((found) => {
- if (found) {
- if (found.getSessionToken()) {
- this.setState({ sessionToken: found.getSessionToken(), error: false, fetchingUser: false });
- } else {
- // Check the Sessions table
- new Parse.Query(Parse.Session).equalTo('user', found).first({ useMasterKey: true }).then((session) => {
- if (session) {
- this.setState({ sessionToken: session.getSessionToken(), error: false, fetchingUser: false });
+ new Parse.Query(Parse.User).equalTo('username', this.state.runAsIdentifier),
+ new Parse.Query(Parse.User).equalTo('objectId', this.state.runAsIdentifier)
+ )
+ .first({ useMasterKey: true })
+ .then(
+ found => {
+ if (found) {
+ if (found.getSessionToken()) {
+ this.setState({
+ sessionToken: found.getSessionToken(),
+ error: false,
+ fetchingUser: false,
+ });
} else {
- this.setState({ error: 'Unable to find any active sessions for that user.', fetchingUser: false });
+ // Check the Sessions table
+ new Parse.Query(Parse.Session)
+ .equalTo('user', found)
+ .first({ useMasterKey: true })
+ .then(
+ session => {
+ if (session) {
+ this.setState({
+ sessionToken: session.getSessionToken(),
+ error: false,
+ fetchingUser: false,
+ });
+ } else {
+ this.setState({
+ error: 'Unable to find any active sessions for that user.',
+ fetchingUser: false,
+ });
+ }
+ },
+ () => {
+ this.setState({
+ error: 'Unable to find any active sessions for that user.',
+ fetchingUser: false,
+ });
+ }
+ );
}
- }, () => {
- this.setState({ error: 'Unable to find any active sessions for that user.', fetchingUser: false });
+ } else {
+ this.setState({
+ error: 'Unable to find that user.',
+ fetchingUser: false,
+ });
+ }
+ },
+ () => {
+ this.setState({
+ error: 'Unable to find that user.',
+ fetchingUser: false,
});
}
- } else {
- this.setState({ error: 'Unable to find that user.', fetchingUser: false });
- }
- }, () => {
- this.setState({ error: 'Unable to find that user.', fetchingUser: false });
- });
+ );
this.setState({ fetchingUser: true });
}
makeRequest() {
- let endpoint = this.state.endpoint + (this.state.method === 'GET' ? `?${this.state.parameters}` : '');
- let payload = (this.state.method === 'DELETE' || this.state.method === 'GET') ? null : this.state.parameters;
- let options = {};
+ const endpoint =
+ this.state.endpoint + (this.state.method === 'GET' ? `?${this.state.parameters}` : '');
+ const payload =
+ this.state.method === 'DELETE' || this.state.method === 'GET' ? null : this.state.parameters;
+ const options = {};
if (this.state.useMasterKey) {
options.useMasterKey = true;
}
if (this.state.sessionToken) {
options.sessionToken = this.state.sessionToken;
}
- request(
- this.context,
- this.state.method,
- endpoint,
- payload,
- options
- ).then((response) => {
+ request(this.context, this.state.method, endpoint, payload, options).then(response => {
this.setState({ response });
document.body.scrollTop = 540;
});
@@ -106,17 +133,19 @@ export default class RestConsole extends Component {
}
render() {
- const methodDropdown =
- this.setState({method})} value={this.state.method}>
- GET
- POST
- PUT
- DELETE
+ const methodDropdown = (
+ this.setState({ method })} value={this.state.method}>
+ GET
+ POST
+ PUT
+ DELETE
+ );
- let hasError = this.state.fetchingUser ||
- this.state.endpoint.length === 0 ||
- (this.state.runAsIdentifier.length > 0 && !this.state.sessionToken);
+ const hasError =
+ this.state.fetchingUser ||
+ this.state.endpoint.length === 0 ||
+ (this.state.runAsIdentifier.length > 0 && !this.state.sessionToken);
let parameterPlaceholder = 'where={"username":"johndoe"}';
if (this.state.method === 'POST' || this.state.method === 'PUT') {
parameterPlaceholder = '{"name":"John"}';
@@ -124,15 +153,15 @@ export default class RestConsole extends Component {
let modal = null;
if (this.state.curlModal) {
- let payload = this.state.method === 'DELETE' ? null : this.state.parameters;
- let options = {};
+ const payload = this.state.method === 'DELETE' ? null : this.state.parameters;
+ const options = {};
if (this.state.useMasterKey) {
options.useMasterKey = true;
}
if (this.state.sessionToken) {
options.sessionToken = this.state.sessionToken;
}
- let content = generateCurl(
+ const content = generateCurl(
this.context,
this.state.method,
this.state.endpoint,
@@ -141,14 +170,19 @@ export default class RestConsole extends Component {
);
modal = (
- this.setState({ curlModal: false })} />
+ this.setState({ curlModal: false })}
+ />
- }>
+ }
+ >
{content}
);
@@ -157,36 +191,107 @@ export default class RestConsole extends Component {
return (
+ legend="Send a test query"
+ description="Try out some queries, and take a look at what they return."
+ >
+ } input={methodDropdown} />
}
- input={methodDropdown} />
- Not sure what endpoint you need? Take a look at our REST API guide .} />}
- input={ this.setState({endpoint})} />} />
+ label={
+
+ Not sure what endpoint you need?
+
+ Take a look at our{' '}
+ REST API guide .
+
+ }
+ />
+ }
+ input={
+ this.setState({ endpoint })}
+ />
+ }
+ />
}
- input={ this.setState({ useMasterKey })} />} />
+ label={ }
+ input={
+ this.setState({ useMasterKey })}
+ />
+ }
+ />
}
- input={ this.setState({runAsIdentifier})} onBlur={this.fetchUser.bind(this)} />} />
- {this.state.error}
+ label={
+
+ }
+ input={
+ this.setState({ runAsIdentifier })}
+ onBlur={this.fetchUser.bind(this)}
+ />
+ }
+ />
+
+ {this.state.error}
+
Learn more about query parameters in our REST API guide .} />}
- input={ this.setState({parameters})} />} />
+ label={
+
+ Learn more about query parameters in our{' '}
+ REST API guide .
+
+ }
+ />
+ }
+ input={
+ this.setState({ parameters })}
+ />
+ }
+ />
-
+
-
+
}
- secondary={ } />
+ primary={
+
+ }
+ secondary={
+
+ }
+ />
{modal}
);
diff --git a/src/dashboard/Data/ApiConsole/generateCurl.js b/src/dashboard/Data/ApiConsole/generateCurl.js
index fbce5620b9..5100945656 100644
--- a/src/dashboard/Data/ApiConsole/generateCurl.js
+++ b/src/dashboard/Data/ApiConsole/generateCurl.js
@@ -10,16 +10,16 @@
* Used to escape the POST body and GET parameters
* when exporting a request to cURL
*/
-let escapeValueForCURL = (value) => {
+const escapeValueForCURL = value => {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
-}
+};
export default function generateCurl(app, method, path, body, options) {
if (path[0] === '/') {
path = path.substr(1);
}
-
- let headers = [[`-H "X-Parse-Application-Id: ${app.applicationId}" \\`]];
+
+ const headers = [[`-H "X-Parse-Application-Id: ${app.applicationId}" \\`]];
if (options.useMasterKey) {
headers.push([`-H "X-Parse-Master-Key: ${app.masterKey}" \\`]);
} else {
@@ -29,9 +29,9 @@ export default function generateCurl(app, method, path, body, options) {
headers.push([`-H "X-Parse-Session-Token: ${options.sessionToken}" \\`]);
}
- let _body = escapeValueForCURL(body);
+ const _body = escapeValueForCURL(body);
- let request = 'curl -X ' + method +' \\\n' + headers.join('\n') + '\n';
+ let request = 'curl -X ' + method + ' \\\n' + headers.join('\n') + '\n';
if (_body && _body.length) {
if (method === 'GET') {
request += '-G \\\n';
diff --git a/src/dashboard/Data/ApiConsole/request.js b/src/dashboard/Data/ApiConsole/request.js
index 06d67b770a..85bdc1b89f 100644
--- a/src/dashboard/Data/ApiConsole/request.js
+++ b/src/dashboard/Data/ApiConsole/request.js
@@ -6,7 +6,7 @@
* the root directory of this source tree.
*/
export default function request(app, method, path, body, options) {
- let xhr = new XMLHttpRequest();
+ const xhr = new XMLHttpRequest();
if (path.startsWith('/') && app.serverURL.endsWith('/')) {
path = path.substr(1);
}
@@ -23,14 +23,16 @@ export default function request(app, method, path, body, options) {
if (options.sessionToken) {
xhr.setRequestHeader('X-Parse-Session-Token', options.sessionToken);
}
- return new Promise((resolve) => {
- xhr.onload = function() {
+ return new Promise(resolve => {
+ xhr.onload = function () {
let response = xhr.responseText;
try {
response = JSON.parse(response);
- } catch (e) {/**/}
+ } catch (e) {
+ /**/
+ }
resolve(response);
- }
+ };
xhr.send(body);
});
}
diff --git a/src/dashboard/Data/Browser/AddColumnDialog.react.js b/src/dashboard/Data/Browser/AddColumnDialog.react.js
index 82af7696d8..295f2cdecb 100644
--- a/src/dashboard/Data/Browser/AddColumnDialog.react.js
+++ b/src/dashboard/Data/Browser/AddColumnDialog.react.js
@@ -6,22 +6,22 @@
* the root directory of this source tree.
*/
-import Parse from 'parse'
-import React from 'react';
-import semver from 'semver/preload.js';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import SegmentSelect from 'components/SegmentSelect/SegmentSelect.react';
-import FileInput from 'components/FileInput/FileInput.react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import validateNumeric from 'lib/validateNumeric';
-import { DataTypes } from 'lib/Constants';
+import Parse from 'parse';
+import React from 'react';
+import semver from 'semver/preload.js';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
+import SegmentSelect from 'components/SegmentSelect/SegmentSelect.react';
+import FileInput from 'components/FileInput/FileInput.react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import validateNumeric from 'lib/validateNumeric';
+import { DataTypes } from 'lib/Constants';
function validColumnName(name) {
return !!name.match(/^[a-zA-Z][_a-zA-Z0-9]*$/);
@@ -37,14 +37,14 @@ export default class AddColumnDialog extends React.Component {
required: false,
defaultValue: undefined,
isDefaultValueValid: true,
- uploadingFile: false
+ uploadingFile: false,
};
- this.renderDefaultValueInput = this.renderDefaultValueInput.bind(this)
- this.handleDefaultValueChange = this.handleDefaultValueChange.bind(this)
+ this.renderDefaultValueInput = this.renderDefaultValueInput.bind(this);
+ this.handleDefaultValueChange = this.handleDefaultValueChange.bind(this);
}
valid() {
- const { name, isDefaultValueValid } = this.state
+ const { name, isDefaultValueValid } = this.state;
return (
name &&
@@ -52,14 +52,14 @@ export default class AddColumnDialog extends React.Component {
validColumnName(this.state.name) &&
this.props.currentColumns.indexOf(this.state.name) === -1 &&
isDefaultValueValid
- )
+ );
}
async handlePointer(objectId, target) {
- const targetClass = new Parse.Object.extend(target)
- const query = new Parse.Query(targetClass)
- const result = await query.get(objectId)
- return result.toPointer()
+ const targetClass = new Parse.Object.extend(target);
+ const query = new Parse.Query(targetClass);
+ const result = await query.get(objectId);
+ return result.toPointer();
}
getBase64(file) {
@@ -73,10 +73,10 @@ export default class AddColumnDialog extends React.Component {
async handleFile(file) {
if (file) {
- let base64 = await this.getBase64(file);
+ const base64 = await this.getBase64(file);
const parseFile = new Parse.File(file.name, { base64 });
this.setState({
- uploadingFile: true
+ uploadingFile: true,
});
try {
await parseFile.save({ useMasterKey: true });
@@ -86,7 +86,7 @@ export default class AddColumnDialog extends React.Component {
return parseFile;
} finally {
this.setState({
- uploadingFile: false
+ uploadingFile: false,
});
}
}
@@ -94,114 +94,165 @@ export default class AddColumnDialog extends React.Component {
renderClassDropdown() {
return (
-
this.setState({ target: target })}>
- {this.props.classes.sort((a, b) => a.localeCompare(b)).map((c) => {c} )}
+ this.setState({ target: target })}>
+ {this.props.classes
+ .sort((a, b) => a.localeCompare(b))
+ .map(c => (
+
+ {c}
+
+ ))}
);
}
async handleDefaultValueChange(defaultValue) {
- const { type, target } = this.state
- let formattedValue = undefined
- let isDefaultValueValid = true
+ const { type, target } = this.state;
+ let formattedValue = undefined;
+ let isDefaultValueValid = true;
try {
switch (type) {
case 'String':
- formattedValue = defaultValue.toString()
- break
+ formattedValue = defaultValue.toString();
+ break;
case 'Number':
- if (!validateNumeric(defaultValue)) throw 'Invalid number'
- formattedValue = +defaultValue
- break
+ if (!validateNumeric(defaultValue)) {
+ throw 'Invalid number';
+ }
+ formattedValue = +defaultValue;
+ break;
case 'Array':
- if (!Array.isArray(JSON.parse(defaultValue))) throw 'Invalid array'
- formattedValue = JSON.parse(defaultValue)
- break
+ if (!Array.isArray(JSON.parse(defaultValue))) {
+ throw 'Invalid array';
+ }
+ formattedValue = JSON.parse(defaultValue);
+ break;
case 'Object':
- if (typeof JSON.parse(defaultValue) !== 'object' || Array.isArray(JSON.parse(defaultValue))) throw 'Invalid object'
- formattedValue = JSON.parse(defaultValue)
- break
+ if (
+ typeof JSON.parse(defaultValue) !== 'object' ||
+ Array.isArray(JSON.parse(defaultValue))
+ ) {
+ throw 'Invalid object';
+ }
+ formattedValue = JSON.parse(defaultValue);
+ break;
case 'Date':
- formattedValue = { __type: 'Date', iso: new Date(defaultValue) }
- break
+ formattedValue = { __type: 'Date', iso: new Date(defaultValue) };
+ break;
case 'Polygon':
- formattedValue = new Parse.Polygon(JSON.parse(defaultValue))
- break
+ formattedValue = new Parse.Polygon(JSON.parse(defaultValue));
+ break;
case 'GeoPoint':
- formattedValue = new Parse.GeoPoint(JSON.parse(defaultValue))
+ formattedValue = new Parse.GeoPoint(JSON.parse(defaultValue));
break;
case 'Pointer':
- formattedValue = await this.handlePointer(defaultValue, target)
- break
+ formattedValue = await this.handlePointer(defaultValue, target);
+ break;
case 'Boolean':
- formattedValue = (defaultValue === 'True' ? true : (defaultValue === 'False' ? false : undefined))
- break
+ formattedValue =
+ defaultValue === 'True' ? true : defaultValue === 'False' ? false : undefined;
+ break;
case 'File':
- formattedValue = await this.handleFile(defaultValue)
- break
+ formattedValue = await this.handleFile(defaultValue);
+ break;
}
} catch (e) {
- isDefaultValueValid = defaultValue === ''
+ isDefaultValueValid = defaultValue === '';
}
- return this.setState({ defaultValue: formattedValue, isDefaultValueValid })
+ return this.setState({ defaultValue: formattedValue, isDefaultValueValid });
}
renderDefaultValueInput() {
- const { type } = this.state
+ const { type } = this.state;
switch (type) {
case 'Array':
case 'Object':
case 'Polygon':
case 'GeoPoint':
- return await this.handleDefaultValueChange(defaultValue)} />
+ return (
+ await this.handleDefaultValueChange(defaultValue)}
+ />
+ );
case 'Number':
case 'String':
case 'Pointer':
- return await this.handleDefaultValueChange(defaultValue)} />
+ return (
+ await this.handleDefaultValueChange(defaultValue)}
+ />
+ );
case 'Date':
- return await this.handleDefaultValueChange(defaultValue)} />
+ return (
+ await this.handleDefaultValueChange(defaultValue)}
+ />
+ );
case 'Boolean':
- return await this.handleDefaultValueChange(defaultValue)} />
+ return (
+ await this.handleDefaultValueChange(defaultValue)}
+ />
+ );
case 'File':
- return await this.handleDefaultValueChange(defaultValue)} />
+ return (
+ await this.handleDefaultValueChange(defaultValue)}
+ />
+ );
}
}
-
render() {
- let typeDropdown = (
+ const typeDropdown = (
this.setState({ type: type, defaultValue: undefined, required: false })}>
- {DataTypes.sort((a, b) => a.localeCompare(b)).map((t) => {t} )}
+ onChange={type =>
+ this.setState({
+ type: type,
+ defaultValue: undefined,
+ required: false,
+ })
+ }
+ >
+ {DataTypes.sort((a, b) => a.localeCompare(b)).map(t => (
+
+ {t}
+
+ ))}
);
return (
{
this.props.onConfirm(this.state);
- }}>
+ }}
+ >
+ }
+ input={typeDropdown}
+ />
+ {this.state.type === 'Pointer' || this.state.type === 'Relation' ? (
+ } input={this.renderClassDropdown()} />
+ ) : null}
+ text="What should we call it?"
+ description={
+ 'Don\u2019t use any special characters, and start your name with a letter.'
+ }
+ />
}
- input={typeDropdown} />
- {this.state.type === 'Pointer' || this.state.type === 'Relation' ?
- }
- input={this.renderClassDropdown()} /> : null}
- }
- input={ this.setState({ name })} />} />
+ input={
+ this.setState({ name })}
+ />
+ }
+ />
{
/*
Allow include require fields and default values if the parse-server
@@ -232,18 +295,39 @@ export default class AddColumnDialog extends React.Component {
*/
semver.valid(this.props.parseServerVersion) &&
semver.gte(this.props.parseServerVersion, '3.7.0') &&
- this.state.type !== 'Relation' ?
- <>
- }
- input={this.renderDefaultValueInput()}
- className={styles.addColumnToggleWrapper} />
- }
- input={ this.setState({ required })} additionalStyles={{ margin: '0px' }} />}
- className={styles.addColumnToggleWrapper} />
- >
- : null
+ this.state.type !== 'Relation' ? (
+ <>
+
+ }
+ input={this.renderDefaultValueInput()}
+ className={styles.addColumnToggleWrapper}
+ />
+
+ }
+ input={
+ this.setState({ required })}
+ additionalStyles={{ margin: '0px' }}
+ />
+ }
+ className={styles.addColumnToggleWrapper}
+ />
+ >
+ ) : null
}
);
diff --git a/src/dashboard/Data/Browser/AttachRowsDialog.react.js b/src/dashboard/Data/Browser/AttachRowsDialog.react.js
index 79a850c520..ece195cd27 100644
--- a/src/dashboard/Data/Browser/AttachRowsDialog.react.js
+++ b/src/dashboard/Data/Browser/AttachRowsDialog.react.js
@@ -23,16 +23,16 @@ export default class AttachRowsDialog extends React.Component {
handleConfirm() {
const objectIds = this.state.objectIds.split(',').reduce((resourceIds, targetResourceId) => {
const objectId = targetResourceId && targetResourceId.trim();
- if (!objectId) return;
+ if (!objectId) {
+ return;
+ }
return [...resourceIds, objectId];
}, []);
return this.props.onConfirm(objectIds);
}
render() {
- const {
- relation
- } = this.props;
+ const { relation } = this.props;
return (
- }
- input={
-
- {this.state.touchableColumns.map(column => (
-
- {column}
-
- ))}
-
- }
- />
- );
- }
- if (this.state.currentColumn) {
- targetEntityIdInsert = (
-
- }
- input={
-
- }
- />
- );
- }
+ if (this.state.touchableColumns.length) {
+ targetRelationSelector = (
+ }
+ input={
+
+ {this.state.touchableColumns.map(column => (
+
+ {column}
+
+ ))}
+
+ }
+ />
+ );
+ }
+ if (this.state.currentColumn) {
+ targetEntityIdInsert = (
+
+ }
+ input={
+
+ }
+ />
+ );
+ }
return (
- }
+ label={ }
input={
-
+
{classes.map(className => (
{className}
diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js
index 09bacbca65..ec4897117c 100644
--- a/src/dashboard/Data/Browser/Browser.react.js
+++ b/src/dashboard/Data/Browser/Browser.react.js
@@ -42,6 +42,7 @@ import { withRouter } from 'lib/withRouter';
// The initial and max amount of rows fetched by lazy loading
const MAX_ROWS_FETCHED = 200;
+const BROWSER_LAST_LOCATION = 'brower_last_location';
@subscribeTo('Schema', 'schema')
@withRouter
@@ -178,8 +179,36 @@ class Browser extends DashboardView {
}
}
+ componentDidMount() {
+ if (window.localStorage) {
+ const pathname = window.localStorage.getItem(BROWSER_LAST_LOCATION);
+ window.localStorage.removeItem(BROWSER_LAST_LOCATION);
+ if (pathname) {
+ setTimeout(
+ function () {
+ this.props.navigate(pathname);
+ }.bind(this)
+ );
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ if (window.localStorage) {
+ window.localStorage.setItem(
+ BROWSER_LAST_LOCATION,
+ this.props.location.pathname + this.props.location.search
+ );
+ }
+ }
+
componentWillReceiveProps(nextProps, nextContext) {
- if (this.props.params.appId !== nextProps.params.appId || this.props.params.className !== nextProps.params.className || this.props.location.search !== nextProps.location.search || this.props.params?.relationName !== nextProps.params?.relationName) {
+ if (
+ this.props.params.appId !== nextProps.params.appId ||
+ this.props.params.className !== nextProps.params.className ||
+ this.props.location.search !== nextProps.location.search ||
+ this.props.params?.relationName !== nextProps.params?.relationName
+ ) {
if (this.props.params.appId !== nextProps.params.appId || !this.props.params.className) {
this.setState({ counts: {} });
Parse.Object._clearAllState();
@@ -232,14 +261,14 @@ class Browser extends DashboardView {
const query = new URLSearchParams(props.location.search);
if (query.has('filters')) {
const queryFilters = JSON.parse(query.get('filters'));
- queryFilters.forEach((filter) => (filters = filters.push(new Map(filter))));
+ queryFilters.forEach(filter => (filters = filters.push(new Map(filter))));
}
return filters;
}
redirectToFirstClass(classList, context) {
if (!classList.isEmpty()) {
- let classes = Object.keys(classList.toObject());
+ const classes = Object.keys(classList.toObject());
classes.sort((a, b) => {
if (a[0] === '_' && b[0] !== '_') {
return -1;
@@ -249,7 +278,9 @@ class Browser extends DashboardView {
}
return a.toUpperCase() < b.toUpperCase() ? -1 : 1;
});
- this.props.navigate(generatePath(context || this.context, 'browser/' + classes[0]), { replace: true });
+ this.props.navigate(generatePath(context || this.context, 'browser/' + classes[0]), {
+ replace: true,
+ });
}
}
@@ -318,7 +349,7 @@ class Browser extends DashboardView {
delete this.state.counts[className];
this.props.navigate(generatePath(this.context, 'browser'));
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -364,19 +395,19 @@ class Browser extends DashboardView {
.dispatch(ActionTypes.ADD_COLUMN, payload)
.then(() => {
if (required) {
- let requiredCols = [...this.state.requiredColumnFields, name];
+ const requiredCols = [...this.state.requiredColumnFields, name];
this.setState({
requiredColumnFields: requiredCols,
});
}
})
- .catch((err) => {
+ .catch(err => {
this.showNote(err.message, true);
});
}
addColumn({ type, name, target, required, defaultValue }) {
- let payload = {
+ const payload = {
className: this.props.params.className,
columnType: type,
name: name,
@@ -390,7 +421,7 @@ class Browser extends DashboardView {
}
addColumnAndContinue({ type, name, target, required, defaultValue }) {
- let payload = {
+ const payload = {
className: this.props.params.className,
columnType: type,
name: name,
@@ -408,7 +439,9 @@ class Browser extends DashboardView {
if (!this.state.newObject) {
const relation = this.state.relation;
this.setState({
- newObject: relation ? new Parse.Object(relation.targetClassName) : new Parse.Object(this.props.params.className),
+ newObject: relation
+ ? new Parse.Object(relation.targetClassName)
+ : new Parse.Object(this.props.params.className),
});
}
}
@@ -435,9 +468,9 @@ class Browser extends DashboardView {
// check if required fields are missing
const className = this.state.newObject.className;
- let requiredCols = [];
+ const requiredCols = [];
if (className) {
- let classColumns = this.props.schema.data.get('classes').get(className);
+ const classColumns = this.props.schema.data.get('classes').get(className);
classColumns.forEach(({ required }, name) => {
if (name === 'objectId' || (this.state.isUnique && name !== this.state.uniqueField)) {
return;
@@ -473,8 +506,8 @@ class Browser extends DashboardView {
});
}
obj.save(null, { useMasterKey }).then(
- (objectSaved) => {
- let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' created';
+ objectSaved => {
+ const msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' created';
this.showNote(msg, false);
const state = { data: this.state.data };
@@ -496,7 +529,7 @@ class Browser extends DashboardView {
},
});
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -516,7 +549,7 @@ class Browser extends DashboardView {
this.setState(state);
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -537,9 +570,9 @@ class Browser extends DashboardView {
// check if required fields are missing
const className = this.props.params.className;
- let requiredCols = [];
+ const requiredCols = [];
if (className) {
- let classColumns = this.props.schema.data.get('classes').get(className);
+ const classColumns = this.props.schema.data.get('classes').get(className);
classColumns.forEach(({ required }, name) => {
if (name === 'objectId' || (this.state.isUnique && name !== this.state.uniqueField)) {
return;
@@ -576,23 +609,27 @@ class Browser extends DashboardView {
}
obj.save(null, { useMasterKey: true }).then(
- (objectSaved) => {
- let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' ' + 'created';
+ objectSaved => {
+ const msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' ' + 'created';
this.showNote(msg, false);
const state = {
data: this.state.data,
editCloneRows: this.state.editCloneRows,
};
- state.editCloneRows = state.editCloneRows.filter((cloneObj) => cloneObj._localId !== obj._localId);
- if (state.editCloneRows.length === 0) state.editCloneRows = null;
+ state.editCloneRows = state.editCloneRows.filter(
+ cloneObj => cloneObj._localId !== obj._localId
+ );
+ if (state.editCloneRows.length === 0) {
+ state.editCloneRows = null;
+ }
if (this.props.params.className === obj.className) {
this.state.data.unshift(obj);
}
this.state.counts[obj.className] += 1;
this.setState(state);
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -612,8 +649,12 @@ class Browser extends DashboardView {
return;
}
const state = { editCloneRows: this.state.editCloneRows };
- state.editCloneRows = state.editCloneRows.filter((cloneObj) => cloneObj._localId !== obj._localId);
- if (state.editCloneRows.length === 0) state.editCloneRows = null;
+ state.editCloneRows = state.editCloneRows.filter(
+ cloneObj => cloneObj._localId !== obj._localId
+ );
+ if (state.editCloneRows.length === 0) {
+ state.editCloneRows = null;
+ }
this.setState(state);
}
@@ -636,12 +677,12 @@ class Browser extends DashboardView {
}
removeColumn(name) {
- let payload = {
+ const payload = {
className: this.props.params.className,
name: name,
};
this.props.schema.dispatch(ActionTypes.DROP_COLUMN, payload).finally(() => {
- let state = { showRemoveColumnDialog: false };
+ const state = { showRemoveColumnDialog: false };
if (this.state.ordering === name || this.state.ordering === '-' + name) {
state.ordering = '-createdAt';
}
@@ -706,7 +747,7 @@ class Browser extends DashboardView {
let promise = query.find({ useMasterKey });
let isUnique = false;
let uniqueField = null;
- filters.forEach(async (filter) => {
+ filters.forEach(async filter => {
if (filter.get('constraint') == 'unique') {
const field = filter.get('field');
promise = query.distinct(field);
@@ -723,8 +764,8 @@ class Browser extends DashboardView {
excludeFields(query, className) {
let columns = ColumnPreferences.getPreferences(this.context.applicationId, className);
if (columns) {
- columns = columns.filter((clmn) => !clmn.visible).map((clmn) => clmn.name);
- for (let columnsKey in columns) {
+ columns = columns.filter(clmn => !clmn.visible).map(clmn => clmn.name);
+ for (const columnsKey in columns) {
query.exclude(columns[columnsKey]);
}
ColumnPreferences.updateCachedColumns(this.context.applicationId, className);
@@ -740,7 +781,7 @@ class Browser extends DashboardView {
async fetchData(source, filters = new List()) {
const data = await this.fetchParseData(source, filters);
- var filteredCounts = { ...this.state.filteredCounts };
+ const filteredCounts = { ...this.state.filteredCounts };
if (filters.size > 0) {
if (this.state.isUnique) {
filteredCounts[source] = data.length;
@@ -779,12 +820,12 @@ class Browser extends DashboardView {
if (!this.state.data || this.state.isUnique) {
return null;
}
- let className = this.props.params.className;
- let source = this.state.relation || className;
+ const className = this.props.params.className;
+ const source = this.state.relation || className;
let query = queryFromFilters(source, this.state.filters);
if (this.state.ordering !== '-createdAt') {
// Construct complex pagination query
- let equalityQuery = queryFromFilters(source, this.state.filters);
+ const equalityQuery = queryFromFilters(source, this.state.filters);
let field = this.state.ordering;
let ascending = true;
let comp = this.state.data[this.state.data.length - 1].get(field);
@@ -799,9 +840,15 @@ class Browser extends DashboardView {
query.greaterThan(field, comp);
}
if (field === 'createdAt') {
- equalityQuery.greaterThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt'));
+ equalityQuery.greaterThan(
+ 'createdAt',
+ this.state.data[this.state.data.length - 1].get('createdAt')
+ );
} else {
- equalityQuery.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt'));
+ equalityQuery.lessThan(
+ 'createdAt',
+ this.state.data[this.state.data.length - 1].get('createdAt')
+ );
equalityQuery.equalTo(field, comp);
}
query = Parse.Query.or(query, equalityQuery);
@@ -818,9 +865,9 @@ class Browser extends DashboardView {
this.excludeFields(query, source);
const { useMasterKey } = this.state;
- query.find({ useMasterKey }).then((nextPage) => {
+ query.find({ useMasterKey }).then(nextPage => {
if (className === this.props.params.className) {
- this.setState((state) => ({
+ this.setState(state => ({
data: state.data.concat(nextPage),
}));
}
@@ -835,7 +882,9 @@ class Browser extends DashboardView {
} else {
const source = this.props.params.className;
const _filters = JSON.stringify(filters.toJSON());
- const url = `browser/${source}${filters.size === 0 ? '' : `?filters=${encodeURIComponent(_filters)}`}`;
+ const url = `browser/${source}${
+ filters.size === 0 ? '' : `?filters=${encodeURIComponent(_filters)}`
+ }`;
// filters param change is making the fetch call
this.props.navigate(generatePath(this.context, url));
}
@@ -843,19 +892,29 @@ class Browser extends DashboardView {
saveFilters(filters, name) {
const _filters = JSON.stringify(filters.toJSON());
- const preferences = ClassPreferences.getPreferences(this.context.applicationId, this.props.params.className);
+ const preferences = ClassPreferences.getPreferences(
+ this.context.applicationId,
+ this.props.params.className
+ );
if (!preferences.filters.includes(_filters)) {
preferences.filters.push({
name,
filter: _filters,
});
}
- ClassPreferences.updatePreferences(preferences, this.context.applicationId, this.props.params.className);
+ ClassPreferences.updatePreferences(
+ preferences,
+ this.context.applicationId,
+ this.props.params.className
+ );
super.forceUpdate();
}
removeFilter(filter) {
- const preferences = ClassPreferences.getPreferences(this.context.applicationId, this.props.params.className);
+ const preferences = ClassPreferences.getPreferences(
+ this.context.applicationId,
+ this.props.params.className
+ );
let i = preferences.filters.length;
while (i--) {
const item = preferences.filters[i];
@@ -863,12 +922,16 @@ class Browser extends DashboardView {
preferences.filters.splice(i, 1);
}
}
- ClassPreferences.updatePreferences(preferences, this.context.applicationId, this.props.params.className);
+ ClassPreferences.updatePreferences(
+ preferences,
+ this.context.applicationId,
+ this.props.params.className
+ );
super.forceUpdate();
}
updateOrdering(ordering) {
- let source = this.state.relation || this.props.params.className;
+ const source = this.state.relation || this.props.params.className;
this.setState(
{
ordering: ordering,
@@ -876,7 +939,11 @@ class Browser extends DashboardView {
},
() => this.fetchData(source, this.state.filters)
);
- ColumnPreferences.getColumnSort(ordering, this.context.applicationId, this.props.params.className);
+ ColumnPreferences.getColumnSort(
+ ordering,
+ this.context.applicationId,
+ this.props.params.className
+ );
}
getRelationURL() {
@@ -898,7 +965,9 @@ class Browser extends DashboardView {
if (filters && filters.size) {
filterQueryString = encodeURIComponent(JSON.stringify(filters.toJSON()));
}
- const url = `${this.getRelationURL()}${filterQueryString ? `?filters=${filterQueryString}` : ''}`;
+ const url = `${this.getRelationURL()}${
+ filterQueryString ? `?filters=${filterQueryString}` : ''
+ }`;
this.props.navigate(url);
}
);
@@ -906,29 +975,34 @@ class Browser extends DashboardView {
}
handlePointerClick({ className, id, field = 'objectId' }) {
- let filters = JSON.stringify([
+ const filters = JSON.stringify([
{
field,
constraint: 'eq',
compareTo: id,
},
]);
- this.props.navigate(generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`));
+ this.props.navigate(
+ generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`)
+ );
}
handlePointerCmdClick({ className, id, field = 'objectId' }) {
- let filters = JSON.stringify([
+ const filters = JSON.stringify([
{
field,
constraint: 'eq',
compareTo: id,
},
]);
- window.open(generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`), '_blank');
+ window.open(
+ generatePath(this.context, `browser/${className}?filters=${encodeURIComponent(filters)}`),
+ '_blank'
+ );
}
handleCLPChange(clp) {
- let p = this.props.schema.dispatch(ActionTypes.SET_CLP, {
+ const p = this.props.schema.dispatch(ActionTypes.SET_CLP, {
className: this.props.params.className,
clp,
});
@@ -937,8 +1011,8 @@ class Browser extends DashboardView {
}
updateRow(row, attr, value) {
- let isNewObject = row === -1;
- let isEditCloneObj = row < -1;
+ const isNewObject = row === -1;
+ const isEditCloneObj = row < -1;
let obj = isNewObject ? this.state.newObject : this.state.data[row];
if (isEditCloneObj) {
obj = this.state.editCloneRows[row + (this.state.editCloneRows.length + 1)];
@@ -964,7 +1038,7 @@ class Browser extends DashboardView {
}
if (isEditCloneObj) {
const editObjIndex = row + (this.state.editCloneRows.length + 1);
- let cloneRows = [...this.state.editCloneRows];
+ const cloneRows = [...this.state.editCloneRows];
cloneRows.splice(editObjIndex, 1, obj);
this.setState({
editCloneRows: cloneRows,
@@ -974,8 +1048,8 @@ class Browser extends DashboardView {
const { useMasterKey } = this.state;
obj.save(null, { useMasterKey }).then(
- (objectSaved) => {
- let msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' updated';
+ objectSaved => {
+ const msg = objectSaved.className + ' with id \'' + objectSaved.id + '\' updated';
this.showNote(msg, false);
const state = {
@@ -1002,7 +1076,7 @@ class Browser extends DashboardView {
},
});
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -1021,8 +1095,12 @@ class Browser extends DashboardView {
}
}
if (isEditCloneObj) {
- state.editCloneRows = state.editCloneRows.filter((cloneObj) => cloneObj._localId !== obj._localId);
- if (state.editCloneRows.length === 0) state.editCloneRows = null;
+ state.editCloneRows = state.editCloneRows.filter(
+ cloneObj => cloneObj._localId !== obj._localId
+ );
+ if (state.editCloneRows.length === 0) {
+ state.editCloneRows = null;
+ }
if (this.props.params.className === obj.className) {
this.state.data.unshift(obj);
}
@@ -1030,7 +1108,7 @@ class Browser extends DashboardView {
}
this.setState(state);
},
- (error) => {
+ error => {
let msg = typeof error === 'string' ? error : error.message;
if (msg) {
msg = msg[0].toUpperCase() + msg.substr(1);
@@ -1047,7 +1125,7 @@ class Browser extends DashboardView {
deleteRows(rows) {
this.setState({ rowsToDelete: null, selection: {} });
- let className = this.props.params.className;
+ const className = this.props.params.className;
if (!this.state.relation && rows['*']) {
this.context.clearCollection(className).then(() => {
if (this.props.params.className === className) {
@@ -1060,11 +1138,11 @@ class Browser extends DashboardView {
}
});
} else {
- let indexes = [];
- let toDelete = [];
- let seeking = Object.keys(rows).length;
+ const indexes = [];
+ const toDelete = [];
+ const seeking = Object.keys(rows).length;
for (let i = 0; i < this.state.data.length && indexes.length < seeking; i++) {
- let obj = this.state.data[i];
+ const obj = this.state.data[i];
if (!obj || !obj.id) {
continue;
}
@@ -1075,12 +1153,12 @@ class Browser extends DashboardView {
}
const toDeleteObjectIds = [];
- toDelete.forEach((obj) => {
+ toDelete.forEach(obj => {
toDeleteObjectIds.push(obj.id);
});
const { useMasterKey } = this.state;
- let relation = this.state.relation;
+ const relation = this.state.relation;
if (relation && toDelete.length) {
relation.remove(toDelete);
relation.parent.save(null, { useMasterKey }).then(() => {
@@ -1121,22 +1199,33 @@ class Browser extends DashboardView {
}
}
},
- (error) => {
+ error => {
let errorDeletingNote = null;
if (error.code === Parse.Error.AGGREGATE_ERROR) {
if (error.errors.length == 1) {
- errorDeletingNote = 'Error deleting ' + className + ' with id \'' + error.errors[0].object.id + '\'';
+ errorDeletingNote =
+ 'Error deleting ' + className + ' with id \'' + error.errors[0].object.id + '\'';
} else if (error.errors.length < toDeleteObjectIds.length) {
- errorDeletingNote = 'Error deleting ' + error.errors.length + ' out of ' + toDeleteObjectIds.length + ' ' + className + ' objects';
+ errorDeletingNote =
+ 'Error deleting ' +
+ error.errors.length +
+ ' out of ' +
+ toDeleteObjectIds.length +
+ ' ' +
+ className +
+ ' objects';
} else {
- errorDeletingNote = 'Error deleting all ' + error.errors.length + ' ' + className + ' objects';
+ errorDeletingNote =
+ 'Error deleting all ' + error.errors.length + ' ' + className + ' objects';
}
} else {
if (toDeleteObjectIds.length == 1) {
- errorDeletingNote = 'Error deleting ' + className + ' with id \'' + toDeleteObjectIds[0] + '\'';
+ errorDeletingNote =
+ 'Error deleting ' + className + ' with id \'' + toDeleteObjectIds[0] + '\'';
} else {
- errorDeletingNote = 'Error deleting ' + toDeleteObjectIds.length + ' ' + className + ' objects';
+ errorDeletingNote =
+ 'Error deleting ' + toDeleteObjectIds.length + ' ' + className + ' objects';
}
}
@@ -1201,19 +1290,23 @@ class Browser extends DashboardView {
const missedObjectsCount = objectIds.length - objects.length;
if (missedObjectsCount) {
const missedObjects = [];
- objectIds.forEach((objectId) => {
- const object = objects.find((x) => x.id === objectId);
+ objectIds.forEach(objectId => {
+ const object = objects.find(x => x.id === objectId);
if (!object) {
missedObjects.push(objectId);
}
});
- const errorSummary = `${missedObjectsCount === 1 ? 'The object is' : `${missedObjectsCount} Objects are`} not retrieved:`;
+ const errorSummary = `${
+ missedObjectsCount === 1 ? 'The object is' : `${missedObjectsCount} Objects are`
+ } not retrieved:`;
throw `${errorSummary} ${JSON.stringify(missedObjects)}`;
}
parent.relation(relation.key).add(objects);
await parent.save(null, { useMasterKey });
// remove duplication
- this.state.data.forEach((origin) => (objects = objects.filter((object) => object.id !== origin.id)));
+ this.state.data.forEach(
+ origin => (objects = objects.filter(object => object.id !== origin.id))
+ );
this.setState({
data: [...objects, ...this.state.data],
relationCount: this.state.relationCount + objects.length,
@@ -1233,7 +1326,13 @@ class Browser extends DashboardView {
});
}
- async confirmAttachSelectedRows(className, targetObjectId, relationName, objectIds, targetClassName) {
+ async confirmAttachSelectedRows(
+ className,
+ targetObjectId,
+ relationName,
+ objectIds,
+ targetClassName
+ ) {
const { useMasterKey } = this.state;
const parentQuery = new Parse.Query(className);
const parent = await parentQuery.get(targetObjectId, { useMasterKey });
@@ -1271,7 +1370,7 @@ class Browser extends DashboardView {
const objects = await query.find({ useMasterKey });
const toClone = [];
for (const object of objects) {
- let clonedObj = object.clone();
+ const clonedObj = object.clone();
if (className === '_User') {
clonedObj.set('username', undefined);
clonedObj.set('authData', undefined);
@@ -1292,9 +1391,9 @@ class Browser extends DashboardView {
} catch (error) {
//for duplicate, username missing or required field missing errors
if (error.code === 137 || error.code === 200 || error.code === 142) {
- let failedSaveObj = [];
- let savedObjects = [];
- toClone.forEach((cloneObj) => {
+ const failedSaveObj = [];
+ const savedObjects = [];
+ toClone.forEach(cloneObj => {
cloneObj.dirty() ? failedSaveObj.push(cloneObj) : savedObjects.push(cloneObj);
});
if (savedObjects.length) {
@@ -1348,22 +1447,26 @@ class Browser extends DashboardView {
query.limit(objectIds.length);
}
- const processObjects = (objects) => {
+ const processObjects = objects => {
const classColumns = this.getClassColumns(className, false);
// create object with classColumns as property keys needed for ColumnPreferences.getOrder function
const columnsObject = {};
- classColumns.forEach((column) => {
+ classColumns.forEach(column => {
columnsObject[column.name] = column;
});
// get ordered list of class columns
- const columns = ColumnPreferences.getOrder(columnsObject, this.context.applicationId, className).filter((column) => column.visible);
+ const columns = ColumnPreferences.getOrder(
+ columnsObject,
+ this.context.applicationId,
+ className
+ ).filter(column => column.visible);
if (type === '.json') {
const element = document.createElement('a');
const file = new Blob(
[
JSON.stringify(
- objects.map((obj) => {
+ objects.map(obj => {
const json = obj._toFullJSON();
delete json.__type;
return json;
@@ -1382,10 +1485,10 @@ class Browser extends DashboardView {
return;
}
- let csvString = columns.map((column) => column.name).join(',') + '\n';
+ let csvString = columns.map(column => column.name).join(',') + '\n';
for (const object of objects) {
const row = columns
- .map((column) => {
+ .map(column => {
const type = columnsObject[column.name].type;
if (column.name === 'objectId') {
return object.id;
@@ -1403,7 +1506,10 @@ class Browser extends DashboardView {
colValue = object.get(column.name);
}
// Stringify objects and arrays
- if (Object.prototype.toString.call(colValue) === '[object Object]' || Object.prototype.toString.call(colValue) === '[object Array]') {
+ if (
+ Object.prototype.toString.call(colValue) === '[object Object]' ||
+ Object.prototype.toString.call(colValue) === '[object Array]'
+ ) {
colValue = JSON.stringify(colValue);
}
if (typeof colValue === 'string') {
@@ -1448,7 +1554,7 @@ class Browser extends DashboardView {
} else {
let batch = [];
query.eachBatch(
- (obj) => {
+ obj => {
batch.push(...obj);
if (batch.length % 10 === 0) {
this.setState({ exportingCount: batch.length });
@@ -1473,12 +1579,12 @@ class Browser extends DashboardView {
getClassRelationColumns(className) {
const currentClassName = this.props.params.className;
return this.getClassColumns(className, false)
- .map((column) => {
+ .map(column => {
if (column.type === 'Relation' && column.targetClass === currentClassName) {
return column.name;
}
})
- .filter((column) => column);
+ .filter(column => column);
}
getClassColumns(className, onlyTouchable = true) {
@@ -1495,19 +1601,19 @@ class Browser extends DashboardView {
if (className[0] === '_' && DefaultColumns[className]) {
untouchable = untouchable.concat(DefaultColumns[className]);
}
- columns = columns.filter((column) => untouchable.indexOf(column.name) === -1);
+ columns = columns.filter(column => untouchable.indexOf(column.name) === -1);
}
return columns;
}
renderSidebar() {
- let current = this.props.params.className || '';
- let classes = this.props.schema.data.get('classes');
+ const current = this.props.params.className || '';
+ const classes = this.props.schema.data.get('classes');
if (!classes) {
return null;
}
- let special = [];
- let categories = [];
+ const special = [];
+ const categories = [];
classes.forEach((value, key) => {
let count = this.state.counts[key];
if (count === undefined) {
@@ -1528,12 +1634,24 @@ class Browser extends DashboardView {
}
const allCategories = [];
for (const row of [...special, ...categories]) {
- const { filters = [] } = ClassPreferences.getPreferences(this.context.applicationId, row.name);
+ const { filters = [] } = ClassPreferences.getPreferences(
+ this.context.applicationId,
+ row.name
+ );
row.filters = filters;
allCategories.push(row);
}
- return this.props.navigate(generatePath(this.context, url))} removeFilter={(filter) => this.removeFilter(filter)} categories={allCategories} />;
+ return (
+ this.props.navigate(generatePath(this.context, url))}
+ removeFilter={filter => this.removeFilter(filter)}
+ categories={allCategories}
+ />
+ );
}
showNote(message, isError) {
@@ -1587,7 +1705,11 @@ class Browser extends DashboardView {
}
async onChangeDefaultKey(name) {
- ColumnPreferences.setPointerDefaultKey(this.context.applicationId, this.props.params.className, name);
+ ColumnPreferences.setPointerDefaultKey(
+ this.context.applicationId,
+ this.props.params.className,
+ name
+ );
this.setState({ showPointerKeyDialog: false });
}
@@ -1597,12 +1719,18 @@ class Browser extends DashboardView {
if (this.state.relation) {
className = this.state.relation.targetClassName;
}
- let classes = this.props.schema.data.get('classes');
+ const classes = this.props.schema.data.get('classes');
if (classes) {
if (classes.size === 0) {
browser = (
-
+
);
} else if (className && classes.get(className)) {
@@ -1617,7 +1745,10 @@ class Browser extends DashboardView {
return;
}
const info = { type, required: !!required };
- if (className === '_User' && (name === 'username' || name === 'password' || name === 'authData')) {
+ if (
+ className === '_User' &&
+ (name === 'username' || name === 'password' || name === 'authData')
+ ) {
info.required = true;
}
if (className === '_Role' && (name === 'name' || name === 'ACL')) {
@@ -1629,7 +1760,7 @@ class Browser extends DashboardView {
columns[name] = info;
});
- var count;
+ let count;
if (this.state.relation) {
count = this.state.relationCount;
} else {
@@ -1706,13 +1837,29 @@ class Browser extends DashboardView {
}
let extras = null;
if (this.state.showPointerKeyDialog) {
- let currentColumns = this.getClassColumns(className).map((column) => column.name);
- extras = this.setState({ showPointerKeyDialog: false })} onConfirm={this.onChangeDefaultKey} />;
+ const currentColumns = this.getClassColumns(className).map(column => column.name);
+ extras = (
+ this.setState({ showPointerKeyDialog: false })}
+ onConfirm={this.onChangeDefaultKey}
+ />
+ );
} else if (this.state.showCreateClassDialog) {
- extras = this.setState({ showCreateClassDialog: false })} onConfirm={this.createClass} />;
+ extras = (
+ this.setState({ showCreateClassDialog: false })}
+ onConfirm={this.createClass}
+ />
+ );
} else if (this.state.showAddColumnDialog) {
const currentApp = this.context || {};
- let currentColumns = [];
+ const currentColumns = [];
classes.get(className).forEach((field, name) => {
currentColumns.push(name);
});
@@ -1729,10 +1876,24 @@ class Browser extends DashboardView {
/>
);
} else if (this.state.showRemoveColumnDialog) {
- let currentColumns = this.getClassColumns(className).map((column) => column.name);
- extras = this.setState({ showRemoveColumnDialog: false })} onConfirm={this.removeColumn} />;
+ const currentColumns = this.getClassColumns(className).map(column => column.name);
+ extras = (
+ this.setState({ showRemoveColumnDialog: false })}
+ onConfirm={this.removeColumn}
+ />
+ );
} else if (this.state.rowsToDelete) {
- extras = this.setState({ rowsToDelete: null })} onConfirm={() => this.deleteRows(this.state.rowsToDelete)} />;
+ extras = (
+ this.setState({ rowsToDelete: null })}
+ onConfirm={() => this.deleteRows(this.state.rowsToDelete)}
+ />
+ );
} else if (this.state.showDropClassDialog) {
extras = (
);
} else if (this.state.showExportDialog) {
- extras = this.setState({ showExportDialog: false })} onConfirm={() => this.exportClass(className)} />;
+ extras = (
+ this.setState({ showExportDialog: false })}
+ onConfirm={() => this.exportClass(className)}
+ />
+ );
} else if (this.state.showExportSchemaDialog) {
- extras = this.setState({ showExportSchemaDialog: false })} onConfirm={(...args) => this.exportSchema(...args)} />;
+ extras = (
+ this.setState({ showExportSchemaDialog: false })}
+ onConfirm={(...args) => this.exportSchema(...args)}
+ />
+ );
} else if (this.state.showAttachRowsDialog) {
- extras = ;
+ extras = (
+
+ );
} else if (this.state.showAttachSelectedRowsDialog) {
- extras = ;
+ extras = (
+
+ );
} else if (this.state.showCloneSelectedRowsDialog) {
- extras = ;
+ extras = (
+
+ );
} else if (this.state.showEditRowDialog) {
const classColumns = this.getClassColumns(className, false);
// create object with classColumns as property keys needed for ColumnPreferences.getOrder function
const columnsObject = {};
- classColumns.forEach((column) => {
+ classColumns.forEach(column => {
columnsObject[column.name] = column;
});
// get ordered list of class columns
const columnPreferences = this.context.columnPreference || {};
- const columns = ColumnPreferences.getOrder(columnsObject, this.context.applicationId, className, columnPreferences[className]);
+ const columns = ColumnPreferences.getOrder(
+ columnsObject,
+ this.context.applicationId,
+ className,
+ columnPreferences[className]
+ );
// extend columns with their type and targetClass properties
- columns.forEach((column) => {
+ columns.forEach(column => {
const { type, targetClass } = columnsObject[column.name];
column.type = type;
column.targetClass = targetClass;
@@ -1788,7 +1988,7 @@ class Browser extends DashboardView {
this.selectRow(selectedId, true);
}
- const row = data.findIndex((d) => d.id === selectedId);
+ const row = data.findIndex(d => d.id === selectedId);
const attributes = selectedId ? data[row].attributes : newObject.attributes;
@@ -1821,7 +2021,9 @@ class Browser extends DashboardView {
count={this.state.counts[className]}
data={this.state.data}
onCancel={this.cancelExportSelectedRows}
- onConfirm={(type, indentation) => this.confirmExportSelectedRows(this.state.rowsToExport, type, indentation)}
+ onConfirm={(type, indentation) =>
+ this.confirmExportSelectedRows(this.state.rowsToExport, type, indentation)
+ }
/>
);
}
@@ -1834,7 +2036,12 @@ class Browser extends DashboardView {
} else if (this.state.lastNote) {
notification = ;
} else if (this.state.exporting) {
- notification = ;
+ notification = (
+
+ );
}
return (
diff --git a/src/dashboard/Data/Browser/Browser.scss b/src/dashboard/Data/Browser/Browser.scss
index b39eee98cb..ba6b92c463 100644
--- a/src/dashboard/Data/Browser/Browser.scss
+++ b/src/dashboard/Data/Browser/Browser.scss
@@ -251,3 +251,17 @@ body:global(.expanded) {
width: calc(100% - 280px);
float: left;
}
+
+.editRowDialogFileCell {
+ max-width: 100%;
+ padding: 25px;
+ span {
+ display: flex;
+ span {
+ width: 90%;
+ }
+ & a {
+ position: relative;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index 235241ccd8..554b852195 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -5,23 +5,23 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import BrowserRow from 'components/BrowserRow/BrowserRow.react';
-import DataBrowserHeaderBar from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.react';
-import Editor from 'dashboard/Data/Browser/Editor.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import Icon from 'components/Icon/Icon.react';
-import Parse from 'parse';
-import encode from 'parse/lib/browser/encode';
-import React from 'react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import Button from 'components/Button/Button.react';
-import { CurrentApp } from 'context/currentApp';
+import BrowserRow from 'components/BrowserRow/BrowserRow.react';
+import DataBrowserHeaderBar from 'components/DataBrowserHeaderBar/DataBrowserHeaderBar.react';
+import Editor from 'dashboard/Data/Browser/Editor.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import Icon from 'components/Icon/Icon.react';
+import Parse from 'parse';
+import encode from 'parse/lib/browser/encode';
+import React from 'react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import Button from 'components/Button/Button.react';
+import { CurrentApp } from 'context/currentApp';
const MAX_ROWS = 200; // Number of rows to render at any time
const ROWS_OFFSET = 160;
const ROW_HEIGHT = 30;
-const READ_ONLY = [ 'objectId', 'createdAt', 'updatedAt' ];
+const READ_ONLY = ['objectId', 'createdAt', 'updatedAt'];
export default class BrowserTable extends React.Component {
static contextType = CurrentApp;
@@ -78,9 +78,10 @@ export default class BrowserTable extends React.Component {
// Rounds the number of rows above
rowsAbove = Math.floor(rowsAbove / 10) * 10;
- offset = currentRow < 10
- ? Math.max(0, rowsAbove - ROWS_OFFSET) // Previous set of rows
- : rowsAbove - 10; // Next set of rows
+ offset =
+ currentRow < 10
+ ? Math.max(0, rowsAbove - ROWS_OFFSET) // Previous set of rows
+ : rowsAbove - 10; // Next set of rows
}
if (this.state.offset !== offset) {
this.setState({ offset });
@@ -96,39 +97,44 @@ export default class BrowserTable extends React.Component {
let ordering = {};
if (this.props.ordering) {
if (this.props.ordering[0] === '-') {
- ordering = { col: this.props.ordering.substr(1), direction: 'descending' };
+ ordering = {
+ col: this.props.ordering.substr(1),
+ direction: 'descending',
+ };
} else {
ordering = { col: this.props.ordering, direction: 'ascending' };
}
}
- let headers = this.props.order.map(({ name, width, visible, preventSort, required }) => (
- {
- width: width,
- name: name,
- type: this.props.columns[name].type,
- targetClass: this.props.columns[name].targetClass,
- order: ordering.col === name ? ordering.direction : null,
- visible,
- preventSort,
- required
- }
- ));
+ const headers = this.props.order.map(({ name, width, visible, preventSort, required }) => ({
+ width: width,
+ name: name,
+ type: this.props.columns[name].type,
+ targetClass: this.props.columns[name].targetClass,
+ order: ordering.col === name ? ordering.direction : null,
+ visible,
+ preventSort,
+ required,
+ }));
let editor = null;
let table =
;
if (this.props.data) {
const rowWidth = this.props.order.reduce(
- (rowWidth, { visible, width }) => visible ? rowWidth + width : rowWidth,
+ (rowWidth, { visible, width }) => (visible ? rowWidth + width : rowWidth),
this.props.onAddRow ? 210 : 0
);
let editCloneRows;
- if(this.props.editCloneRows){
+ if (this.props.editCloneRows) {
editCloneRows = (
{this.props.editCloneRows.map((cloneRow, idx) => {
- let index = (this.props.editCloneRows.length + 1) * -1 + idx;
- const currentCol = this.props.current && this.props.current.row === index ? this.props.current.col : undefined;
- const isEditingRow = this.props.current && this.props.current.row === index && !!this.props.editing;
+ const index = (this.props.editCloneRows.length + 1) * -1 + idx;
+ const currentCol =
+ this.props.current && this.props.current.row === index
+ ? this.props.current.col
+ : undefined;
+ const isEditingRow =
+ this.props.current && this.props.current.row === index && !!this.props.editing;
return (
this.props.onAbortEditCloneRow(index)}
- additionalStyles={{ fontSize: '12px', height: '20px', lineHeight: '20px', margin: '5px', padding: '0'}}
+ additionalStyles={{
+ fontSize: '12px',
+ height: '20px',
+ lineHeight: '20px',
+ margin: '5px',
+ padding: '0',
+ }}
/>
);
})}
- )
+ );
}
let newRow;
if (this.props.newObject && this.state.offset <= 0) {
- const currentCol = this.props.current && this.props.current.row === -1 ? this.props.current.col : undefined;
+ const currentCol =
+ this.props.current && this.props.current.row === -1 ? this.props.current.col : undefined;
newRow = (
);
}
- let rows = [];
- let end = Math.min(this.state.offset + MAX_ROWS, this.props.data.length);
+ const rows = [];
+ const end = Math.min(this.state.offset + MAX_ROWS, this.props.data.length);
for (let i = this.state.offset; i < end; i++) {
- let index = i - this.state.offset;
- let obj = this.props.data[i];
- const currentCol = this.props.current && this.props.current.row === i ? this.props.current.col : undefined;
+ const index = i - this.state.offset;
+ const obj = this.props.data[i];
+ const currentCol =
+ this.props.current && this.props.current.row === i ? this.props.current.col : undefined;
// Needed in order to force BrowserRow to update and re-render (and possibly update columns values),
// since the "obj" instance will only be updated when the update request is done.
- const isEditingRow = this.props.current && this.props.current.row === i && !!this.props.editing;
- rows[index] =
+ const isEditingRow =
+ this.props.current && this.props.current.row === i && !!this.props.editing;
+ rows[index] = (
+
+ );
}
if (this.props.editing) {
@@ -280,21 +326,27 @@ 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];
+ const { name, width } = this.props.order[this.props.current.col];
+ const { type, targetClass } = this.props.columns[name];
let readonly = this.props.isUnique || READ_ONLY.indexOf(name) > -1;
if (name === 'sessionToken') {
if (this.props.className === '_User' || this.props.className === '_Session') {
readonly = true;
}
}
- if(name === 'expiresAt' && this.props.className === '_Session'){
+ if (name === 'expiresAt' && this.props.className === '_Session') {
readonly = true;
}
- let obj = this.props.current.row < 0 ? this.props.newObject : this.props.data[this.props.current.row];
+ let obj =
+ this.props.current.row < 0
+ ? this.props.newObject
+ : this.props.data[this.props.current.row];
let value = obj;
- if(!obj && this.props.current.row < -1){
- obj = this.props.editCloneRows[this.props.current.row + this.props.editCloneRows.length + 1];
+ if (!obj && this.props.current.row < -1) {
+ obj =
+ this.props.editCloneRows[
+ this.props.current.row + this.props.editCloneRows.length + 1
+ ];
}
if (!this.props.isUnique) {
if (type === 'Array' || type === 'Object') {
@@ -312,14 +364,18 @@ export default class BrowserTable extends React.Component {
value = obj.id;
}
} else if (name === 'ACL' && this.props.className === '_User' && !value) {
- value = new Parse.ACL({ '*': { read: true }, [obj.id]: { read: true, write: true }});
+ value = new Parse.ACL({
+ '*': { read: true },
+ [obj.id]: { read: true, write: true },
+ });
} else if (name === 'password' && this.props.className === '_User') {
value = '';
}
let wrapTop = Math.max(0, this.props.current.row * ROW_HEIGHT);
- if(this.props.current.row < -1 && this.props.editCloneRows){
+ if (this.props.current.row < -1 && this.props.editCloneRows) {
//for edit clone rows
- wrapTop = (2 * ROW_HEIGHT) * (this.props.current.row + (this.props.editCloneRows.length + 1));
+ wrapTop =
+ 2 * ROW_HEIGHT * (this.props.current.row + (this.props.editCloneRows.length + 1));
}
if (this.props.current.row > -1 && this.props.newObject) {
//for data rows when there's new row
@@ -327,7 +383,7 @@ export default class BrowserTable extends React.Component {
}
if (this.props.current.row >= -1 && this.props.editCloneRows) {
//for data rows & new row when there are edit clone rows
- wrapTop += (2 * ROW_HEIGHT) * (this.props.editCloneRows.length);
+ wrapTop += 2 * ROW_HEIGHT * this.props.editCloneRows.length;
}
let wrapLeft = 30;
for (let i = 0; i < this.props.current.col; i++) {
@@ -344,17 +400,14 @@ export default class BrowserTable extends React.Component {
value={value}
readonly={readonly}
width={width}
- onCommit={(newValue) => {
+ onCommit={newValue => {
if (newValue !== value) {
- this.props.updateRow(
- this.props.current.row,
- name,
- newValue
- );
+ this.props.updateRow(this.props.current.row, name, newValue);
}
this.props.setEditing(false);
}}
- onCancel={() =>this.props.setEditing(false)} />
+ onCancel={() => this.props.setEditing(false)}
+ />
);
}
}
@@ -369,8 +422,7 @@ export default class BrowserTable extends React.Component {
onClick={this.props.onAddRow}
primary
value={`Create a ${this.props.relation.targetClassName} and attach`}
- />
- {' '}
+ />{' '}
-
-
+
+
);
@@ -400,7 +448,14 @@ export default class BrowserTable extends React.Component {
{editCloneRows}
{newRow}
{rows}
-
+
{addRow}
{editor}
@@ -409,22 +464,25 @@ export default class BrowserTable extends React.Component {
table = (
- {this.props.relation ?
+ {this.props.relation ? (
:
+ icon="files-solid"
+ />
+ ) : (
- }
+ action={this.props.onAddRow}
+ />
+ )}
);
@@ -438,9 +496,12 @@ export default class BrowserTable extends React.Component {
selected={
!!this.props.selection &&
!!this.props.data &&
- Object.values(this.props.selection).filter(checked => checked).length === this.props.data.length
+ Object.values(this.props.selection).filter(checked => checked).length ===
+ this.props.data.length
+ }
+ selectAll={checked =>
+ this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))
}
- selectAll={checked => this.props.data.forEach(({ id }) => this.props.selectRow(id, checked))}
headers={headers}
updateOrdering={this.props.updateOrdering}
readonly={!!this.props.relation || !!this.props.isUnique}
diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js
index 73824bc86c..7afa2b5648 100644
--- a/src/dashboard/Data/Browser/BrowserToolbar.react.js
+++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js
@@ -5,22 +5,22 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import BrowserFilter from 'components/BrowserFilter/BrowserFilter.react';
-import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react';
-import Icon from 'components/Icon/Icon.react';
-import MenuItem from 'components/BrowserMenu/MenuItem.react';
-import prettyNumber from 'lib/prettyNumber';
-import React, { useRef } from 'react';
-import Separator from 'components/BrowserMenu/Separator.react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
-import ColumnsConfiguration from 'components/ColumnsConfiguration/ColumnsConfiguration.react'
-import SecureFieldsDialog from 'dashboard/Data/Browser/SecureFieldsDialog.react';
-import LoginDialog from 'dashboard/Data/Browser/LoginDialog.react';
-import Toggle from 'components/Toggle/Toggle.react';
+import BrowserFilter from 'components/BrowserFilter/BrowserFilter.react';
+import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react';
+import Icon from 'components/Icon/Icon.react';
+import MenuItem from 'components/BrowserMenu/MenuItem.react';
+import prettyNumber from 'lib/prettyNumber';
+import React, { useRef } from 'react';
+import Separator from 'components/BrowserMenu/Separator.react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
+import ColumnsConfiguration from 'components/ColumnsConfiguration/ColumnsConfiguration.react';
+import SecureFieldsDialog from 'dashboard/Data/Browser/SecureFieldsDialog.react';
+import LoginDialog from 'dashboard/Data/Browser/LoginDialog.react';
+import Toggle from 'components/Toggle/Toggle.react';
-let BrowserToolbar = ({
+const BrowserToolbar = ({
className,
classNameForEditors,
count,
@@ -72,21 +72,27 @@ let BrowserToolbar = ({
logout,
toggleMasterKeyUsage,
}) => {
- let selectionLength = Object.keys(selection).length;
- let isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
- let details = [];
+ const selectionLength = Object.keys(selection).length;
+ const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
+ const details = [];
if (count !== undefined) {
- if (count === 1) {
- details.push('1 object');
- } else {
- details.push(prettyNumber(count) + ' objects');
- }
+ if (count === 1) {
+ details.push('1 object');
+ } else {
+ details.push(prettyNumber(count) + ' objects');
+ }
}
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['*'];
+ const read = perms.get && perms.find && perms.get['*'] && perms.find['*'];
+ const write =
+ perms.create &&
+ perms.update &&
+ perms.delete &&
+ perms.create['*'] &&
+ perms.update['*'] &&
+ perms.delete['*'];
if (read && write) {
details.push('Public Read and Write enabled');
} else if (read) {
@@ -99,15 +105,9 @@ let BrowserToolbar = ({
let menu = null;
if (relation) {
menu = (
-
-
-
+
+
+
-
-
- {enableColumnManipulation ? : }
- {enableClassManipulation ? : }
+
+
+
+ {enableColumnManipulation ? (
+
+ ) : (
+
+ )}
+ {enableClassManipulation ? (
+
+ ) : (
+
+ )}
-
+
onDeleteRows(selection)} />
- {enableColumnManipulation ? : }
- {enableDeleteAllRows ? onDeleteRows({ '*': true })} /> : }
- {enableClassManipulation ? : }
+ onClick={() => onDeleteRows(selection)}
+ />
+ {enableColumnManipulation ? (
+
+ ) : (
+
+ )}
+ {enableDeleteAllRows ? (
+ onDeleteRows({ '*': true })} />
+ ) : (
+
+ )}
+ {enableClassManipulation ? (
+
+ ) : (
+
+ )}
{enableExportClass ? : }
- {enableExportClass ? : }
+ {enableExportClass ? : }
);
}
@@ -186,20 +212,20 @@ let BrowserToolbar = ({
columns[col] = { type, targetClass };
- if (col === 'objectId' || isUnique && col !== uniqueField) {
+ if (col === 'objectId' || (isUnique && col !== uniqueField)) {
return;
}
- if ((type ==='Pointer' && targetClass === '_User') || type === 'Array' ) {
+ if ((type === 'Pointer' && targetClass === '_User') || type === 'Array') {
userPointers.push(col);
}
});
}
- let clpDialogRef = useRef(null);
- let protectedDialogRef = useRef(null);
- let loginDialogRef = useRef(null);
+ const clpDialogRef = useRef(null);
+ const protectedDialogRef = useRef(null);
+ const loginDialogRef = useRef(null);
- const showCLP = ()=> clpDialogRef.current.handleOpen();
+ const showCLP = () => clpDialogRef.current.handleOpen();
const showProtected = () => protectedDialogRef.current.handleOpen();
const showLogin = () => loginDialogRef.current.handleOpen();
@@ -227,42 +253,70 @@ let BrowserToolbar = ({
/>
{onAddRow && (
-
+
)}
{onAddRow && (
-
- {currentUser ? Use Master Key } onClick={toggleMasterKeyUsage} /> : }
- {currentUser ? Stop browsing ({currentUser.get('username')} )} onClick={logout} /> : }
+ setCurrent={setCurrent}
+ title={currentUser ? 'Browsing' : 'Browse'}
+ icon="users-solid"
+ active={!!currentUser}
+ disabled={isPendingEditCloneRows}
+ >
+
+ {currentUser ? (
+
+ Use Master Key{' '}
+
+
+ }
+ onClick={toggleMasterKeyUsage}
+ />
+ ) : (
+
+ )}
+ {currentUser ? (
+
+ Stop browsing ({currentUser.get('username')} )
+
+ }
+ onClick={logout}
+ />
+ ) : (
+
+ )}
)}
{onAddRow &&
}
{onAddRow && (
-
+
onExportSelectedRows(selection)}
/>
- onExportSelectedRows({ '*': true })}
- />
- onExportSchema()}
- />
+ onExportSelectedRows({ '*': true })} />
+ onExportSchema()} />
)}
{onAddRow &&
}
@@ -306,8 +360,8 @@ let BrowserToolbar = ({
className={classNameForEditors}
onChangeCLP={onChangeCLP}
userPointers={userPointers}
- title='ProtectedFields'
- icon='locked-solid'
+ title="ProtectedFields"
+ icon="locked-solid"
onEditPermissions={onEditPermissions}
/>
{enableSecurityDialog ? (
@@ -323,22 +377,14 @@ let BrowserToolbar = ({
) : (
)}
- {enableSecurityDialog ? (
-
- ) : (
-
- )}
+ {enableSecurityDialog ?
: }
{menu}
{editCloneRows && editCloneRows.length > 0 &&
}
{editCloneRows && editCloneRows.length > 0 && (
-
+
)}
-
);
};
diff --git a/src/dashboard/Data/Browser/CloneSelectedRowsDialog.react.js b/src/dashboard/Data/Browser/CloneSelectedRowsDialog.react.js
index 3a387f4a21..19645549af 100644
--- a/src/dashboard/Data/Browser/CloneSelectedRowsDialog.react.js
+++ b/src/dashboard/Data/Browser/CloneSelectedRowsDialog.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
export default class CloneSelectedRowsDialog extends React.Component {
@@ -16,7 +16,7 @@ export default class CloneSelectedRowsDialog extends React.Component {
super();
this.state = {
- confirmation: ''
+ confirmation: '',
};
}
@@ -32,34 +32,44 @@ export default class CloneSelectedRowsDialog extends React.Component {
render() {
let content = null;
- let selectionLength = Object.keys(this.props.selection).length;
+ const selectionLength = Object.keys(this.props.selection).length;
if (this.props.selection['*'] || selectionLength >= 10) {
content = (
+ text="Confirm this action"
+ description="Enter the current class name to continue."
+ />
}
input={
this.setState({ confirmation })} />
- } />
+ onChange={confirmation => this.setState({ confirmation })}
+ />
+ }
+ />
);
}
return (
+ onConfirm={this.props.onConfirm}
+ >
{content}
);
diff --git a/src/dashboard/Data/Browser/CreateClassDialog.react.js b/src/dashboard/Data/Browser/CreateClassDialog.react.js
index 9c2747bccf..92f2767d1f 100644
--- a/src/dashboard/Data/Browser/CreateClassDialog.react.js
+++ b/src/dashboard/Data/Browser/CreateClassDialog.react.js
@@ -5,14 +5,14 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import React from 'react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import React from 'react';
import { SpecialClasses } from 'lib/Constants';
-import TextInput from 'components/TextInput/TextInput.react';
+import TextInput from 'components/TextInput/TextInput.react';
import { withRouter } from 'lib/withRouter';
function validClassName(name) {
@@ -25,7 +25,7 @@ class CreateClassDialog extends React.Component {
super();
this.state = {
type: 'Custom',
- name: ''
+ name: '',
};
}
@@ -46,58 +46,70 @@ class CreateClassDialog extends React.Component {
}
render() {
- let availableClasses = ['Custom'];
- for (let raw of SpecialClasses) {
+ const availableClasses = ['Custom'];
+ for (const raw of SpecialClasses) {
if (raw !== '_Session' && !this.props.currentClasses.includes(raw)) {
availableClasses.push(raw);
}
}
- let typeDropdown = (
- this.setState({ type: type, name: '' })}>
- {availableClasses.map((t) => {t} )}
+ const typeDropdown = (
+ this.setState({ type: type, name: '' })}>
+ {availableClasses.map(t => (
+
+ {t}
+
+ ))}
);
return (
{
- let type = this.state.type;
- let className = type === 'Custom' ? this.state.name : type;
+ const type = this.state.type;
+ const className = type === 'Custom' ? this.state.name : type;
await this.props.onConfirm(className);
this.props.navigate(`/apps/${this.props.currentAppSlug}/browser/${className}`);
this.props.onAddColumn();
}}
onConfirm={() => {
- let type = this.state.type;
- let className = type === 'Custom' ? this.state.name : type;
+ const type = this.state.type;
+ const className = type === 'Custom' ? this.state.name : type;
this.props.onConfirm(className);
- }}>
- {availableClasses.length > 1 ?
-
- }
- input={typeDropdown} /> : null
- }
- {this.state.type === 'Custom' ?
+ }}
+ >
+ {availableClasses.length > 1 ? (
+ } input={typeDropdown} />
+ ) : null}
+ {this.state.type === 'Custom' ? (
}
- input={ this.setState({ name })} />}/> : null
- }
+ label={
+
+ }
+ input={
+ this.setState({ name })}
+ />
+ }
+ />
+ ) : null}
);
}
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js
index a1bca22cb7..e60b6fb23d 100644
--- a/src/dashboard/Data/Browser/DataBrowser.react.js
+++ b/src/dashboard/Data/Browser/DataBrowser.react.js
@@ -5,12 +5,12 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import copy from 'copy-to-clipboard';
-import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react';
-import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react';
-import ContextMenu from 'components/ContextMenu/ContextMenu.react';
+import copy from 'copy-to-clipboard';
+import BrowserTable from 'dashboard/Data/Browser/BrowserTable.react';
+import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react';
+import ContextMenu from 'components/ContextMenu/ContextMenu.react';
import * as ColumnPreferences from 'lib/ColumnPreferences';
-import React from 'react';
+import React from 'react';
/**
* DataBrowser renders the browser toolbar and data table
@@ -21,8 +21,8 @@ export default class DataBrowser extends React.Component {
constructor(props) {
super(props);
- const columnPreferences = props.app.columnPreference || {}
- let order = ColumnPreferences.getOrder(
+ const columnPreferences = props.app.columnPreference || {};
+ const order = ColumnPreferences.getOrder(
props.columns,
props.app.applicationId,
props.className,
@@ -33,7 +33,7 @@ export default class DataBrowser extends React.Component {
current: null,
editing: false,
copyableValue: undefined,
- simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className)
+ simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className),
};
this.handleKey = this.handleKey.bind(this);
@@ -50,8 +50,8 @@ export default class DataBrowser extends React.Component {
componentWillReceiveProps(props) {
if (props.className !== this.props.className) {
- const columnPreferences = props.app.columnPreference || {}
- let order = ColumnPreferences.getOrder(
+ const columnPreferences = props.app.columnPreference || {};
+ const order = ColumnPreferences.getOrder(
props.columns,
props.app.applicationId,
props.className,
@@ -61,12 +61,14 @@ export default class DataBrowser extends React.Component {
order: order,
current: null,
editing: false,
- simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className)
+ simplifiedSchema: this.getSimplifiedSchema(props.schema, props.className),
});
- } else if (Object.keys(props.columns).length !== Object.keys(this.props.columns).length
- || (props.isUnique && props.uniqueField !== this.props.uniqueField)) {
- const columnPreferences = props.app.columnPreference || {}
- let order = ColumnPreferences.getOrder(
+ } else if (
+ Object.keys(props.columns).length !== Object.keys(this.props.columns).length ||
+ (props.isUnique && props.uniqueField !== this.props.uniqueField)
+ ) {
+ const columnPreferences = props.app.columnPreference || {};
+ const order = ColumnPreferences.getOrder(
props.columns,
props.app.applicationId,
props.className,
@@ -88,10 +90,10 @@ export default class DataBrowser extends React.Component {
if (this.saveOrderTimeout) {
clearTimeout(this.saveOrderTimeout);
}
- let appId = this.props.app.applicationId;
- let className = this.props.className;
+ const appId = this.props.app.applicationId;
+ const className = this.props.className;
this.saveOrderTimeout = setTimeout(() => {
- ColumnPreferences.updatePreferences(order, appId, className)
+ ColumnPreferences.updatePreferences(order, appId, className);
shouldReload && this.props.onRefresh();
}, 1000);
}
@@ -145,7 +147,7 @@ export default class DataBrowser extends React.Component {
// if user is editing new row and want to cancel editing cell
if (e.keyCode === 27) {
this.setState({
- editing: false
+ editing: false,
});
e.preventDefault();
}
@@ -162,7 +164,7 @@ export default class DataBrowser extends React.Component {
switch (e.keyCode) {
case 27: // ESC
this.setState({
- editing: false
+ editing: false,
});
e.preventDefault();
break;
@@ -177,7 +179,7 @@ export default class DataBrowser extends React.Component {
const visibleColumnIndexes = [];
this.state.order.forEach((column, index) => {
column.visible && visibleColumnIndexes.push(index);
- })
+ });
const firstVisibleColumnIndex = Math.min(...visibleColumnIndexes);
const lastVisibleColumnIndex = Math.max(...visibleColumnIndexes);
@@ -185,14 +187,10 @@ export default class DataBrowser extends React.Component {
case 8:
case 46:
// Backspace or Delete
- let colName = this.state.order[this.state.current.col].name;
- let col = this.props.columns[colName];
+ const colName = this.state.order[this.state.current.col].name;
+ const col = this.props.columns[colName];
if (col.type !== 'Relation') {
- this.props.updateRow(
- this.state.current.row,
- colName,
- undefined
- );
+ this.props.updateRow(this.state.current.row, colName, undefined);
}
e.preventDefault();
break;
@@ -202,9 +200,15 @@ export default class DataBrowser extends React.Component {
this.setState({
current: {
row: this.state.current.row,
- col: (e.ctrlKey || e.metaKey) ? firstVisibleColumnIndex :
- this.getNextVisibleColumnIndex(-1, firstVisibleColumnIndex, lastVisibleColumnIndex)
- }
+ col:
+ e.ctrlKey || e.metaKey
+ ? firstVisibleColumnIndex
+ : this.getNextVisibleColumnIndex(
+ -1,
+ firstVisibleColumnIndex,
+ lastVisibleColumnIndex
+ ),
+ },
});
e.preventDefault();
break;
@@ -213,9 +217,9 @@ export default class DataBrowser extends React.Component {
// or with ctrl/meta (excel style - move to the first row)
this.setState({
current: {
- row: (e.ctrlKey || e.metaKey) ? 0 : Math.max(this.state.current.row - 1, 0),
- col: this.state.current.col
- }
+ row: e.ctrlKey || e.metaKey ? 0 : Math.max(this.state.current.row - 1, 0),
+ col: this.state.current.col,
+ },
});
e.preventDefault();
break;
@@ -225,9 +229,15 @@ export default class DataBrowser extends React.Component {
this.setState({
current: {
row: this.state.current.row,
- col: (e.ctrlKey || e.metaKey) ? lastVisibleColumnIndex :
- this.getNextVisibleColumnIndex(1, firstVisibleColumnIndex, lastVisibleColumnIndex)
- }
+ col:
+ e.ctrlKey || e.metaKey
+ ? lastVisibleColumnIndex
+ : this.getNextVisibleColumnIndex(
+ 1,
+ firstVisibleColumnIndex,
+ lastVisibleColumnIndex
+ ),
+ },
});
e.preventDefault();
break;
@@ -236,9 +246,12 @@ export default class DataBrowser extends React.Component {
// or with ctrl/meta (excel style - move to the last row)
this.setState({
current: {
- row: (e.ctrlKey || e.metaKey) ? this.props.data.length - 1 : Math.min(this.state.current.row + 1, this.props.data.length - 1),
- col: this.state.current.col
- }
+ row:
+ e.ctrlKey || e.metaKey
+ ? this.props.data.length - 1
+ : Math.min(this.state.current.row + 1, this.props.data.length - 1),
+ col: this.state.current.col,
+ },
});
e.preventDefault();
break;
@@ -248,20 +261,28 @@ export default class DataBrowser extends React.Component {
if (this.props.showNote) {
this.props.showNote('Value copied to clipboard', false);
}
- e.preventDefault()
+ e.preventDefault();
}
break;
}
}
getNextVisibleColumnIndex(distance = 1, min = 0, max = 0) {
- if (distance === 0) { return this.state.current.col; }
+ if (distance === 0) {
+ return this.state.current.col;
+ }
let newIndex = this.state.current.col + distance;
// eslint-disable-next-line no-constant-condition
while (true) {
- if (this.state.order[newIndex]?.visible) { return newIndex; }
- if (newIndex <= min) { return min; }
- if (newIndex >= max) { return max; }
+ if (this.state.order[newIndex]?.visible) {
+ return newIndex;
+ }
+ if (newIndex <= min) {
+ return min;
+ }
+ if (newIndex >= max) {
+ return max;
+ }
newIndex += distance;
}
}
@@ -297,7 +318,15 @@ export default class DataBrowser extends React.Component {
}
render() {
- let { className, count, disableSecurityDialog, onCancelPendingEditRows, editCloneRows, app, ...other } = this.props;
+ const {
+ className,
+ count,
+ disableSecurityDialog,
+ onCancelPendingEditRows,
+ editCloneRows,
+ app,
+ ...other
+ } = this.props;
const { preventSchemaEdits, applicationId } = app;
return (
@@ -317,16 +346,23 @@ export default class DataBrowser extends React.Component {
setContextMenu={this.setContextMenu}
onFilterChange={this.props.onFilterChange}
onFilterSave={this.props.onFilterSave}
- {...other} />
+ {...other}
+ />
+ {...other}
+ />
- {this.state.contextMenuX && }
+ {this.state.contextMenuX && (
+
+ )}
);
}
diff --git a/src/dashboard/Data/Browser/DeleteRowsDialog.react.js b/src/dashboard/Data/Browser/DeleteRowsDialog.react.js
index e995ef342e..d3049ff072 100644
--- a/src/dashboard/Data/Browser/DeleteRowsDialog.react.js
+++ b/src/dashboard/Data/Browser/DeleteRowsDialog.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
export default class DeleteRowsDialog extends React.Component {
@@ -16,7 +16,7 @@ export default class DeleteRowsDialog extends React.Component {
super();
this.state = {
- confirmation: ''
+ confirmation: '',
};
}
@@ -37,53 +37,65 @@ export default class DeleteRowsDialog extends React.Component {
render() {
let content = null;
- let selectionLength = Object.keys(this.props.selection).length;
+ const selectionLength = Object.keys(this.props.selection).length;
if (selectionLength >= 10) {
content = (
+ text="Confirm this action"
+ description='Enter "delete selected" word to continue.'
+ />
}
input={
this.setState({ confirmation })} />
- } />
+ onChange={confirmation => this.setState({ confirmation })}
+ />
+ }
+ />
);
}
if (this.props.selection['*']) {
content = (
- }
+ label={ }
input={
this.setState({ confirmation })} />
- } />
+ onChange={confirmation => this.setState({ confirmation })}
+ />
+ }
+ />
);
}
const deleteText = this.props.relation ? 'Detach' : 'Delete';
return (
+ onConfirm={this.props.onConfirm}
+ >
{content}
);
diff --git a/src/dashboard/Data/Browser/DropClassDialog.react.js b/src/dashboard/Data/Browser/DropClassDialog.react.js
index b007ab559e..0206685e83 100644
--- a/src/dashboard/Data/Browser/DropClassDialog.react.js
+++ b/src/dashboard/Data/Browser/DropClassDialog.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
export default class DropClassDialog extends React.Component {
@@ -16,12 +16,15 @@ export default class DropClassDialog extends React.Component {
super();
this.state = {
- confirmation: ''
+ confirmation: '',
};
}
valid() {
- if (this.state.confirmation === this.props.className || this.state.confirmation === this.props.className.substr(1)) {
+ if (
+ this.state.confirmation === this.props.className ||
+ this.state.confirmation === this.props.className.substr(1)
+ ) {
return true;
}
return false;
@@ -31,26 +34,30 @@ export default class DropClassDialog extends React.Component {
return (
+ onConfirm={this.props.onConfirm}
+ >
+ text="Confirm this action"
+ description="Enter the current class name to continue"
+ />
}
input={
this.setState({ confirmation })} />
- } />
+ onChange={confirmation => this.setState({ confirmation })}
+ />
+ }
+ />
);
}
diff --git a/src/dashboard/Data/Browser/EditRowDialog.react.js b/src/dashboard/Data/Browser/EditRowDialog.react.js
index 532279bdb1..59d307e454 100644
--- a/src/dashboard/Data/Browser/EditRowDialog.react.js
+++ b/src/dashboard/Data/Browser/EditRowDialog.react.js
@@ -21,10 +21,14 @@ export default class EditRowDialog extends React.Component {
super(props);
const { selectedObject } = this.props;
- const { currentObject, openObjectPickers, expandedTextAreas } = this.initializeState(
- selectedObject
- );
- this.state = { currentObject, openObjectPickers, expandedTextAreas, showFileEditor: null };
+ const { currentObject, openObjectPickers, expandedTextAreas } =
+ this.initializeState(selectedObject);
+ this.state = {
+ currentObject,
+ openObjectPickers,
+ expandedTextAreas,
+ showFileEditor: null,
+ };
this.updateCurrentObject = this.updateCurrentObject.bind(this);
this.handleChange = this.handleChange.bind(this);
@@ -40,9 +44,8 @@ export default class EditRowDialog extends React.Component {
const newSelectedObject = props.selectedObject;
const previousSelectedObject = this.props.selectedObject;
if (newSelectedObject.id !== previousSelectedObject.id) {
- const { currentObject, openObjectPickers, expandedTextAreas } = this.initializeState(
- newSelectedObject
- );
+ const { currentObject, openObjectPickers, expandedTextAreas } =
+ this.initializeState(newSelectedObject);
this.setState({ currentObject, openObjectPickers, expandedTextAreas });
} else if (newSelectedObject.updatedAt !== previousSelectedObject.updatedAt) {
this.updateCurrentObjectFromProps(newSelectedObject);
@@ -61,7 +64,7 @@ export default class EditRowDialog extends React.Component {
// "Parse._encoding" is responsible to convert Parse data into raw data.
// Since array and object are generic types, we want to render them the way
// they were stored in the database.
- let val = encode(currentObject[name], undefined, true);
+ const val = encode(currentObject[name], undefined, true);
const stringifyValue = JSON.stringify(val, null, 4);
currentObject[name] = stringifyValue;
const rows = stringifyValue ? stringifyValue.split('\n').length : 1;
@@ -69,9 +72,7 @@ export default class EditRowDialog extends React.Component {
}
if (type === 'Polygon') {
const stringifyValue = JSON.stringify(
- (currentObject[name] && currentObject[name].coordinates) || [
- ['lat', 'lon']
- ],
+ (currentObject[name] && currentObject[name].coordinates) || [['lat', 'lon']],
null,
4
);
@@ -109,9 +110,7 @@ export default class EditRowDialog extends React.Component {
}
if (type === 'Polygon') {
const stringifyValue = JSON.stringify(
- (newObject[name] && newObject[name].coordinates) || [
- ['lat', 'lon']
- ],
+ (newObject[name] && newObject[name].coordinates) || [['lat', 'lon']],
null,
4
);
@@ -132,26 +131,15 @@ export default class EditRowDialog extends React.Component {
currentObject.password = '';
}
}
- const {
- selectedObject,
- className,
- updateRow,
- confirmAttachSelectedRows,
- useMasterKey
- } = this.props;
+ const { selectedObject, className, updateRow, confirmAttachSelectedRows, useMasterKey } =
+ this.props;
if (type === 'Relation') {
if (toDelete.length > 0) {
selectedObject[name].remove(toDelete);
selectedObject[name].parent.save(null, { useMasterKey });
}
if (newValue.length > 0) {
- confirmAttachSelectedRows(
- className,
- selectedObject.id,
- name,
- newValue,
- targetClass
- );
+ confirmAttachSelectedRows(className, selectedObject.id, name, newValue, targetClass);
}
this.toggleObjectPicker(name, false);
} else {
@@ -159,9 +147,7 @@ export default class EditRowDialog extends React.Component {
const { selectedObject } = this.props;
const { currentObject, expandedTextAreas } = this.state;
const oldStringifyValue = JSON.stringify(
- type === 'Polygon'
- ? selectedObject[name].coordinates
- : selectedObject[name],
+ type === 'Polygon' ? selectedObject[name].coordinates : selectedObject[name],
null,
4
);
@@ -175,7 +161,7 @@ export default class EditRowDialog extends React.Component {
if (type === 'Polygon') {
newValue = {
__type: type,
- coordinates: newValue
+ coordinates: newValue,
};
}
}
@@ -184,9 +170,9 @@ export default class EditRowDialog extends React.Component {
const pointerId = newValue[0];
newValue = pointerId
? Parse.Object.fromJSON({
- className: targetClass,
- objectId: pointerId
- })
+ className: targetClass,
+ objectId: pointerId,
+ })
: undefined;
this.toggleObjectPicker(name, false);
}
@@ -227,13 +213,13 @@ export default class EditRowDialog extends React.Component {
openFileEditor(column) {
this.setState({
- showFileEditor: column
+ showFileEditor: column,
});
}
hideFileEditor() {
this.setState({
- showFileEditor: null
+ showFileEditor: null,
});
}
@@ -244,8 +230,7 @@ export default class EditRowDialog extends React.Component {
const fields = columns.map(column => {
const { name, type, targetClass } = column;
- const isHidden =
- ['objectId', 'createdAt', 'updatedAt', 'ACL'].indexOf(name) >= 0;
+ const isHidden = ['objectId', 'createdAt', 'updatedAt', 'ACL'].indexOf(name) >= 0;
if (isHidden) {
return;
@@ -255,9 +240,7 @@ export default class EditRowDialog extends React.Component {
const isDisabled =
(className === '_User' && ['authData'].indexOf(name) >= 0) ||
- (selectedObject.id &&
- className === '_Role' &&
- ['name'].indexOf(name) >= 0) ||
+ (selectedObject.id && className === '_Role' && ['name'].indexOf(name) >= 0) ||
(className === '_Session' &&
[
'sessionToken',
@@ -265,21 +248,19 @@ export default class EditRowDialog extends React.Component {
'user',
'createdWith',
'installationId',
- 'restricted'
+ 'restricted',
].indexOf(name) >= 0);
- let val = currentObject[name];
+ const val = currentObject[name];
switch (type) {
case 'String':
inputComponent = (
25
- ? true
- : false
- }
+ multiline={currentObject[name] && currentObject[name].length > 25 ? true : false}
disabled={isDisabled}
- placeholder={name === 'password' ? '(hidden)' : val === undefined ? '(undefined)' : ''}
+ placeholder={
+ name === 'password' ? '(hidden)' : val === undefined ? '(undefined)' : ''
+ }
value={currentObject[name]}
onChange={newValue => this.updateCurrentObject(newValue, name)}
onBlur={newValue => this.handleChange(newValue, name)}
@@ -292,8 +273,18 @@ export default class EditRowDialog extends React.Component {
disabled={isDisabled}
value={currentObject[name]}
placeholder={val === undefined ? '(undefined)' : ''}
- onChange={newValue => this.updateCurrentObject(validateNumeric(newValue) ? newValue : currentObject[name], name)}
- onBlur={newValue => this.handleChange(validateNumeric(parseFloat(newValue)) ? parseFloat(newValue) : undefined, name)}
+ onChange={newValue =>
+ this.updateCurrentObject(
+ validateNumeric(newValue) ? newValue : currentObject[name],
+ name
+ )
+ }
+ onBlur={newValue =>
+ this.handleChange(
+ validateNumeric(parseFloat(newValue)) ? parseFloat(newValue) : undefined,
+ name
+ )
+ }
/>
);
break;
@@ -312,15 +303,17 @@ export default class EditRowDialog extends React.Component {
placeholder={val === undefined && '(undefined)'}
value={currentObject[name]}
onChange={newValue => this.updateCurrentObject(newValue, name)}
- onBlur={newValue =>
- this.handleChange(JSON.parse(newValue), name, type)
- }
+ onBlur={newValue => this.handleChange(JSON.parse(newValue), name, type)}
/>
);
break;
case 'Boolean':
inputComponent = isDisabled ? (
-
+
) : (
+
{file &&
}
- this.handleChange(newValue, name, type, targetClass)
- }
- onCancel={() => this.toggleObjectPicker(name, false)}
- useMasterKey={useMasterKey}
- />
+ this.handleChange(newValue, name, type, targetClass)}
+ onCancel={() => this.toggleObjectPicker(name, false)}
+ useMasterKey={useMasterKey}
+ />
) : (
{pointerId && (
@@ -401,38 +392,34 @@ export default class EditRowDialog extends React.Component {
followClick={true}
/>
)}
-
this.toggleObjectPicker(name, true)}
- value={`Select ${name}`}
- />
+ this.toggleObjectPicker(name, true)} value={`Select ${name}`} />
);
break;
case 'Relation':
// fallback if selectedObject is just saved, so it still doesn't have relation properites set
- const relation =
- selectedObject[name] || new Parse.Relation(selectedObject, name);
+ const relation = selectedObject[name] || new Parse.Relation(selectedObject, name);
relation.targetClassName = targetClass;
inputComponent = openObjectPickers[name] ? (
-
- this.handleChange(newValue, name, type, targetClass, toDelete)
- }
- onCancel={() => this.toggleObjectPicker(name, false)}
- useMasterKey={useMasterKey}
- />
+
+ this.handleChange(newValue, name, type, targetClass, toDelete)
+ }
+ onCancel={() => this.toggleObjectPicker(name, false)}
+ useMasterKey={useMasterKey}
+ />
) : (
selectedObject.id && (
` : type}
{expandedTextAreas[name] && expandedTextAreas[name].rows > 3 && (
-
this.toggleExpandTextArea(name)}
- >
+ this.toggleExpandTextArea(name)}>
{expandedTextAreas[name].expanded ? 'collapse' : 'expand'}
)}
@@ -471,12 +455,7 @@ export default class EditRowDialog extends React.Component {
return (
- }
+ label={
}
labelWidth={33}
input={inputComponent}
/>
@@ -504,14 +483,12 @@ export default class EditRowDialog extends React.Component {
{selectedObject.createdAt && (
- CreatedAt{' '}
- {dateStringUTC(selectedObject.createdAt)}
+ CreatedAt {dateStringUTC(selectedObject.createdAt)}
)}
{selectedObject.updatedAt && (
- UpdatedAt{' '}
- {dateStringUTC(selectedObject.updatedAt)}
+ UpdatedAt {dateStringUTC(selectedObject.updatedAt)}
)}
diff --git a/src/dashboard/Data/Browser/Editor.react.js b/src/dashboard/Data/Browser/Editor.react.js
index 30758cd710..c8bfefc161 100644
--- a/src/dashboard/Data/Browser/Editor.react.js
+++ b/src/dashboard/Data/Browser/Editor.react.js
@@ -5,18 +5,18 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import ACLEditor from 'components/ACLEditor/ACLEditor.react';
-import BooleanEditor from 'components/BooleanEditor/BooleanEditor.react';
+import ACLEditor from 'components/ACLEditor/ACLEditor.react';
+import BooleanEditor from 'components/BooleanEditor/BooleanEditor.react';
import DateTimeEditor from 'components/DateTimeEditor/DateTimeEditor.react';
-import FileEditor from 'components/FileEditor/FileEditor.react';
+import FileEditor from 'components/FileEditor/FileEditor.react';
import GeoPointEditor from 'components/GeoPointEditor/GeoPointEditor.react';
-import NumberEditor from 'components/NumberEditor/NumberEditor.react';
-import Parse from 'parse';
-import decode from 'parse/lib/browser/decode';
-import React from 'react';
-import StringEditor from 'components/StringEditor/StringEditor.react';
+import NumberEditor from 'components/NumberEditor/NumberEditor.react';
+import Parse from 'parse';
+import decode from 'parse/lib/browser/decode';
+import React from 'react';
+import StringEditor from 'components/StringEditor/StringEditor.react';
-let Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit, onCancel }) => {
+const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit, onCancel }) => {
let content = null;
if (type === 'String') {
content = (
@@ -26,35 +26,40 @@ let Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit,
multiline={!readonly}
width={width}
onCommit={onCommit}
- resizable={true} />
+ resizable={true}
+ />
);
} else if (type === 'Array' || type === 'Object') {
- let encodeCommit = (json) => {
+ const encodeCommit = json => {
try {
- let obj = decode(JSON.parse(json));
+ const obj = decode(JSON.parse(json));
onCommit(obj);
} catch (e) {
onCommit(value);
}
- }
+ };
content = (
+ onCommit={encodeCommit}
+ />
);
} else if (type === 'Polygon') {
- let encodeCommit = (json) => {
+ const encodeCommit = json => {
try {
- let coordinates = JSON.parse(json);
+ const coordinates = JSON.parse(json);
if (coordinates.length < 3) {
throw 'Polygon must have at least 3 coordinates';
}
if (value && value.coordinates && value.coordinates.length === coordinates.length) {
- let dirty = coordinates.some((coord, index) => {
- if (value.coordinates[index][0] !== coord[0] || value.coordinates[index][1] !== coord[1]) {
+ const dirty = coordinates.some((coord, index) => {
+ if (
+ value.coordinates[index][0] !== coord[0] ||
+ value.coordinates[index][1] !== coord[1]
+ ) {
return true;
}
});
@@ -62,22 +67,23 @@ let Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit,
throw 'No change in coordinates';
}
}
- let obj = {
- '__type': 'Polygon',
- coordinates
- }
+ const obj = {
+ __type: 'Polygon',
+ coordinates,
+ };
onCommit(obj);
} catch (e) {
onCommit(value);
}
- }
+ };
content = (
+ onCommit={encodeCommit}
+ />
);
} else if (type === 'Date') {
if (readonly) {
@@ -86,72 +92,39 @@ let Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit,
value={value ? value.toISOString() : ''}
readonly={true}
width={width}
- onCommit={() => onCommit(value)} />
+ onCommit={() => onCommit(value)}
+ />
);
} else {
- content = (
-
- );
+ content =
;
}
} else if (type === 'Boolean') {
- content = (
-
- );
+ content =
;
} else if (type === 'Number') {
- content = (
-
- );
+ content =
;
} else if (type === 'GeoPoint') {
- content = (
-
- );
+ content =
;
} else if (type === 'File') {
- content = (
-
- );
+ content =
;
} else if (type === 'ACL') {
- content = (
-
- );
+ content =
;
} else if (type === 'Pointer') {
- let encodeCommit = (pointer) => {
+ const encodeCommit = pointer => {
if (pointer.length === 0) {
onCommit(undefined);
} else {
- onCommit(Parse.Object.fromJSON({
- className: targetClass,
- objectId: pointer
- }));
+ onCommit(
+ Parse.Object.fromJSON({
+ className: targetClass,
+ objectId: pointer,
+ })
+ );
}
};
- content = (
-
- );
+ content =
;
}
- return (
-
- {content}
-
- );
+ return
{content}
;
};
export default Editor;
diff --git a/src/dashboard/Data/Browser/ExportDialog.react.js b/src/dashboard/Data/Browser/ExportDialog.react.js
index fec7e64bfd..6f5ec75a0e 100644
--- a/src/dashboard/Data/Browser/ExportDialog.react.js
+++ b/src/dashboard/Data/Browser/ExportDialog.react.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
import { CurrentApp } from 'context/currentApp';
export default class ExportDialog extends React.Component {
@@ -14,12 +14,12 @@ export default class ExportDialog extends React.Component {
constructor() {
super();
this.state = {
- progress: undefined
+ progress: undefined,
};
}
componentWillMount() {
- this.context.getExportProgress().then((progress) => {
+ this.context.getExportProgress().then(progress => {
this.setState({ progress });
});
}
@@ -30,7 +30,7 @@ export default class ExportDialog extends React.Component {
}
let found = false;
if (Array.isArray(this.state.progress)) {
- this.state.progress.forEach((obj) => {
+ this.state.progress.forEach(obj => {
if (obj.id === this.props.className) {
found = true;
}
@@ -40,22 +40,27 @@ export default class ExportDialog extends React.Component {
}
render() {
- let inProgress = this.inProgress();
+ const inProgress = this.inProgress();
return (
- {inProgress ?
- You are currently exporting this class. We'll send you an email when that data is available for you to download.
: null}
+ onConfirm={this.props.onConfirm}
+ >
+ {inProgress ? (
+
+ You are currently exporting this class. We'll send you an email when that data is
+ available for you to download.
+
+ ) : null}
);
}
diff --git a/src/dashboard/Data/Browser/ExportSchemaDialog.react.js b/src/dashboard/Data/Browser/ExportSchemaDialog.react.js
index 0f2d7d84c2..a2b2d91efb 100644
--- a/src/dashboard/Data/Browser/ExportSchemaDialog.react.js
+++ b/src/dashboard/Data/Browser/ExportSchemaDialog.react.js
@@ -5,13 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Option from 'components/Dropdown/Option.react';
-import Toggle from 'components/Toggle/Toggle.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Option from 'components/Dropdown/Option.react';
+import Toggle from 'components/Toggle/Toggle.react';
export default class ExportSchemaDialog extends React.Component {
constructor(props) {
@@ -29,36 +29,51 @@ export default class ExportSchemaDialog extends React.Component {
this.state = {
all: false,
className: props.className,
- classes
+ classes,
};
}
-
render() {
return (
this.props.onConfirm(this.state.className, this.state.all)}>
- {!this.state.all &&
+ onConfirm={() => this.props.onConfirm(this.state.className, this.state.all)}
+ >
+ {!this.state.all && (
}
+ label={ }
input={
this.setState({ className })}>
- {this.state.classes.map(schema => {schema} )}
+ onChange={className => this.setState({ className })}
+ >
+ {this.state.classes.map(schema => (
+
+ {schema}
+
+ ))}
- } />
- }
+ }
+ />
+ )}
}
- input={ {this.setState({all})}} />} />
+ label={ }
+ input={
+ {
+ this.setState({ all });
+ }}
+ />
+ }
+ />
);
}
diff --git a/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js
index 0478c56d71..75d75d3c3c 100644
--- a/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js
+++ b/src/dashboard/Data/Browser/ExportSelectedRowsDialog.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Option from 'components/Dropdown/Option.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import TextInput from 'components/TextInput/TextInput.react';
-import styles from 'dashboard/Data/Browser/ExportSelectedRowsDialog.scss';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Option from 'components/Dropdown/Option.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import TextInput from 'components/TextInput/TextInput.react';
+import styles from 'dashboard/Data/Browser/ExportSelectedRowsDialog.scss';
export default class ExportSelectedRowsDialog extends React.Component {
constructor() {
@@ -37,62 +37,105 @@ export default class ExportSelectedRowsDialog extends React.Component {
}
formatBytes(bytes) {
- if (!+bytes) return '0 Bytes'
-
- const k = 1024
- const decimals = 2
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+ if (!+bytes) {
+ return '0 Bytes';
+ }
- const i = Math.floor(Math.log(bytes) / Math.log(k))
+ const k = 1024;
+ const decimals = 2;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`
-}
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
+ }
render() {
- let selectionLength = Object.keys(this.props.selection).length;
- const fileSize = new TextEncoder().encode(JSON.stringify(this.props.data, null, this.state.exportType === '.json' && this.state.indentation ? 2 : null)).length / this.props.data.length
+ const selectionLength = Object.keys(this.props.selection).length;
+ const fileSize =
+ new TextEncoder().encode(
+ JSON.stringify(
+ this.props.data,
+ null,
+ this.state.exportType === '.json' && this.state.indentation ? 2 : null
+ )
+ ).length / this.props.data.length;
return (
this.props.onConfirm(this.state.exportType, this.state.indentation)}>
- {this.props.selection['*'] &&
- Estimated row count: {this.props.count} Estimated export size: {this.formatBytes(fileSize * this.props.count)} ⚠️ Exporting all rows may severely impact server or database resources. Large datasets are exported as multiple files of up to 1 GB each.}/>
-
- }
+ onConfirm={() => this.props.onConfirm(this.state.exportType, this.state.indentation)}
+ >
+ {this.props.selection['*'] && (
+
+
+ Estimated row count: {this.props.count}
+
+ Estimated export size: {this.formatBytes(fileSize * this.props.count)}
+
+
+ ⚠️ Exporting all rows may severely impact server or database resources.
+
+ Large datasets are exported as multiple files of up to 1 GB each.
+
+ }
+ />
+
+ )}
}
+ label={ }
input={
this.setState({ exportType })}>
- .csv
- .json
+ onChange={exportType => this.setState({ exportType })}
+ >
+ .csv
+ .json
- } />
- {this.state.exportType === '.json' && }
- input={ {this.setState({indentation})}} />} />
- }
- {this.props.selection['*'] &&
}
- input={
- this.setState({ confirmation })} />
- } />
- }
+ />
+ {this.state.exportType === '.json' && (
+ }
+ input={
+ {
+ this.setState({ indentation });
+ }}
+ />
+ }
+ />
+ )}
+ {this.props.selection['*'] && (
+
+ }
+ input={
+ this.setState({ confirmation })}
+ />
+ }
+ />
+ )}
);
}
diff --git a/src/dashboard/Data/Browser/LoginDialog.react.js b/src/dashboard/Data/Browser/LoginDialog.react.js
index 19840d5a19..9a9c612a94 100644
--- a/src/dashboard/Data/Browser/LoginDialog.react.js
+++ b/src/dashboard/Data/Browser/LoginDialog.react.js
@@ -9,7 +9,7 @@ export default class LoginDialog extends React.Component {
this.state = {
open: false,
username: '',
- password: ''
+ password: '',
};
this.handleOpen = this.handleOpen.bind(this);
@@ -78,9 +78,7 @@ export default class LoginDialog extends React.Component {
label="Username"
input={
- this.setState({ username: e.nativeEvent.target.value })
- }
+ onChange={e => this.setState({ username: e.nativeEvent.target.value })}
onKeyDown={this.handleKeyDown}
autoFocus
/>
@@ -90,9 +88,7 @@ export default class LoginDialog extends React.Component {
label="Password"
input={
- this.setState({ password: e.nativeEvent.target.value })
- }
+ onChange={e => this.setState({ password: e.nativeEvent.target.value })}
type="password"
onKeyDown={this.handleKeyDown}
/>
diff --git a/src/dashboard/Data/Browser/Notification.react.js b/src/dashboard/Data/Browser/Notification.react.js
index b09be8d5ab..52f021d580 100644
--- a/src/dashboard/Data/Browser/Notification.react.js
+++ b/src/dashboard/Data/Browser/Notification.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Popover from 'components/Popover/Popover.react';
+import Popover from 'components/Popover/Popover.react';
import Position from 'lib/Position';
-import React from 'react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
+import React from 'react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
export default class Notification extends React.Component {
constructor(props) {
@@ -31,9 +31,16 @@ export default class Notification extends React.Component {
if (this.state.lastNote !== nextProps.note) {
clearTimeout(this.timeout);
if (this.state.hiding) {
- this.setState({ lastNote: nextProps.note, isErrorNote: nextProps.isErrorNote, hiding: false })
+ this.setState({
+ lastNote: nextProps.note,
+ isErrorNote: nextProps.isErrorNote,
+ hiding: false,
+ });
} else {
- this.setState({ lastNote: nextProps.note, isErrorNote: nextProps.isErrorNote });
+ this.setState({
+ lastNote: nextProps.note,
+ isErrorNote: nextProps.isErrorNote,
+ });
}
}
if (!nextProps.note) {
@@ -52,8 +59,8 @@ export default class Notification extends React.Component {
return null;
}
- let bottomRight = new Position(window.innerWidth, window.innerHeight);
- let classes = [];
+ const bottomRight = new Position(window.innerWidth, window.innerHeight);
+ const classes = [];
if (this.state.isErrorNote) {
classes.push(styles.notificationError);
diff --git a/src/dashboard/Data/Browser/ObjectPickerDialog.react.js b/src/dashboard/Data/Browser/ObjectPickerDialog.react.js
index f63746a22f..710939ae44 100644
--- a/src/dashboard/Data/Browser/ObjectPickerDialog.react.js
+++ b/src/dashboard/Data/Browser/ObjectPickerDialog.react.js
@@ -35,12 +35,10 @@ export default class ObjectPickerDialog extends React.Component {
// initial relation ids -> currently saved in database
initialRelationData: [],
selectionInput: '',
- disableDataBrowserKeyControls: false
+ disableDataBrowserKeyControls: false,
};
- this.disableDataBrowserKeyControls = this.disableDataBrowserKeyControls.bind(
- this
- );
+ this.disableDataBrowserKeyControls = this.disableDataBrowserKeyControls.bind(this);
this.fetchNextPage = this.fetchNextPage.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.refresh = this.refresh.bind(this);
@@ -83,7 +81,7 @@ export default class ObjectPickerDialog extends React.Component {
async fetchData(source, filters = new List()) {
const data = await this.fetchParseData(source, filters);
- var filteredCounts = { ...this.state.filteredCounts };
+ const filteredCounts = { ...this.state.filteredCounts };
if (filters.size > 0) {
filteredCounts[source] = await this.fetchParseDataCount(source, filters);
} else {
@@ -93,7 +91,7 @@ export default class ObjectPickerDialog extends React.Component {
data: data,
filters,
lastMax: MAX_ROWS_FETCHED,
- filteredCounts: filteredCounts
+ filteredCounts: filteredCounts,
});
}
@@ -111,7 +109,7 @@ export default class ObjectPickerDialog extends React.Component {
query.limit(MAX_ROWS_FETCHED);
- let promise = query.find({ useMasterKey });
+ const promise = query.find({ useMasterKey });
const data = await promise;
return data;
@@ -128,12 +126,12 @@ export default class ObjectPickerDialog extends React.Component {
if (!this.state.data) {
return null;
}
- let className = this.props.className;
- let source = this.state.relation || className;
+ const className = this.props.className;
+ const source = this.state.relation || className;
let query = queryFromFilters(source, this.state.filters);
if (this.state.ordering !== '-createdAt') {
// Construct complex pagination query
- let equalityQuery = queryFromFilters(source, this.state.filters);
+ const equalityQuery = queryFromFilters(source, this.state.filters);
let field = this.state.ordering;
let ascending = true;
let comp = this.state.data[this.state.data.length - 1].get(field);
@@ -166,10 +164,7 @@ export default class ObjectPickerDialog extends React.Component {
query.descending(this.state.ordering.substr(1));
}
} else {
- query.lessThan(
- 'createdAt',
- this.state.data[this.state.data.length - 1].get('createdAt')
- );
+ query.lessThan('createdAt', this.state.data[this.state.data.length - 1].get('createdAt'));
query.addDescending('createdAt');
}
query.limit(MAX_ROWS_FETCHED);
@@ -178,7 +173,7 @@ export default class ObjectPickerDialog extends React.Component {
query.find({ useMasterKey: useMasterKey }).then(nextPage => {
if (className === this.props.className) {
this.setState(state => ({
- data: state.data.concat(nextPage)
+ data: state.data.concat(nextPage),
}));
}
});
@@ -190,7 +185,7 @@ export default class ObjectPickerDialog extends React.Component {
const { className } = this.props;
this.setState({
filters: filters,
- selection: {}
+ selection: {},
});
await this.fetchData(className, filters);
Object.keys(selection).forEach(id => this.selectRow(id, true));
@@ -201,15 +196,11 @@ export default class ObjectPickerDialog extends React.Component {
const { filters, selection } = this.state;
this.setState({
ordering: ordering,
- selection: {}
+ selection: {},
});
await this.fetchData(className, filters);
Object.keys(selection).forEach(id => this.selectRow(id, true));
- ColumnPreferences.getColumnSort(
- ordering,
- this.context.applicationId,
- className
- );
+ ColumnPreferences.getColumnSort(ordering, this.context.applicationId, className);
}
async refresh() {
@@ -218,7 +209,7 @@ export default class ObjectPickerDialog extends React.Component {
this.setState({
data: null,
lastMax: -1,
- selection: {}
+ selection: {},
});
await this.fetchData(className, filters);
Object.keys(selection).forEach(id => this.selectRow(id, true));
@@ -255,7 +246,7 @@ export default class ObjectPickerDialog extends React.Component {
});
this.setState({
selection: newSelection,
- disableDataBrowserKeyControls: false
+ disableDataBrowserKeyControls: false,
});
}
@@ -284,7 +275,7 @@ export default class ObjectPickerDialog extends React.Component {
ordering,
selection,
selectionInput,
- disableDataBrowserKeyControls
+ disableDataBrowserKeyControls,
} = this.state;
const columns = { objectId: { type: 'String' } };
@@ -300,10 +291,7 @@ export default class ObjectPickerDialog extends React.Component {
columns[name] = info;
});
- const count =
- className in filteredCounts
- ? filteredCounts[className]
- : counts[className];
+ const count = className in filteredCounts ? filteredCounts[className] : counts[className];
return (
this.setState({
selectionInput: newValue,
- disableDataBrowserKeyControls: true
+ disableDataBrowserKeyControls: true,
})
}
onBlur={newValue => this.updateSelectionFromInput(newValue)}
@@ -352,9 +338,7 @@ export default class ObjectPickerDialog extends React.Component {
stylesBrowserFilter.objectPickerContent,
stylesToolbar.objectPickerContent,
stylesColumnsConfiguration.objectPickerContent,
- column.type === 'Pointer'
- ? stylesDataBrowserHeaderBar.pickerPointer
- : ''
+ column.type === 'Pointer' ? stylesDataBrowserHeaderBar.pickerPointer : '',
].join(' ')}
>
@@ -375,7 +359,7 @@ export default class ObjectPickerDialog extends React.Component {
padding: '0',
fontSize: '12px',
marginBottom: '9px',
- fontFamily: 'Source Code Pro, Courier New, monospace'
+ fontFamily: 'Source Code Pro, Courier New, monospace',
}}
onClick={() => this.selectRow(id, false)}
/>
@@ -383,25 +367,25 @@ export default class ObjectPickerDialog extends React.Component {
})}
-
+
);
diff --git a/src/dashboard/Data/Browser/PointerKeyDialog.react.js b/src/dashboard/Data/Browser/PointerKeyDialog.react.js
index f102a514cb..f3fd070c43 100644
--- a/src/dashboard/Data/Browser/PointerKeyDialog.react.js
+++ b/src/dashboard/Data/Browser/PointerKeyDialog.react.js
@@ -5,67 +5,80 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import modalStyles from 'components/Modal/Modal.scss';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import React from 'react';
+import Button from 'components/Button/Button.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import modalStyles from 'components/Modal/Modal.scss';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import React from 'react';
import * as ColumnPreferences from 'lib/ColumnPreferences';
export default class PointerKeyDialog extends React.Component {
constructor() {
super();
this.state = {
- name: null
+ name: null,
};
}
componentDidMount() {
- const pointerKey = ColumnPreferences.getPointerDefaultKey(this.props.app.applicationId, this.props.className);
+ const pointerKey = ColumnPreferences.getPointerDefaultKey(
+ this.props.app.applicationId,
+ this.props.className
+ );
this.setState({ name: pointerKey });
}
render() {
let content = null;
- let hasColumns = this.props.currentColumns.length > 0;
- let currentColumns = [...this.props.currentColumns, 'objectId'];
+ const hasColumns = this.props.currentColumns.length > 0;
+ const currentColumns = [...this.props.currentColumns, 'objectId'];
if (hasColumns) {
content = (
- }
+ label={ }
input={
this.setState({ name: name })}>
- {currentColumns.map((t) => {t} )}
+ onChange={name => this.setState({ name: name })}
+ >
+ {currentColumns.map(t => (
+
+ {t}
+
+ ))}
- } />
- )
+ }
+ />
+ );
}
return (
{
this.props.onConfirm(this.state.name);
}}
- customFooter={hasColumns ? null :
-
-
-
- }>
+ customFooter={
+ hasColumns ? null : (
+
+
+
+ )
+ }
+ >
{content}
);
diff --git a/src/dashboard/Data/Browser/RemoveColumnDialog.react.js b/src/dashboard/Data/Browser/RemoveColumnDialog.react.js
index 38eef79be8..d76cb9eaa9 100644
--- a/src/dashboard/Data/Browser/RemoveColumnDialog.react.js
+++ b/src/dashboard/Data/Browser/RemoveColumnDialog.react.js
@@ -5,61 +5,71 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import modalStyles from 'components/Modal/Modal.scss';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import React from 'react';
+import Button from 'components/Button/Button.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import modalStyles from 'components/Modal/Modal.scss';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import React from 'react';
export default class RemoveColumnDialog extends React.Component {
constructor() {
super();
this.state = {
- name: null
+ name: null,
};
}
render() {
let content = null;
- let hasColumns = this.props.currentColumns.length > 0;
+ const hasColumns = this.props.currentColumns.length > 0;
if (hasColumns) {
content = (
- }
+ label={ }
input={
this.setState({ name: name })}>
- {this.props.currentColumns.sort().map((t) => {t} )}
+ onChange={name => this.setState({ name: name })}
+ >
+ {this.props.currentColumns.sort().map(t => (
+
+ {t}
+
+ ))}
- } />
- )
+ }
+ />
+ );
}
return (
{
this.props.onConfirm(this.state.name);
}}
- customFooter={hasColumns ? null :
-
-
-
- }>
+ customFooter={
+ hasColumns ? null : (
+
+
+
+ )
+ }
+ >
{content}
);
diff --git a/src/dashboard/Data/Browser/SchemaOverview.react.js b/src/dashboard/Data/Browser/SchemaOverview.react.js
index d8fb5f896b..35c0dfdfcc 100644
--- a/src/dashboard/Data/Browser/SchemaOverview.react.js
+++ b/src/dashboard/Data/Browser/SchemaOverview.react.js
@@ -5,16 +5,16 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import CategoryList from 'components/CategoryList/CategoryList.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import React from 'react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import subscribeTo from 'lib/subscribeTo';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { ActionTypes } from 'lib/stores/SchemaStore';
-import { SpecialClasses } from 'lib/Constants';
-import stringCompare from 'lib/stringCompare';
-import prettyNumber from 'lib/prettyNumber';
+import CategoryList from 'components/CategoryList/CategoryList.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import React from 'react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import subscribeTo from 'lib/subscribeTo';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { ActionTypes } from 'lib/stores/SchemaStore';
+import { SpecialClasses } from 'lib/Constants';
+import stringCompare from 'lib/stringCompare';
+import prettyNumber from 'lib/prettyNumber';
export default
@subscribeTo('Schema', 'schema')
@@ -22,8 +22,10 @@ class Browser extends DashboardView {
constructor() {
super();
this.section = 'Core';
- this.subsection = 'Browser'
- this.action = new SidebarAction('Create a class', () => this.setState({ showCreateClassDialog: true }));
+ this.subsection = 'Browser';
+ this.action = new SidebarAction('Create a class', () =>
+ this.setState({ showCreateClassDialog: true })
+ );
this.state = {
counts: {},
@@ -36,12 +38,12 @@ class Browser extends DashboardView {
renderSidebar() {
//TODO: refactor this to share code with Browser.react and actually fetch counts
- let classes = this.props.schema.data.get('classes');
+ const classes = this.props.schema.data.get('classes');
if (!classes) {
return null;
}
- let special = [];
- let categories = [];
+ const special = [];
+ const categories = [];
classes.forEach((value, key) => {
let count = this.state.counts[key];
if (count === undefined) {
@@ -57,17 +59,13 @@ class Browser extends DashboardView {
});
special.sort((a, b) => stringCompare(a.name, b.name));
categories.sort((a, b) => stringCompare(a.name, b.name));
- return (
-
- );
+ return ;
}
renderContent() {
return (
-
+
);
}
diff --git a/src/dashboard/Data/Browser/SecureFieldsDialog.react.js b/src/dashboard/Data/Browser/SecureFieldsDialog.react.js
index 87a878b36c..71c1d26dd8 100644
--- a/src/dashboard/Data/Browser/SecureFieldsDialog.react.js
+++ b/src/dashboard/Data/Browser/SecureFieldsDialog.react.js
@@ -6,23 +6,21 @@
* the root directory of this source tree.
*/
-import Parse from 'parse';
-import React from 'react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import ProtectedFieldsDialog from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.react';
-import { CurrentApp } from 'context/currentApp';
+import Parse from 'parse';
+import React from 'react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import ProtectedFieldsDialog from 'components/ProtectedFieldsDialog/ProtectedFieldsDialog.react';
+import { CurrentApp } from 'context/currentApp';
const pointerPrefix = 'userField:';
function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
if (parseServerSupportsPointerPermissions) {
- let fieldName = text.startsWith(pointerPrefix)
- ? text.substring(pointerPrefix.length)
- : text;
+ const fieldName = text.startsWith(pointerPrefix) ? text.substring(pointerPrefix.length) : text;
if (pointers.includes(fieldName)) {
return Promise.resolve({
entry: pointerPrefix + fieldName,
- type: 'pointer'
+ type: 'pointer',
});
}
}
@@ -44,10 +42,10 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
if (text.startsWith('user:')) {
// no need to query roles
roleQuery = {
- find: () => Promise.resolve([])
+ find: () => Promise.resolve([]),
};
- let user = text.substring(5);
+ const user = text.substring(5);
userQuery = new Parse.Query.or(
new Parse.Query(Parse.User).equalTo('username', user),
new Parse.Query(Parse.User).equalTo('objectId', user)
@@ -55,9 +53,9 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
} else if (text.startsWith('role:')) {
// no need to query users
userQuery = {
- find: () => Promise.resolve([])
+ find: () => Promise.resolve([]),
};
- let role = text.substring(5);
+ const role = text.substring(5);
roleQuery = new Parse.Query.or(
new Parse.Query(Parse.Role).equalTo('name', role),
new Parse.Query(Parse.Role).equalTo('objectId', role)
@@ -77,7 +75,7 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
return Promise.all([
userQuery.find({ useMasterKey: true }),
- roleQuery.find({ useMasterKey: true })
+ roleQuery.find({ useMasterKey: true }),
]).then(([user, role]) => {
if (user.length > 0) {
return { entry: user[0], type: 'user' };
@@ -112,13 +110,13 @@ export default class SecureFieldsDialog extends React.Component {
}
handleClose() {
- this.setState({ open: false },() => this.props.onEditPermissions(false));
+ this.setState({ open: false }, () => this.props.onEditPermissions(false));
}
render() {
let dialog = null;
- let parseServerSupportsPointerPermissions = this.context
- .serverInfo.features.schemas.editClassLevelPermissions;
+ const parseServerSupportsPointerPermissions =
+ this.context.serverInfo.features.schemas.editClassLevelPermissions;
if (this.props.perms && this.state.open) {
dialog = (
+
Learn more about CLPs and app security
}
validateEntry={entry =>
- validateEntry(
- this.props.userPointers,
- entry,
- parseServerSupportsPointerPermissions
- )
+ validateEntry(this.props.userPointers, entry, parseServerSupportsPointerPermissions)
}
onCancel={this.handleClose}
onConfirm={protectedFields => {
- let newPerms = this.props.perms;
+ const newPerms = this.props.perms;
newPerms.protectedFields = protectedFields;
- this.props
- .onChangeCLP(newPerms)
- .then(this.handleClose);
+ this.props.onChangeCLP(newPerms).then(this.handleClose);
}}
/>
);
}
- let classes = [styles.toolbarButton];
+ const classes = [styles.toolbarButton];
if (this.props.disabled) {
classes.push(styles.toolbarButtonDisabled);
}
diff --git a/src/dashboard/Data/Browser/SecurityDialog.react.js b/src/dashboard/Data/Browser/SecurityDialog.react.js
index 6e138b3b91..5945dffabb 100644
--- a/src/dashboard/Data/Browser/SecurityDialog.react.js
+++ b/src/dashboard/Data/Browser/SecurityDialog.react.js
@@ -5,19 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Parse from 'parse'
+import Parse from 'parse';
import PermissionsDialog from 'components/PermissionsDialog/PermissionsDialog.react';
-import React from 'react';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import { CurrentApp } from 'context/currentApp';
+import React from 'react';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import { CurrentApp } from 'context/currentApp';
const pointerPrefix = 'userField:';
function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
- if (parseServerSupportsPointerPermissions) {
- let fieldName = text.startsWith(pointerPrefix)
- ? text.substring(pointerPrefix.length)
- : text;
+ if (parseServerSupportsPointerPermissions) {
+ const fieldName = text.startsWith(pointerPrefix) ? text.substring(pointerPrefix.length) : text;
if (pointers.includes(fieldName)) {
return Promise.resolve({ entry: fieldName, type: 'pointer' });
}
@@ -35,7 +33,7 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
}
if (text.startsWith('user:')) {
- let user = text.substring(5);
+ const user = text.substring(5);
userQuery = new Parse.Query.or(
new Parse.Query(Parse.User).equalTo('username', user),
@@ -43,11 +41,10 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
);
// no need to query roles
roleQuery = {
- find: () => Promise.resolve([])
+ find: () => Promise.resolve([]),
};
-
} else if (text.startsWith('role:')) {
- let role = text.substring(5);
+ const role = text.substring(5);
roleQuery = new Parse.Query.or(
new Parse.Query(Parse.Role).equalTo('name', role),
@@ -55,7 +52,7 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
);
// no need to query users
userQuery = {
- find: () => Promise.resolve([])
+ find: () => Promise.resolve([]),
};
} else {
// query both
@@ -72,7 +69,7 @@ function validateEntry(pointers, text, parseServerSupportsPointerPermissions) {
return Promise.all([
userQuery.find({ useMasterKey: true }),
- roleQuery.find({ useMasterKey: true })
+ roleQuery.find({ useMasterKey: true }),
]).then(([user, role]) => {
if (user.length > 0) {
return { entry: user[0], type: 'user' };
@@ -105,32 +102,37 @@ export default class SecurityDialog extends React.Component {
}
handleClose() {
- this.setState({ open: false },() => this.props.onEditPermissions(false));
+ this.setState({ open: false }, () => this.props.onEditPermissions(false));
}
render() {
let dialog = null;
- let parseServerSupportsPointerPermissions = this.context.serverInfo.features.schemas.editClassLevelPermissions;
+ const parseServerSupportsPointerPermissions =
+ this.context.serverInfo.features.schemas.editClassLevelPermissions;
if (this.props.perms && this.state.open) {
dialog = (
Learn more about CLPs and app security}
+ details={
+
+ Learn more about CLPs and app security
+
+ }
permissions={this.props.perms}
userPointers={this.props.userPointers}
validateEntry={entry =>
- validateEntry(this.props.userPointers, entry, parseServerSupportsPointerPermissions)}
+ validateEntry(this.props.userPointers, entry, parseServerSupportsPointerPermissions)
+ }
onCancel={this.handleClose}
- onConfirm={perms =>
- this.props.onChangeCLP(perms).then(this.handleClose)}
+ onConfirm={perms => this.props.onChangeCLP(perms).then(this.handleClose)}
/>
);
}
- let classes = [styles.toolbarButton];
+ const classes = [styles.toolbarButton];
if (this.props.disabled) {
classes.push(styles.toolbarButtonDisabled);
}
diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js
index 8af0698389..1ee52521ea 100644
--- a/src/dashboard/Data/CloudCode/CloudCode.react.js
+++ b/src/dashboard/Data/CloudCode/CloudCode.react.js
@@ -5,13 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
+import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react';
import DashboardView from 'dashboard/DashboardView.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import FileTree from 'components/FileTree/FileTree.react';
-import React from 'react';
-import styles from 'dashboard/Data/CloudCode/CloudCode.scss';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import FileTree from 'components/FileTree/FileTree.react';
+import React from 'react';
+import styles from 'dashboard/Data/CloudCode/CloudCode.scss';
+import Toolbar from 'components/Toolbar/Toolbar.react';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
@@ -28,7 +28,7 @@ class CloudCode extends DashboardView {
this.state = {
files: undefined,
- source: undefined
+ source: undefined,
};
}
@@ -44,7 +44,7 @@ class CloudCode extends DashboardView {
fetchSource(app, fileName) {
app.getLatestRelease().then(
- (release) => {
+ release => {
this.setState({ files: release.files, source: undefined });
if (!release.files || Object.keys(release.files).length === 0) {
@@ -58,7 +58,7 @@ class CloudCode extends DashboardView {
} else {
// Means we can load /cloud_code/
app.getSource(fileName).then(
- (source) => this.setState({ source: source }),
+ source => this.setState({ source: source }),
() => this.setState({ source: undefined })
);
}
@@ -68,13 +68,13 @@ class CloudCode extends DashboardView {
}
renderSidebar() {
- let current = getPath(this.props.params) || '';
- let files = this.state.files;
+ const current = getPath(this.props.params) || '';
+ const files = this.state.files;
if (!files) {
return null;
}
- let paths = [];
- for (let key in files) {
+ const paths = [];
+ for (const key in files) {
paths.push(key);
}
return (
@@ -83,7 +83,8 @@ class CloudCode extends DashboardView {
+ files={paths}
+ />
);
@@ -92,30 +93,31 @@ class CloudCode extends DashboardView {
renderContent() {
let toolbar = null;
let content = null;
- let fileName = getPath(this.props.params);
+ const fileName = getPath(this.props.params);
if (!this.state.files || Object.keys(this.state.files).length === 0) {
content = (
window.location = 'http://docs.parseplatform.org/cloudcode/guide'} />
+ icon="folder-outline"
+ description={
+ 'When you deploy your cloud code, you\u2019ll be able to see your files here'
+ }
+ cta="Get started with Cloud Code"
+ action={() => (window.location = 'http://docs.parseplatform.org/cloudcode/guide')}
+ />
);
} else {
if (fileName) {
- toolbar = ;
+ toolbar = ;
- let source = this.state.files[fileName];
+ const source = this.state.files[fileName];
if (source && source.source) {
content = (
-
+
);
}
diff --git a/src/dashboard/Data/Config/Config.react.js b/src/dashboard/Data/Config/Config.react.js
index 80f83d08d1..3a4aee1546 100644
--- a/src/dashboard/Data/Config/Config.react.js
+++ b/src/dashboard/Data/Config/Config.react.js
@@ -5,20 +5,21 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { ActionTypes } from 'lib/stores/ConfigStore';
-import Button from 'components/Button/Button.react';
-import ConfigDialog from 'dashboard/Data/Config/ConfigDialog.react';
-import DeleteParameterDialog from 'dashboard/Data/Config/DeleteParameterDialog.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import Icon from 'components/Icon/Icon.react';
-import { isDate } from 'lib/DateUtils';
-import Parse from 'parse';
-import React from 'react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import subscribeTo from 'lib/subscribeTo';
-import TableHeader from 'components/Table/TableHeader.react';
-import TableView from 'dashboard/TableView.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import { ActionTypes } from 'lib/stores/ConfigStore';
+import Button from 'components/Button/Button.react';
+import ConfigDialog from 'dashboard/Data/Config/ConfigDialog.react';
+import DeleteParameterDialog from 'dashboard/Data/Config/DeleteParameterDialog.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import Icon from 'components/Icon/Icon.react';
+import { isDate } from 'lib/DateUtils';
+import Parse from 'parse';
+import React from 'react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import subscribeTo from 'lib/subscribeTo';
+import TableHeader from 'components/Table/TableHeader.react';
+import TableView from 'dashboard/TableView.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import browserStyles from 'dashboard/Data/Browser/Browser.scss';
@subscribeTo('Config', 'config')
class Config extends TableView {
@@ -33,12 +34,12 @@ class Config extends TableView {
modalParam: '',
modalType: 'String',
modalValue: '',
- modalMasterKeyOnly: false
+ modalMasterKeyOnly: false,
};
}
componentWillMount() {
- this.props.config.dispatch(ActionTypes.FETCH);
+ this.loadData();
}
componentWillReceiveProps(nextProps, nextContext) {
@@ -47,12 +48,26 @@ class Config extends TableView {
}
}
+ onRefresh() {
+ this.loadData();
+ }
+
+ loadData() {
+ this.props.config.dispatch(ActionTypes.FETCH);
+ }
+
renderToolbar() {
return (
-
-
+
+
+
+ Refresh
+
+
);
}
@@ -68,14 +83,16 @@ class Config extends TableView {
type={this.state.modalType}
value={this.state.modalValue}
masterKeyOnly={this.state.modalMasterKeyOnly}
- parseServerVersion={this.context.serverInfo?.parseServerVersion} />
+ parseServerVersion={this.context.serverInfo?.parseServerVersion}
+ />
);
} else if (this.state.showDeleteParameterDialog) {
extras = (
this.setState({ showDeleteParameterDialog: false })}
- onConfirm={this.deleteParam.bind(this, this.state.modalParam)} />
+ onConfirm={this.deleteParam.bind(this, this.state.modalParam)}
+ />
);
}
return extras;
@@ -100,7 +117,11 @@ class Config extends TableView {
modalValue = data.value.toJSON();
} else if (data.value instanceof Parse.File) {
type = 'File';
- value = Open in new window ;
+ value = (
+
+ Open in new window
+
+ );
} else {
type = 'Object';
value = JSON.stringify(value);
@@ -112,37 +133,47 @@ class Config extends TableView {
}
type = type.substr(0, 1).toUpperCase() + type.substr(1);
}
- let openModal = () => this.setState({
- modalOpen: true,
- modalParam: data.param,
- modalType: type,
- modalValue: modalValue,
- modalMasterKeyOnly: data.masterKeyOnly
- });
- let columnStyleLarge = { width: '30%', cursor: 'pointer' };
- let columnStyleSmall = { width: '15%', cursor: 'pointer' };
+ const openModal = () =>
+ this.setState({
+ modalOpen: true,
+ modalParam: data.param,
+ modalType: type,
+ modalValue: modalValue,
+ modalMasterKeyOnly: data.masterKeyOnly,
+ });
+ const columnStyleLarge = { width: '30%', cursor: 'pointer' };
+ const columnStyleSmall = { width: '15%', cursor: 'pointer' };
- let openModalValueColumn = () => {
+ const openModalValueColumn = () => {
if (data.value instanceof Parse.File) {
- return
+ return;
}
- openModal()
- }
+ openModal();
+ };
- let openDeleteParameterDialog = () => this.setState({
- showDeleteParameterDialog: true,
- modalParam: data.param
- });
+ const openDeleteParameterDialog = () =>
+ this.setState({
+ showDeleteParameterDialog: true,
+ modalParam: data.param,
+ });
return (
- {data.param}
- {type}
- {value}
- {data.masterKeyOnly.toString()}
+
+ {data.param}
+
+
+ {type}
+
+
+ {value}
+
+
+ {data.masterKeyOnly.toString()}
+
-
+
@@ -151,41 +182,53 @@ class Config extends TableView {
renderHeaders() {
return [
- Parameter ,
- Type ,
- Value ,
- Master key only
+
+ Parameter
+ ,
+
+ Type
+ ,
+
+ Value
+ ,
+
+ Master key only
+ ,
];
}
renderEmpty() {
return (
+ title="Dynamically configure your app"
+ description="Set up parameters that let you control the appearance or behavior of your app."
+ icon="gears"
+ cta="Create your first parameter"
+ action={this.createParameter.bind(this)}
+ />
);
}
tableData() {
let data = undefined;
if (this.props.config.data) {
- let params = this.props.config.data.get('params');
- let masterKeyOnlyParams = this.props.config.data.get('masterKeyOnly') || {};
+ const params = this.props.config.data.get('params');
+ const masterKeyOnlyParams = this.props.config.data.get('masterKeyOnly') || {};
if (params) {
data = [];
params.forEach((value, param) => {
- let masterKeyOnly = masterKeyOnlyParams.get(param) || false;
- let type = typeof value;
+ const masterKeyOnly = masterKeyOnlyParams.get(param) || false;
+ const type = typeof value;
if (type === 'object' && value.__type == 'File') {
value = Parse.File.fromJSON(value);
- }
- else if (type === 'object' && value.__type == 'GeoPoint') {
+ } else if (type === 'object' && value.__type == 'GeoPoint') {
value = new Parse.GeoPoint(value);
}
- data.push({ param: param, value: value, masterKeyOnly: masterKeyOnly })
+ data.push({
+ param: param,
+ value: value,
+ masterKeyOnly: masterKeyOnly,
+ });
});
data.sort((object1, object2) => {
return object1.param.localeCompare(object2.param);
@@ -196,21 +239,24 @@ class Config extends TableView {
}
saveParam({ name, value, masterKeyOnly }) {
- this.props.config.dispatch(
- ActionTypes.SET,
- { param: name, value: value, masterKeyOnly: masterKeyOnly }
- ).then(() => {
- this.setState({ modalOpen: false });
- }, () => {
- // Catch the error
- });
+ this.props.config
+ .dispatch(ActionTypes.SET, {
+ param: name,
+ value: value,
+ masterKeyOnly: masterKeyOnly,
+ })
+ .then(
+ () => {
+ this.setState({ modalOpen: false });
+ },
+ () => {
+ // Catch the error
+ }
+ );
}
deleteParam(name) {
- this.props.config.dispatch(
- ActionTypes.DELETE,
- { param: name }
- ).then(() => {
+ this.props.config.dispatch(ActionTypes.DELETE, { param: name }).then(() => {
this.setState({ showDeleteParameterDialog: false });
});
}
@@ -221,7 +267,7 @@ class Config extends TableView {
modalParam: '',
modalType: 'String',
modalValue: '',
- modalMasterKeyOnly: false
+ modalMasterKeyOnly: false,
});
}
}
diff --git a/src/dashboard/Data/Config/ConfigDialog.react.js b/src/dashboard/Data/Config/ConfigDialog.react.js
index be14f77388..711b66efe0 100644
--- a/src/dashboard/Data/Config/ConfigDialog.react.js
+++ b/src/dashboard/Data/Config/ConfigDialog.react.js
@@ -5,77 +5,56 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import FileInput from 'components/FileInput/FileInput.react';
-import GeoPointInput from 'components/GeoPointInput/GeoPointInput.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import Option from 'components/Dropdown/Option.react';
-import Parse from 'parse';
-import React from 'react';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
+import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import FileInput from 'components/FileInput/FileInput.react';
+import GeoPointInput from 'components/GeoPointInput/GeoPointInput.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import Option from 'components/Dropdown/Option.react';
+import Parse from 'parse';
+import React from 'react';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
import validateNumeric from 'lib/validateNumeric';
-import styles from 'dashboard/Data/Browser/Browser.scss';
-import semver from 'semver/preload.js';
+import styles from 'dashboard/Data/Browser/Browser.scss';
+import semver from 'semver/preload.js';
-const PARAM_TYPES = [
- 'Boolean',
- 'String',
- 'Number',
- 'Date',
- 'Object',
- 'Array',
- 'GeoPoint',
- 'File'
-];
+const PARAM_TYPES = ['Boolean', 'String', 'Number', 'Date', 'Object', 'Array', 'GeoPoint', 'File'];
function numberValidator(onChange) {
- return function(next) {
+ return function (next) {
if (validateNumeric(next)) {
onChange(next);
}
- }
+ };
}
function saveFile(onChange, file) {
- let value = new Parse.File(file.name, file);
+ const value = new Parse.File(file.name, file);
value.save({ useMasterKey: true }).then(() => onChange(value));
}
const EDITORS = {
Boolean: (value, onChange) => (
-
+
),
String: (value, onChange) => (
-
+
),
Number: (value, onChange) => (
-
- ),
- Date: (value, onChange) => (
-
+
),
+ Date: (value, onChange) => ,
Object: (value, onChange) => (
+ onChange={onChange}
+ />
),
Array: (value, onChange) => (
- ),
- GeoPoint: (value, onChange) => (
-
+ onChange={onChange}
+ />
),
+ GeoPoint: (value, onChange) => ,
File: (value, onChange) => (
-
- )
+
+ ),
};
const GET_VALUE = {
- Boolean: (value) => !!value,
- String: (value) => value,
- Number: (value) => parseFloat(value),
- Date: (value) => value,
- Object: (value) => JSON.parse(value),
- Array: (value) => JSON.parse(value),
- GeoPoint: (value) => new Parse.GeoPoint({latitude: value.latitude, longitude: value.longitude}),
- File: (value) => value
+ Boolean: value => !!value,
+ String: value => value,
+ Number: value => parseFloat(value),
+ Date: value => value,
+ Object: value => JSON.parse(value),
+ Array: value => JSON.parse(value),
+ GeoPoint: value =>
+ new Parse.GeoPoint({
+ latitude: value.latitude,
+ longitude: value.longitude,
+ }),
+ File: value => value,
};
export default class ConfigDialog extends React.Component {
@@ -111,14 +96,14 @@ export default class ConfigDialog extends React.Component {
value: null,
type: 'String',
name: '',
- masterKeyOnly: false
+ masterKeyOnly: false,
};
if (props.param.length > 0) {
this.state = {
name: props.param,
type: props.type,
value: props.value,
- masterKeyOnly: props.masterKeyOnly
+ masterKeyOnly: props.masterKeyOnly,
};
}
}
@@ -136,7 +121,7 @@ export default class ConfigDialog extends React.Component {
return !isNaN(new Date(this.state.value));
case 'Object':
try {
- let obj = JSON.parse(this.state.value);
+ const obj = JSON.parse(this.state.value);
if (obj && typeof obj === 'object') {
return true;
}
@@ -146,7 +131,7 @@ export default class ConfigDialog extends React.Component {
}
case 'Array':
try {
- let obj = JSON.parse(this.state.value);
+ const obj = JSON.parse(this.state.value);
if (obj && Array.isArray(obj)) {
return true;
}
@@ -155,19 +140,24 @@ export default class ConfigDialog extends React.Component {
return false;
}
case 'GeoPoint':
- let val = this.state.value;
+ const val = this.state.value;
if (!val || typeof val !== 'object') {
return false;
}
if (isNaN(parseFloat(val.latitude)) || isNaN(parseFloat(val.longitude))) {
return false;
}
- if (parseFloat(val.latitude) > 90.0 || parseFloat(val.latitude) < -90.0 || parseFloat(val.longitude) > 180.0 || parseFloat(val.longitude) < -180.0) {
+ if (
+ parseFloat(val.latitude) > 90.0 ||
+ parseFloat(val.latitude) < -90.0 ||
+ parseFloat(val.longitude) > 180.0 ||
+ parseFloat(val.longitude) < -180.0
+ ) {
return false;
}
return true;
case 'File':
- let fileVal = this.state.value;
+ const fileVal = this.state.value;
if (fileVal && fileVal.url()) {
return true;
}
@@ -180,82 +170,88 @@ export default class ConfigDialog extends React.Component {
this.props.onConfirm({
name: this.state.name,
value: GET_VALUE[this.state.type](this.state.value),
- masterKeyOnly: this.state.masterKeyOnly
+ masterKeyOnly: this.state.masterKeyOnly,
});
}
render() {
- let newParam = !this.props.param;
- let typeDropdown = (
+ const newParam = !this.props.param;
+ const typeDropdown = (
0}
- onChange={(type) => this.setState({ type: type, value: null })}>
- {PARAM_TYPES.map((t) => {t} )}
+ onChange={type => this.setState({ type: type, value: null })}
+ >
+ {PARAM_TYPES.map(t => (
+
+ {t}
+
+ ))}
);
return (
+ onConfirm={this.submit.bind(this)}
+ >
- }
+ label={ }
input={
0}
- onChange={(name) => this.setState({ name })} />
- } />
-
+ onChange={name => this.setState({ name })}
+ />
}
- input={typeDropdown} />
+ />
+ } input={typeDropdown} />
+ text="Value"
+ description="Use this to configure your app. You can change it at any time."
+ />
}
- input={EDITORS[this.state.type](this.state.value, (value) => { this.setState({ value }) })} />
+ input={EDITORS[this.state.type](this.state.value, value => {
+ this.setState({ value });
+ })}
+ />
{
/*
Add `Requires master key` field if parse-server version >= 3.9.0,
that is the minimum version that supports this feature.
*/
- semver.valid(this.props.parseServerVersion) && semver.gte(this.props.parseServerVersion, '3.9.0')
- ?
- }
- input={
- this.setState({ masterKeyOnly })}
- additionalStyles={{ margin: '0px' }} />
- }
- className={styles.addColumnToggleWrapper}
- />
- : null
+ semver.valid(this.props.parseServerVersion) &&
+ semver.gte(this.props.parseServerVersion, '3.9.0') ? (
+
+ }
+ input={
+ this.setState({ masterKeyOnly })}
+ additionalStyles={{ margin: '0px' }}
+ />
+ }
+ className={styles.addColumnToggleWrapper}
+ />
+ ) : null
}
);
diff --git a/src/dashboard/Data/Config/DeleteParameterDialog.react.js b/src/dashboard/Data/Config/DeleteParameterDialog.react.js
index d545b0c777..6cca9b745b 100644
--- a/src/dashboard/Data/Config/DeleteParameterDialog.react.js
+++ b/src/dashboard/Data/Config/DeleteParameterDialog.react.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
import TextInput from 'components/TextInput/TextInput.react';
export default class DeleteParameterDialog extends React.Component {
@@ -16,7 +16,7 @@ export default class DeleteParameterDialog extends React.Component {
super();
this.state = {
- confirmation: ''
+ confirmation: '',
};
}
@@ -28,31 +28,32 @@ export default class DeleteParameterDialog extends React.Component {
}
render() {
- let content = (
+ const content = (
+
}
input={
this.setState({ confirmation })} />
- } />
+ onChange={confirmation => this.setState({ confirmation })}
+ />
+ }
+ />
);
return (
+ onConfirm={this.props.onConfirm}
+ >
{content}
);
diff --git a/src/dashboard/Data/Jobs/JobEdit.react.js b/src/dashboard/Data/Jobs/JobEdit.react.js
index 6e1111ffe4..fda8970553 100644
--- a/src/dashboard/Data/Jobs/JobEdit.react.js
+++ b/src/dashboard/Data/Jobs/JobEdit.react.js
@@ -6,11 +6,11 @@
* the root directory of this source tree.
*/
import { ActionTypes } from 'lib/stores/JobsStore';
-import JobsForm from 'dashboard/Data/Jobs/JobsForm.react';
-import React from 'react';
-import subscribeTo from 'lib/subscribeTo';
-import generatePath from 'lib/generatePath';
-import { CurrentApp } from 'context/currentApp';
+import JobsForm from 'dashboard/Data/Jobs/JobsForm.react';
+import React from 'react';
+import subscribeTo from 'lib/subscribeTo';
+import generatePath from 'lib/generatePath';
+import { CurrentApp } from 'context/currentApp';
import { withRouter } from 'lib/withRouter';
@subscribeTo('Jobs', 'jobs')
@@ -19,11 +19,11 @@ class JobEdit extends React.Component {
static contextType = CurrentApp;
submitForm(changes) {
- let schedule = {
+ const schedule = {
job_schedule: {
params: changes.parameter || '{}',
- daysOfWeek: [1, 1, 1, 1, 1, 1, 1]
- }
+ daysOfWeek: [1, 1, 1, 1, 1, 1, 1],
+ },
};
if (changes.description) {
schedule.job_schedule.description = changes.description;
@@ -49,10 +49,15 @@ class JobEdit extends React.Component {
schedule.job_schedule.repeatMinutes = interval;
}
- let promise = this.props.params.jobId ?
- this.props.jobs.dispatch(ActionTypes.EDIT, { jobId: this.props.params.jobId, updates: schedule }) :
- this.props.jobs.dispatch(ActionTypes.CREATE, { schedule });
- promise.then(() => {this.props.navigate(generatePath(this.context, 'jobs/scheduled'))});
+ const promise = this.props.params.jobId
+ ? this.props.jobs.dispatch(ActionTypes.EDIT, {
+ jobId: this.props.params.jobId,
+ updates: schedule,
+ })
+ : this.props.jobs.dispatch(ActionTypes.CREATE, { schedule });
+ promise.then(() => {
+ this.props.navigate(generatePath(this.context, 'jobs/scheduled'));
+ });
return promise;
}
@@ -63,12 +68,15 @@ class JobEdit extends React.Component {
render() {
if (this.props.params.jobId) {
if (this.props.jobs.data.get('jobs') && this.props.jobs.data.get('jobs').size) {
- let data = this.props.jobs.data.get('jobs').filter((obj) => obj.objectId === this.props.params.jobId).first();
+ const data = this.props.jobs.data
+ .get('jobs')
+ .filter(obj => obj.objectId === this.props.params.jobId)
+ .first();
if (data) {
- let initialFields = {
+ const initialFields = {
description: data.description,
job: data.jobName,
- parameter: data.params
+ parameter: data.params,
};
if (data.repeatMinutes) {
initialFields.repeat = true;
@@ -89,7 +97,7 @@ class JobEdit extends React.Component {
initialFields.runAt = new Date(data.startAfter);
}
if (data.timeOfDay) {
- let split = data.timeOfDay.split(':');
+ const split = data.timeOfDay.split(':');
initialFields.repeatStartHour = split[0] || '12';
if (split[0][0] === '0') {
initialFields.repeatStartHour = split[0].substr(1);
@@ -103,18 +111,14 @@ class JobEdit extends React.Component {
+ initialFields={initialFields}
+ />
);
}
}
return null;
}
- return (
-
- );
+ return ;
}
}
diff --git a/src/dashboard/Data/Jobs/JobScheduleReminder.react.js b/src/dashboard/Data/Jobs/JobScheduleReminder.react.js
index 5dcd744be7..bd2e4b50d7 100644
--- a/src/dashboard/Data/Jobs/JobScheduleReminder.react.js
+++ b/src/dashboard/Data/Jobs/JobScheduleReminder.react.js
@@ -1,5 +1,5 @@
import React from 'react';
-import baseStyles from 'stylesheets/base.scss'
+import baseStyles from 'stylesheets/base.scss';
export default class JobScheduleReminder extends React.Component {
render() {
diff --git a/src/dashboard/Data/Jobs/Jobs.react.js b/src/dashboard/Data/Jobs/Jobs.react.js
index e94a78f595..4fb234977d 100644
--- a/src/dashboard/Data/Jobs/Jobs.react.js
+++ b/src/dashboard/Data/Jobs/Jobs.react.js
@@ -5,38 +5,38 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import { ActionTypes } from 'lib/stores/JobsStore';
-import Button from 'components/Button/Button.react';
-import * as DateUtils from 'lib/DateUtils';
-import CategoryList from 'components/CategoryList/CategoryList.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import Icon from 'components/Icon/Icon.react';
-import JobScheduleReminder from 'dashboard/Data/Jobs/JobScheduleReminder.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
-import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
-import RunNowButton from 'dashboard/Data/Jobs/RunNowButton.react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import StatusIndicator from 'components/StatusIndicator/StatusIndicator.react';
-import styles from 'dashboard/Data/Jobs/Jobs.scss';
-import browserStyles from 'dashboard/Data/Browser/Browser.scss';
-import subscribeTo from 'lib/subscribeTo';
-import TableHeader from 'components/Table/TableHeader.react';
-import TableView from 'dashboard/TableView.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import { ActionTypes } from 'lib/stores/JobsStore';
+import Button from 'components/Button/Button.react';
+import * as DateUtils from 'lib/DateUtils';
+import CategoryList from 'components/CategoryList/CategoryList.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import Icon from 'components/Icon/Icon.react';
+import JobScheduleReminder from 'dashboard/Data/Jobs/JobScheduleReminder.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
+import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
+import RunNowButton from 'dashboard/Data/Jobs/RunNowButton.react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import StatusIndicator from 'components/StatusIndicator/StatusIndicator.react';
+import styles from 'dashboard/Data/Jobs/Jobs.scss';
+import browserStyles from 'dashboard/Data/Browser/Browser.scss';
+import subscribeTo from 'lib/subscribeTo';
+import TableHeader from 'components/Table/TableHeader.react';
+import TableView from 'dashboard/TableView.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
-let subsections = {
+const subsections = {
all: 'All Jobs',
scheduled: 'Scheduled Jobs',
- status: 'Job Status'
+ status: 'Job Status',
};
-let statusColors = {
+const statusColors = {
succeeded: 'green',
failed: 'red',
- running: 'blue'
+ running: 'blue',
};
function scheduleString(data) {
@@ -54,9 +54,17 @@ function scheduleString(data) {
} else {
schedule = 'On ';
}
- let runAt = new Date(data.startAfter);
- schedule += runAt.getUTCMonth() + '/' + runAt.getUTCDate() + '/' + String(runAt.getUTCFullYear()).substr(2);
- schedule += ' at ' + (runAt.getUTCHours() < 10 ? '0' : '') + runAt.getUTCHours() + ':' + (runAt.getUTCMinutes() < 10 ? '0' : '') + runAt.getUTCMinutes() + '.';
+ const runAt = new Date(data.startAfter);
+ schedule +=
+ runAt.getUTCMonth() + '/' + runAt.getUTCDate() + '/' + String(runAt.getUTCFullYear()).substr(2);
+ schedule +=
+ ' at ' +
+ (runAt.getUTCHours() < 10 ? '0' : '') +
+ runAt.getUTCHours() +
+ ':' +
+ (runAt.getUTCMinutes() < 10 ? '0' : '') +
+ runAt.getUTCMinutes() +
+ '.';
return {schedule}
;
}
@@ -96,26 +104,30 @@ class Jobs extends TableView {
}
navigateToJob(jobId) {
- this.props.navigate(generatePath(this.context, `jobs/edit/${jobId}`))
+ this.props.navigate(generatePath(this.context, `jobs/edit/${jobId}`));
}
loadData() {
this.props.jobs.dispatch(ActionTypes.FETCH).finally(() => {
this.setState({ loading: false });
});
- this.context.getJobStatus().then((status) => {
+ this.context.getJobStatus().then(status => {
this.setState({ jobStatus: status });
});
}
renderSidebar() {
- let current = this.props.params.section || '';
+ const current = this.props.params.section || '';
return (
-
+
);
}
@@ -123,7 +135,7 @@ class Jobs extends TableView {
if (this.props.params.section === 'all') {
return (
- {data.jobName}
+ {data.jobName}
@@ -132,28 +144,35 @@ class Jobs extends TableView {
} else if (this.props.params.section === 'scheduled') {
return (
- {data.description}
- {data.jobName}
- {scheduleString(data)}
+ {data.description}
+ {data.jobName}
+ {scheduleString(data)}
- this.navigateToJob(data.objectId)} />
- this.setState({ toDelete: data.objectId })} />
+ this.navigateToJob(data.objectId)} />
+ this.setState({ toDelete: data.objectId })}
+ />
);
} else if (this.props.params.section === 'status') {
return (
- {data.jobName}
- {DateUtils.dateStringUTC(new Date(data.createdAt))}
- {data.finishedAt ? DateUtils.dateStringUTC(new Date(data.finishedAt.iso)) : ''}
-
+ {data.jobName}
+ {DateUtils.dateStringUTC(new Date(data.createdAt))}
+
+ {data.finishedAt ? DateUtils.dateStringUTC(new Date(data.finishedAt.iso)) : ''}
+
+
{data.message}
-
+
@@ -164,30 +183,52 @@ class Jobs extends TableView {
renderHeaders() {
if (this.props.params.section === 'all') {
return [
- Name ,
- Actions ,
+
+ Name
+ ,
+
+ Actions
+ ,
];
} else if (this.props.params.section === 'scheduled') {
return [
- Name ,
- Function ,
- Schedule (UTC) ,
- Actions ,
+
+ Name
+ ,
+
+ Function
+ ,
+
+ Schedule (UTC)
+ ,
+
+ Actions
+ ,
];
} else {
return [
- Function ,
- Started At (UTC) ,
- Finished At (UTC) ,
- Message ,
- Status ,
+
+ Function
+ ,
+
+ Started At (UTC)
+ ,
+
+ Finished At (UTC)
+ ,
+
+ Message
+ ,
+
+ Status
+ ,
];
}
}
renderFooter() {
if (this.props.params.section === 'scheduled') {
- return
+ return ;
}
return null;
@@ -197,28 +238,32 @@ class Jobs extends TableView {
if (this.props.params.section === 'all') {
return (
+ title="Cloud Jobs"
+ description="Define Jobs on parse-server with Parse.Cloud.job()"
+ icon="cloud-happy"
+ />
);
} else if (this.props.params.section === 'scheduled') {
return (
+ title="Cloud Jobs"
+ description={
+
{'On this page you can create JobSchedule objects.'}
-
+
-
}
- icon='cloud-happy' />
+
+ }
+ icon="cloud-happy"
+ />
);
} else {
return (
+ title="Job Status"
+ description="There are no active jobs to show at this time."
+ icon="cloud-unsure"
+ />
);
}
}
@@ -228,15 +273,18 @@ class Jobs extends TableView {
return (
this.setState({ toDelete: null })}
onConfirm={() => {
this.setState({ toDelete: null });
- this.props.jobs.dispatch(ActionTypes.DELETE, { jobId: this.state.toDelete });
- }} />
+ this.props.jobs.dispatch(ActionTypes.DELETE, {
+ jobId: this.state.toDelete,
+ });
+ }}
+ />
);
}
}
@@ -255,13 +303,13 @@ class Jobs extends TableView {
}
}
if (data) {
- data = data.sort().map((jobName) => {
+ data = data.sort().map(jobName => {
return { jobName };
});
}
- } else if (this.props.params.section === 'scheduled' ) {
+ } else if (this.props.params.section === 'scheduled') {
if (this.props.jobs.data) {
- let jobs = this.props.jobs.data.get('jobs');
+ const jobs = this.props.jobs.data.get('jobs');
if (jobs) {
data = jobs.toArray();
}
@@ -285,15 +333,17 @@ class Jobs extends TableView {
if (subsections[this.props.params.section]) {
return (
+ details={ReleaseInfo({ release: this.props.release })}
+ >
-
+
Refresh
- {this.props.availableJobs && this.props.availableJobs.length > 0 ?
- : null}
+ {this.props.availableJobs && this.props.availableJobs.length > 0 ? (
+
+ ) : null}
);
}
diff --git a/src/dashboard/Data/Jobs/JobsData.react.js b/src/dashboard/Data/Jobs/JobsData.react.js
index 441dd7c25b..f06189165a 100644
--- a/src/dashboard/Data/Jobs/JobsData.react.js
+++ b/src/dashboard/Data/Jobs/JobsData.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import { CurrentApp } from 'context/currentApp';
-import { Outlet } from 'react-router-dom';
+import { Outlet } from 'react-router-dom';
export default class JobsData extends React.Component {
static contextType = CurrentApp;
@@ -17,7 +17,7 @@ export default class JobsData extends React.Component {
this.state = {
jobs: undefined,
inUse: undefined,
- release: undefined
+ release: undefined,
};
}
@@ -35,14 +35,15 @@ export default class JobsData extends React.Component {
fetchJobs(app) {
app.getAvailableJobs().then(
({ jobs, in_use }) => {
- let available = [];
+ const available = [];
for (let i = 0; i < jobs.length; i++) {
if (in_use.indexOf(jobs[i]) < 0) {
available.push(jobs[i]);
}
}
- this.setState({ jobs: available, inUse: in_use })
- }, () => this.setState({ jobs: [], inUse: [] })
+ this.setState({ jobs: available, inUse: in_use });
+ },
+ () => this.setState({ jobs: [], inUse: [] })
);
}
@@ -64,7 +65,7 @@ export default class JobsData extends React.Component {
context={{
availableJobs: this.state.jobs,
jobsInUse: this.state.inUse,
- release: this.state.release
+ release: this.state.release,
}}
/>
);
diff --git a/src/dashboard/Data/Jobs/JobsForm.react.js b/src/dashboard/Data/Jobs/JobsForm.react.js
index 643a957ba6..1ffaf4d612 100644
--- a/src/dashboard/Data/Jobs/JobsForm.react.js
+++ b/src/dashboard/Data/Jobs/JobsForm.react.js
@@ -5,25 +5,25 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DashboardView from 'dashboard/DashboardView.react';
-import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import IntervalInput from 'components/IntervalInput/IntervalInput.react';
-import JobScheduleReminder from 'dashboard/Data/Jobs/JobScheduleReminder.react';
-import Label from 'components/Label/Label.react';
-import Option from 'components/Dropdown/Option.react';
-import pluralize from 'lib/pluralize';
-import React from 'react';
-import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
-import styles from 'dashboard/Data/Jobs/Jobs.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import TimeInput from 'components/TimeInput/TimeInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { hoursFrom, dateStringUTC } from 'lib/DateUtils';
+import DashboardView from 'dashboard/DashboardView.react';
+import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import IntervalInput from 'components/IntervalInput/IntervalInput.react';
+import JobScheduleReminder from 'dashboard/Data/Jobs/JobScheduleReminder.react';
+import Label from 'components/Label/Label.react';
+import Option from 'components/Dropdown/Option.react';
+import pluralize from 'lib/pluralize';
+import React from 'react';
+import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
+import styles from 'dashboard/Data/Jobs/Jobs.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import TimeInput from 'components/TimeInput/TimeInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { hoursFrom, dateStringUTC } from 'lib/DateUtils';
export default class JobsForm extends DashboardView {
constructor(props) {
@@ -34,7 +34,7 @@ export default class JobsForm extends DashboardView {
initialChanges() {
if (this.props.initialFields.job) {
- let changes = {};
+ const changes = {};
if (!this.props.initialFields.repeatType) {
changes.repeatType = 'daily';
}
@@ -63,7 +63,7 @@ export default class JobsForm extends DashboardView {
repeatStartHour: '12',
repeatStartMinute: '00',
intervalCount: 15,
- intervalUnit: 'minute'
+ intervalUnit: 'minute',
};
}
@@ -71,25 +71,27 @@ export default class JobsForm extends DashboardView {
if (!fields.repeat) {
return null;
}
- let rows = [
+ const rows = [
}
+ key="repeatType"
+ label={ }
input={
- } />
+ optionLeft="on an interval"
+ optionRight="daily"
+ onChange={setField.bind(null, 'repeatType')}
+ />
+ }
+ />,
];
if (fields.repeatType === 'daily') {
rows.push(
}
+ label={ }
input={
{
setField('repeatStartHour', hour);
setField('repeatStartMinute', minute);
- }} />
- } />
+ }}
+ />
+ }
+ />
);
} else {
rows.push(
}
- input={ {
- setField('intervalCount', count);
- setField('intervalUnit', unit);
- }} />} />
+ label={ }
+ input={
+ {
+ setField('intervalCount', count);
+ setField('intervalUnit', unit);
+ }}
+ />
+ }
+ />
);
rows.push(
}
+ label={ }
input={
{
setField('repeatStartHour', hour);
setField('repeatStartMinute', minute);
- }} />
- } />
- )
+ }}
+ />
+ }
+ />
+ );
}
return rows;
}
@@ -142,119 +152,163 @@ export default class JobsForm extends DashboardView {
+ legend="Pick a Job"
+ description="Choose a job from your cloud code, and specify the parameters to run it with"
+ >
}
- input={
- } />
+ label={
+
+ }
+ input={
+
+ }
+ />
}
+ label={ }
input={
- {jobs.map((job) => {job} )}
+ {jobs.map(job => (
+
+ {job}
+
+ ))}
- } />
+ }
+ />
}
- input={
- } />
+ label={
+
+ }
+ input={
+
+ }
+ />
- {this.props.initialFields.job ? null :
+ legend="Schedule a time"
+ description="Choose when this job runs and how often it repeats"
+ >
+ {this.props.initialFields.job ? null : (
}
- input={ } />}
- {fields.immediate ? null : }
- input={
-
- } />}
+ label={ }
+ input={
+
+ }
+ />
+ )}
+ {fields.immediate ? null : (
+ }
+ input={
+
+ }
+ />
+ )}
}
- input={ } />
+ label={
+
+ }
+ input={ }
+ />
{this.renderRepeatFields(fields, setField)}
+ section="Jobs"
+ subsection="Schedule a Job"
+ details={ReleaseInfo({ release: this.props.release })}
+ />
);
}
renderContent() {
- return changes.job !== ''}
- submitText='Schedule'
- onSubmit={({fields}) => this.props.submitForm(fields)}
- inProgressText={'Scheduling\u2026'}
- validate={({fields}) => {
- //Don't even display the footer if they haven't selected a name or function.
- if (!fields.job.length && !fields.description.length) {
- return '';
- }
- let errorMessages = [];
- if (!fields.description.length) {
- errorMessages.push('A description is required.');
- }
- if (!fields.job.length) {
- errorMessages.push('Pick a cloud code job to run.');
- }
- //Allow them to submit no params
- if (fields.parameter !== '') {
- try {
- JSON.parse(fields.parameter);
- } catch (e) {
- errorMessages.push('Parameters must be valid JSON.');
+ return (
+ changes.job !== ''}
+ submitText="Schedule"
+ onSubmit={({ fields }) => this.props.submitForm(fields)}
+ inProgressText={'Scheduling\u2026'}
+ validate={({ fields }) => {
+ //Don't even display the footer if they haven't selected a name or function.
+ if (!fields.job.length && !fields.description.length) {
+ return '';
}
- }
- return errorMessages.join(' ');
- }}
- footerContents={({fields}) => {
- let pieces = [];
- pieces.push({fields.job} , ' will run ');
- if (fields.immediate) {
- pieces.push(immediately , '.')
- } else {
- pieces.push('on ', {dateStringUTC(fields.runAt)} , '.');
- }
- if (fields.repeat) {
- pieces.push(' It will repeat ');
- if (fields.repeatType === 'daily') {
- pieces.push(
- every day ,
- ' at ',
- {fields.repeatStartHour}:{fields.repeatStartMinute} UTC
- );
+ const errorMessages = [];
+ if (!fields.description.length) {
+ errorMessages.push('A description is required.');
+ }
+ if (!fields.job.length) {
+ errorMessages.push('Pick a cloud code job to run.');
+ }
+ //Allow them to submit no params
+ if (fields.parameter !== '') {
+ try {
+ JSON.parse(fields.parameter);
+ } catch (e) {
+ errorMessages.push('Parameters must be valid JSON.');
+ }
+ }
+ return errorMessages.join(' ');
+ }}
+ footerContents={({ fields }) => {
+ const pieces = [];
+ pieces.push({fields.job} , ' will run ');
+ if (fields.immediate) {
+ pieces.push(immediately , '.');
} else {
- pieces.push(
- {'every ' + pluralize(fields.intervalCount, fields.intervalUnit)} ,
- ' after ',
- {fields.repeatStartHour}:{fields.repeatStartMinute} UTC
- );
+ pieces.push('on ', {dateStringUTC(fields.runAt)} , '.');
}
- }
- return pieces;
- }}
- />;
+ if (fields.repeat) {
+ pieces.push(' It will repeat ');
+ if (fields.repeatType === 'daily') {
+ pieces.push(
+ every day ,
+ ' at ',
+
+ {fields.repeatStartHour}:{fields.repeatStartMinute} UTC
+
+ );
+ } else {
+ pieces.push(
+ {'every ' + pluralize(fields.intervalCount, fields.intervalUnit)} ,
+ ' after ',
+
+ {fields.repeatStartHour}:{fields.repeatStartMinute} UTC
+
+ );
+ }
+ }
+ return pieces;
+ }}
+ />
+ );
}
}
diff --git a/src/dashboard/Data/Jobs/RunNowButton.react.js b/src/dashboard/Data/Jobs/RunNowButton.react.js
index fea64bca6b..7e26f572eb 100644
--- a/src/dashboard/Data/Jobs/RunNowButton.react.js
+++ b/src/dashboard/Data/Jobs/RunNowButton.react.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import React from 'react';
+import Button from 'components/Button/Button.react';
+import React from 'react';
import { CurrentApp } from 'context/currentApp';
export default class RunNowButton extends React.Component {
@@ -16,7 +16,7 @@ export default class RunNowButton extends React.Component {
this.state = {
progress: null,
- result: null
+ result: null,
};
this.timeout = null;
@@ -28,17 +28,20 @@ export default class RunNowButton extends React.Component {
handleClick() {
this.setState({ progress: true });
- this.context.runJob(this.props.job).then(() => {
- this.setState({ progress: false, result: 'success' });
- this.timeout = setTimeout(() => this.setState({ result: null }), 3000);
- }, () => {
- this.setState({ progress: false, result: 'error' });
- this.timeout = setTimeout(() => this.setState({ result: null }), 3000);
- });
+ this.context.runJob(this.props.job).then(
+ () => {
+ this.setState({ progress: false, result: 'success' });
+ this.timeout = setTimeout(() => this.setState({ result: null }), 3000);
+ },
+ () => {
+ this.setState({ progress: false, result: 'error' });
+ this.timeout = setTimeout(() => this.setState({ result: null }), 3000);
+ }
+ );
}
render() {
- let { ...other } = this.props;
+ const { ...other } = this.props;
let value = 'Run now';
if (this.state.result === 'error') {
value = 'Failed.';
@@ -51,7 +54,8 @@ export default class RunNowButton extends React.Component {
onClick={this.handleClick.bind(this)}
color={this.state.result === 'error' ? 'red' : 'blue'}
value={value}
- {...other} />
+ {...other}
+ />
);
}
}
diff --git a/src/dashboard/Data/Logs/Logs.react.js b/src/dashboard/Data/Logs/Logs.react.js
index 27ce8eb8f6..f78ea680bf 100644
--- a/src/dashboard/Data/Logs/Logs.react.js
+++ b/src/dashboard/Data/Logs/Logs.react.js
@@ -5,21 +5,21 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import CategoryList from 'components/CategoryList/CategoryList.react';
+import CategoryList from 'components/CategoryList/CategoryList.react';
import DashboardView from 'dashboard/DashboardView.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import LogView from 'components/LogView/LogView.react';
-import LogViewEntry from 'components/LogView/LogViewEntry.react';
-import React from 'react';
-import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import LogView from 'components/LogView/LogView.react';
+import LogViewEntry from 'components/LogView/LogViewEntry.react';
+import React from 'react';
+import ReleaseInfo from 'components/ReleaseInfo/ReleaseInfo';
+import Toolbar from 'components/Toolbar/Toolbar.react';
-import styles from 'dashboard/Data/Logs/Logs.scss';
+import styles from 'dashboard/Data/Logs/Logs.scss';
import { withRouter } from 'lib/withRouter';
-let subsections = {
+const subsections = {
info: 'Info',
- error: 'Error'
+ error: 'Error',
};
@withRouter
@@ -31,7 +31,7 @@ class Logs extends DashboardView {
this.state = {
logs: undefined,
- release: undefined
+ release: undefined,
};
}
@@ -48,9 +48,9 @@ class Logs extends DashboardView {
}
fetchLogs(app, type) {
- let typeParam = (type || 'INFO').toUpperCase();
+ const typeParam = (type || 'INFO').toUpperCase();
app.getLogs(typeParam).then(
- (logs) => this.setState({ logs }),
+ logs => this.setState({ logs }),
() => this.setState({ logs: [] })
);
}
@@ -68,12 +68,16 @@ class Logs extends DashboardView {
*/
renderSidebar() {
- let current = this.props.params.type || '';
+ const current = this.props.params.type || '';
return (
-
+
);
}
@@ -82,10 +86,10 @@ class Logs extends DashboardView {
if (subsections[this.props.params.type]) {
toolbar = (
-
+ details={ReleaseInfo({ release: this.state.release })}
+ >
);
}
let content = null;
@@ -95,21 +99,21 @@ class Logs extends DashboardView {
content = (
window.location = 'http://docs.parseplatform.org/cloudcode/guide'} />
+ icon="files-outline"
+ title="No logs in the last 30 days"
+ description="When you start using Cloud Code, your logs will show up here."
+ cta="Learn more"
+ action={() => (window.location = 'http://docs.parseplatform.org/cloudcode/guide')}
+ />
);
} else {
content = (
- {this.state.logs.map(({ message, timestamp }) => )}
+ {this.state.logs.map(({ message, timestamp }) => (
+
+ ))}
);
diff --git a/src/dashboard/Data/Migration/Migration.react.js b/src/dashboard/Data/Migration/Migration.react.js
index ce14d91678..6f01746c54 100644
--- a/src/dashboard/Data/Migration/Migration.react.js
+++ b/src/dashboard/Data/Migration/Migration.react.js
@@ -5,20 +5,20 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Button from 'components/Button/Button.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import Icon from 'components/Icon/Icon.react';
-import LiveReload from 'components/LiveReload/LiveReload.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import MigrationStep from 'dashboard/Data/Migration/MigrationStep.react';
-import Modal from 'components/Modal/Modal.react';
-import prettyNumber from 'lib/prettyNumber';
-import React from 'react';
-import styles from 'dashboard/Data/Migration/Migration.scss';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { AsyncStatus } from 'lib/Constants';
+import baseStyles from 'stylesheets/base.scss';
+import Button from 'components/Button/Button.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import Icon from 'components/Icon/Icon.react';
+import LiveReload from 'components/LiveReload/LiveReload.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import MigrationStep from 'dashboard/Data/Migration/MigrationStep.react';
+import Modal from 'components/Modal/Modal.react';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import styles from 'dashboard/Data/Migration/Migration.scss';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { AsyncStatus } from 'lib/Constants';
const MIGRATION_INVALID = 0;
const MIGRATION_NOTSTARTED = 1;
@@ -30,11 +30,14 @@ const MIGRATION_DONE = 6;
const MIGRATION_FATALED = 7;
const MIGRATION_STOPPED = 8;
-let StatusBarNote = ({note, value}) =>
- {note}{value}
- ;
+const StatusBarNote = ({ note, value }) => (
+
+ {note}
+ {value}
+
+);
-let StatusBar = ({
+const StatusBar = ({
errorMessage,
rowsMigrated,
classesMigrated,
@@ -42,76 +45,88 @@ let StatusBar = ({
secondsRemainingStr,
detailsVisible,
}) => {
- let classes = [styles.statusBar, detailsVisible ? styles.statusBottomCorners : ''];
+ const classes = [styles.statusBar, detailsVisible ? styles.statusBottomCorners : ''];
if (errorMessage) {
classes.push(styles.migrationStatusError);
- return {errorMessage}
+ return {errorMessage}
;
}
- return
-
-
-
-
-
-}
+ return (
+
+
+
+
+
+
+ );
+};
-let ClassProgressBar = ({ job, last }) => {
- let percentComplete = 100 * job.InsertPosition / (job.ExpectedDocumentMinimum || job.ExpectedDocumentsApprox);
+const ClassProgressBar = ({ job, last }) => {
+ const percentComplete =
+ (100 * job.InsertPosition) / (job.ExpectedDocumentMinimum || job.ExpectedDocumentsApprox);
let progressDiv = null;
let icon = null;
switch (job.State) {
case MIGRATION_INITIALSYNC:
- progressDiv =
+ progressDiv = (
+
+ );
break;
case MIGRATION_DONE:
- progressDiv =
- icon = ;
+ progressDiv = (
+
+ );
+ icon = ;
break;
case MIGRATION_FATALED:
- progressDiv =
- icon = ;
+ progressDiv = (
+
+ );
+ icon = ;
}
- return
-
- {job.Name}
-
-
-
-
- {progressDiv}
+ return (
+
+
+ {job.Name}
+
+
-
-
- {job.Name}
-
-
;
-}
+ );
+};
export default class Migration extends DashboardView {
constructor() {
super();
this.section = 'Core';
- this.subsection = 'Migration'
+ this.subsection = 'Migration';
this.state = {
stoppingState: AsyncStatus.WAITING,
@@ -122,214 +137,284 @@ export default class Migration extends DashboardView {
}
renderContent() {
- return
-
this.context.getMigrations()}
- render={migration => {
- if (migration === undefined) {
- return ;
- }
- let moreDetails = null;
- let moreDetailsButton =
- this.setState({showDetails: !this.state.showDetails})} />
-
+ return (
+
+
this.context.getMigrations()}
+ render={migration => {
+ if (migration === undefined) {
+ return (
+
+
+
+ );
+ }
+ let moreDetails = null;
+ const moreDetailsButton = (
+
+ this.setState({ showDetails: !this.state.showDetails })}
+ />
+
+ );
- let showStopMigrationButton = false;
- let stopMigrationButton =
- {
- this.setState({stoppingState: AsyncStatus.PROGRESS});
- //No need to handle failure of this request becase it's really rare and it doesn't really matter if the user doesn't realize it failed.
- this.context.stopMigration().finally(() => {
- this.setState({stoppingState: AsyncStatus.WAITING});
- });
- }}/>
-
;
+ let showStopMigrationButton = false;
+ const stopMigrationButton = (
+
+ {
+ this.setState({ stoppingState: AsyncStatus.PROGRESS });
+ //No need to handle failure of this request becase it's really rare and it doesn't really matter if the user doesn't realize it failed.
+ this.context.stopMigration().finally(() => {
+ this.setState({ stoppingState: AsyncStatus.WAITING });
+ });
+ }}
+ />
+
+ );
- let showFinalizeButton = false;
- let finalizeButton =
- this.setState({commitDialogOpen: true})} />
-
+ let showFinalizeButton = false;
+ const finalizeButton = (
+
+ this.setState({ commitDialogOpen: true })}
+ />
+
+ );
- let collectionJobs = this.context.migration.collectionJobs;
- let copyState = AsyncStatus.WAITING;
- let syncState = AsyncStatus.WAITING;
- let verifyState = AsyncStatus.WAITING;
+ const collectionJobs = this.context.migration.collectionJobs;
+ let copyState = AsyncStatus.WAITING;
+ let syncState = AsyncStatus.WAITING;
+ let verifyState = AsyncStatus.WAITING;
- let longStateDescription = '';
- switch (this.context.migration.migrationState) {
- case MIGRATION_NOTSTARTED:
- showStopMigrationButton = true;
- showFinalizeButton = true;
- break;
- case MIGRATION_STOPPED:
- case MIGRATION_INITIALSYNC:
- longStateDescription =
-
-
- We are copying a snapshot of your Parse hosted database into your MongoDB instance at {this.context.migration.destination} .
-
-
- This could take a while, depending on the amount of data. During this phase, your app continues to read and write to the Parse hosted database.
-
-
- showStopMigrationButton = true;
- showFinalizeButton = true;
- copyState = AsyncStatus.PROGRESS;
- //Even after the migration has entered INITIAL_SYNC, the collection jobs might not be created.
- if (collectionJobs) {
- moreDetails =
-
-
{this.context.migration.source}
- {/* padding */}
-
-
-
{this.context.migration.destination}
+ let longStateDescription = '';
+ switch (this.context.migration.migrationState) {
+ case MIGRATION_NOTSTARTED:
+ showStopMigrationButton = true;
+ showFinalizeButton = true;
+ break;
+ case MIGRATION_STOPPED:
+ case MIGRATION_INITIALSYNC:
+ longStateDescription = (
+
+
+ We are copying a snapshot of your Parse hosted database into your MongoDB
+ instance at{' '}
+
+ {this.context.migration.destination}
+
+ .
+
+
+ This could take a while, depending on the amount of data. During this phase,
+ your app continues to read and write to the Parse hosted database.
+
- {Object.keys(collectionJobs).map((mongoKey, index, array) =>
)}
-
;
- }
- break;
- case MIGRATION_OPLOGREPLAY:
- longStateDescription =
-
-
- The snapshot copy has been completed, and we are now syncing any new data since the snapshot into your MongoDB instance at {this.context.migration.destination} .
-
-
- This could take a while, depending on the amount of new data. During this phase, your app continues to read and write to the Parse hosted database.
-
-
+ );
+ showStopMigrationButton = true;
+ showFinalizeButton = true;
+ copyState = AsyncStatus.PROGRESS;
+ //Even after the migration has entered INITIAL_SYNC, the collection jobs might not be created.
+ if (collectionJobs) {
+ moreDetails = (
+
+
+
+ {this.context.migration.source}
+
+ {/* padding */}
+
+
+
+ {this.context.migration.destination}
+
+
+ {Object.keys(collectionJobs).map((mongoKey, index, array) => (
+
+ ))}
+
+ );
+ }
+ break;
+ case MIGRATION_OPLOGREPLAY:
+ longStateDescription = (
+
+
+ The snapshot copy has been completed, and we are now syncing any new data
+ since the snapshot into your MongoDB instance at{' '}
+
+ {this.context.migration.destination}
+
+ .
+
+
+ This could take a while, depending on the amount of new data. During this
+ phase, your app continues to read and write to the Parse hosted database.
+
+
+ );
- showStopMigrationButton = true;
- showFinalizeButton = true;
- copyState = AsyncStatus.SUCCESS;
- syncState = AsyncStatus.PROGRESS;
- break;
- case MIGRATION_COMMITREADY:
- longStateDescription =
-
-
- Your MongoDB instance at {this.context.migration.destination} is now in sync. Browse through the data to make sure your data looks correct.
-
-
- During this phase, your app continues to read and write to the Parse hosted database. When you are satisfied, you can finalize your migration and all reads and writes will now go to your MongoDB instance.
+ showStopMigrationButton = true;
+ showFinalizeButton = true;
+ copyState = AsyncStatus.SUCCESS;
+ syncState = AsyncStatus.PROGRESS;
+ break;
+ case MIGRATION_COMMITREADY:
+ longStateDescription = (
+
+
+ Your MongoDB instance at{' '}
+
+ {this.context.migration.destination}
+ {' '}
+ is now in sync. Browse through the data to make sure your data looks correct.
+
+
+ During this phase, your app continues to read and write to the Parse hosted
+ database. When you are satisfied, you can finalize your migration and all
+ reads and writes will now go to your MongoDB instance.
+
+
+ );
+ showStopMigrationButton = true;
+ showFinalizeButton = true;
+ copyState = AsyncStatus.SUCCESS;
+ syncState = AsyncStatus.SUCCESS;
+ verifyState = AsyncStatus.PROGRESS;
+ break;
+ case MIGRATION_FINISH:
+ case MIGRATION_DONE:
+ copyState = AsyncStatus.SUCCESS;
+ syncState = AsyncStatus.SUCCESS;
+ verifyState = AsyncStatus.SUCCESS;
+ break;
+ case MIGRATION_INVALID:
+ case MIGRATION_FATALED:
+ showFinalizeButton = true;
+ copyState = AsyncStatus.FAILED;
+ syncState = AsyncStatus.FAILED;
+ verifyState = AsyncStatus.FAILED;
+ break;
+ }
+ let errorMessage = null;
+ switch (this.context.migration.wellKnownError) {
+ case 1:
+ errorMessage = 'This is an error state.';
+ break;
+ default:
+ errorMessage =
+ this.context.migration.migrationState === MIGRATION_INITIALSYNC ? null : ' ';
+ break;
+ }
+
+ return (
+
+
CURRENT PROGRESS
+
+
+
+
+
+ {this.state.showDetails ? moreDetails : null}
+ {longStateDescription ? (
+
{longStateDescription}
+ ) : null}
+ {showStopMigrationButton ? stopMigrationButton : null}
+ {moreDetails ? moreDetailsButton : null}
+ {showFinalizeButton ? finalizeButton : null}
- showStopMigrationButton = true;
- showFinalizeButton = true;
- copyState = AsyncStatus.SUCCESS;
- syncState = AsyncStatus.SUCCESS;
- verifyState = AsyncStatus.PROGRESS;
- break;
- case MIGRATION_FINISH:
- case MIGRATION_DONE:
- copyState = AsyncStatus.SUCCESS;
- syncState = AsyncStatus.SUCCESS;
- verifyState = AsyncStatus.SUCCESS;
- break;
- case MIGRATION_INVALID:
- case MIGRATION_FATALED:
- showFinalizeButton = true;
- copyState = AsyncStatus.FAILED;
- syncState = AsyncStatus.FAILED;
- verifyState = AsyncStatus.FAILED;
- break;
- }
- let errorMessage = null;
- switch (this.context.migration.wellKnownError) {
- case 1:
- errorMessage = 'This is an error state.';
- break;
- default:
- errorMessage = this.context.migration.migrationState === MIGRATION_INITIALSYNC ? null : ' ';
- break;
- }
-
- return
-
CURRENT PROGRESS
-
-
-
-
+ );
+ }}
+ />
+
+ {this.state.commitDialogOpen ? (
+
this.setState({ commitDialogOpen: false })}
+ onConfirm={() => {
+ this.refs.reloaderView.abortXHR();
+ this.setState({ commitingState: AsyncStatus.PROGRESS });
+ this.context
+ .commitMigration()
+ .then(() => {
+ return this.refs.reloaderView.fetchNewData();
+ })
+ .then(() => {
+ this.setState({
+ commitingState: AsyncStatus.SUCCESS,
+ commitDialogOpen: false,
+ });
+ })
+ .catch(() => {
+ this.setState({ commitingState: AsyncStatus.FAILED });
+ });
+ }}
+ progress={this.state.commitingState === AsyncStatus.PROGRESS}
+ buttonsInCenter={true}
+ >
+
+ After you commit to using your new database, you cannot switch back to using a Parse
+ managed database! You will be responsible for your own imports, exports, backups,
+ indexes, monitoring, and other database administration. Are you sure you want to
+ continue?
-
- {this.state.showDetails ? moreDetails : null}
- {longStateDescription ? {longStateDescription}
: null}
- {showStopMigrationButton ? stopMigrationButton : null}
- {moreDetails ? moreDetailsButton : null}
- {showFinalizeButton ? finalizeButton : null}
-
- }}/>
-
- {this.state.commitDialogOpen ?
this.setState({commitDialogOpen: false})}
- onConfirm={() => {
- this.refs.reloaderView.abortXHR();
- this.setState({commitingState: AsyncStatus.PROGRESS});
- this.context.commitMigration().then(() => {
- return this.refs.reloaderView.fetchNewData();
- }).then(() => {
- this.setState({
- commitingState: AsyncStatus.SUCCESS,
- commitDialogOpen: false,
- });
- }).catch(() => {
- this.setState({commitingState: AsyncStatus.FAILED});
- });
- }}
- progress={this.state.commitingState === AsyncStatus.PROGRESS}
- buttonsInCenter={true}>
- After you commit to using your new database, you cannot switch back to using a Parse managed database! You will be responsible for your own imports, exports, backups, indexes, monitoring, and other database administration. Are you sure you want to continue?
-
- We were unable to commit your migration. Please try again.
-
- : null}
-
;
- }
+
+ We were unable to commit your migration. Please try again.
+
+
+ ) : null}
+
+ );
+ }
}
diff --git a/src/dashboard/Data/Migration/MigrationStep.react.js b/src/dashboard/Data/Migration/MigrationStep.react.js
index 65ff2b6c9e..03b2f5f0f7 100644
--- a/src/dashboard/Data/Migration/MigrationStep.react.js
+++ b/src/dashboard/Data/Migration/MigrationStep.react.js
@@ -5,19 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import baseStyles from 'stylesheets/base.scss';
-import Icon from 'components/Icon/Icon.react';
-import React from 'react';
-import styles from 'dashboard/Data/Migration/MigrationStep.scss';
+import baseStyles from 'stylesheets/base.scss';
+import Icon from 'components/Icon/Icon.react';
+import React from 'react';
+import styles from 'dashboard/Data/Migration/MigrationStep.scss';
import { AsyncStatus } from 'lib/Constants';
-export default ({
- title,
- description,
- descriptionWidth = '100%',
- percentComplete = 0,
- status
-}) => {
+export default ({ title, description, descriptionWidth = '100%', percentComplete = 0, status }) => {
if (isNaN(percentComplete) || percentComplete < 0 || percentComplete > 100) {
percentComplete = 0;
}
@@ -29,18 +23,18 @@ export default ({
case AsyncStatus.SUCCESS:
percentComplete = 100;
progressClass = baseStyles.succeededBackground;
- titleClass= baseStyles.succeededText;
- icon =
;
+ titleClass = baseStyles.succeededText;
+ icon = ;
break;
case AsyncStatus.FAILED:
percentComplete = 100;
progressClass = baseStyles.failedBackground;
- titleClass= baseStyles.failedText;
- icon = ;
+ titleClass = baseStyles.failedText;
+ icon = ;
break;
case AsyncStatus.PROGRESS:
progressClass = baseStyles.progressBackground;
- titleClass= baseStyles.progressText;
+ titleClass = baseStyles.progressText;
break;
case AsyncStatus.WAITING:
percentComplete = 0;
@@ -49,17 +43,26 @@ export default ({
}
return (
-
{title} {icon}
-
{description}
+
+ {title} {icon}
+
+ style={{ width: descriptionWidth }}
+ className={[styles.description, descriptionClass].join(' ')}
+ >
+ {description}
+
+
+ className={styles.progressBackground}
+ />
);
};
diff --git a/src/dashboard/Data/Playground/Playground.react.js b/src/dashboard/Data/Playground/Playground.react.js
index ccb9e34fcf..08e830246a 100644
--- a/src/dashboard/Data/Playground/Playground.react.js
+++ b/src/dashboard/Data/Playground/Playground.react.js
@@ -21,7 +21,7 @@ export default class Playground extends Component {
results: [],
running: false,
saving: false,
- savingState: SaveButton.States.WAITING
+ savingState: SaveButton.States.WAITING,
};
}
@@ -40,9 +40,9 @@ export default class Playground extends Component {
? arg.map(this.getParseObjectAttr)
: this.getParseObjectAttr(arg)
: { result: arg },
- name: 'Log'
- }))
- ]
+ name: 'Log',
+ })),
+ ],
}));
originalConsoleLog.apply(console, args);
@@ -56,9 +56,9 @@ export default class Playground extends Component {
arg instanceof Error
? { message: arg.message, name: arg.name, stack: arg.stack }
: { result: arg },
- name: 'Error'
- }))
- ]
+ name: 'Error',
+ })),
+ ],
}));
originalConsoleError.apply(console, args);
@@ -71,16 +71,12 @@ export default class Playground extends Component {
const [originalConsoleLog, originalConsoleError] = this.overrideConsole();
try {
- const {
- applicationId, masterKey, serverURL, javascriptKey
- } = this.context;
+ const { applicationId, masterKey, serverURL, javascriptKey } = this.context;
const originalCode = this.editor.value;
const finalCode = `return (async function(){
try{
- Parse.initialize('${applicationId}', ${
- javascriptKey ? `'${javascriptKey}'` : undefined
- });
+ Parse.initialize('${applicationId}', ${javascriptKey ? `'${javascriptKey}'` : undefined});
Parse.masterKey = '${masterKey}';
Parse.serverUrl = '${serverURL}';
@@ -110,13 +106,10 @@ export default class Playground extends Component {
window.localStorage.setItem(this.localKey, code);
this.setState({
saving: false,
- savingState: SaveButton.States.SUCCEEDED
+ savingState: SaveButton.States.SUCCEEDED,
});
- setTimeout(
- () => this.setState({ savingState: SaveButton.States.WAITING }),
- 3000
- );
+ setTimeout(() => this.setState({ savingState: SaveButton.States.WAITING }), 3000);
} catch (e) {
console.error(e);
this.setState({ saving: false, savingState: SaveButton.States.FAILED });
diff --git a/src/dashboard/Data/Webhooks/Webhooks.react.js b/src/dashboard/Data/Webhooks/Webhooks.react.js
index b200fb5d29..b98cd23c2d 100644
--- a/src/dashboard/Data/Webhooks/Webhooks.react.js
+++ b/src/dashboard/Data/Webhooks/Webhooks.react.js
@@ -5,29 +5,32 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Button from 'components/Button/Button.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import DropdownOption from 'components/Dropdown/Option.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import Field from 'components/Field/Field.react';
-import FormModal from 'components/FormModal/FormModal.react';
-import Icon from 'components/Icon/Icon.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import subscribeTo from 'lib/subscribeTo';
-import TableHeader from 'components/Table/TableHeader.react';
-import TableView from 'dashboard/TableView.react';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { ActionTypes as SchemaActionTypes } from 'lib/stores/SchemaStore';
+import Button from 'components/Button/Button.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import DropdownOption from 'components/Dropdown/Option.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import Field from 'components/Field/Field.react';
+import FormModal from 'components/FormModal/FormModal.react';
+import Icon from 'components/Icon/Icon.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import subscribeTo from 'lib/subscribeTo';
+import TableHeader from 'components/Table/TableHeader.react';
+import TableView from 'dashboard/TableView.react';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { ActionTypes as SchemaActionTypes } from 'lib/stores/SchemaStore';
import { ActionTypes as WebhookActionTypes } from 'lib/stores/WebhookStore';
-import styles from './Webhooks.scss'
+import styles from './Webhooks.scss';
-let TableWarning = ({ text }) =>
- {text}
-
;
+const TableWarning = ({ text }) => (
+
+
+ {text}
+
+);
export default
@subscribeTo('Webhooks', 'webhooks')
@@ -63,14 +66,15 @@ class Webhooks extends TableView {
}
renderToolbar() {
- return
-
- ;
+ return (
+
+
+
+ );
}
clearFields() {
@@ -85,66 +89,124 @@ class Webhooks extends TableView {
renderExtras() {
let classNames = [];
if (this.props.schema.data) {
- let classes = this.props.schema.data.get('classes')
+ const classes = this.props.schema.data.get('classes');
if (classes) {
classNames = Object.keys(classes.toObject());
}
}
- let webhookModalFields =
-
Learn about functions and triggers .}
- />}
- input={ {
- this.setState({hookType: value});
- }}
- disabled={!this.state.showNewWebhookModal}
- value={this.state.hookType}
- fixed={true}>
- Function
- beforeSave
- afterSave
- beforeDelete
- afterDelete
- } />
- {this.state.hookType === 'function' ? }
- input={ {
- this.setState({functionName: value});
- }}
- value={this.state.functionName}
- />} /> : }
- input={ {
- this.setState({triggerClass: value});
- }}
- value={this.state.triggerClass}
- fixed={true}>
- {/*TODO(drewgross)(non-blocking) display special classes without leading underscore*/}
- {classNames.map(name => {name} )}
- } />
- }
+ const webhookModalFields = (
+
+
+ Learn about{' '}
+
+ functions
+ {' '}
+ and{' '}
+
+ triggers
+
+ .
+
+ }
+ />
+ }
+ input={
+ {
+ this.setState({ hookType: value });
+ }}
+ disabled={!this.state.showNewWebhookModal}
+ value={this.state.hookType}
+ fixed={true}
+ >
+
+ Function
+
+
+ beforeSave
+
+
+ afterSave
+
+
+ beforeDelete
+
+
+ afterDelete
+
+
+ }
+ />
+ {this.state.hookType === 'function' ? (
+
+ }
+ input={
+ {
+ this.setState({ functionName: value });
+ }}
+ value={this.state.functionName}
+ />
+ }
+ />
+ ) : (
+ }
+ input={
+ {
+ this.setState({ triggerClass: value });
+ }}
+ value={this.state.triggerClass}
+ fixed={true}
+ >
+ {/*TODO(drewgross)(non-blocking) display special classes without leading underscore*/}
+ {classNames.map(name => (
+
+ {name}
+
+ ))}
+
+ }
+ />
+ )}
}
- input={ {
- this.setState({hookURL: value})
- }}
- value={this.state.hookURL}
- />} />
-
+ label={ }
+ input={
+ {
+ this.setState({ hookURL: value });
+ }}
+ value={this.state.hookURL}
+ />
+ }
+ />
+
+ );
- let hookRequestData = ({hookURL, hookType, functionName, triggerClass}) => {
- let data = { hookURL: hookURL };
+ const hookRequestData = ({ hookURL, hookType, functionName, triggerClass }) => {
+ const data = { hookURL: hookURL };
if (hookType === 'function') {
data.functionName = functionName;
} else {
@@ -154,146 +216,200 @@ class Webhooks extends TableView {
return data;
};
- let newHookModal =
{
- return this.props.webhooks.dispatch(WebhookActionTypes.CREATE, hookRequestData(this.state));
- }}
- onClose={() => {
- this.setState({showNewWebhookModal: false});
- }}
- submitText='Create'
- inProgressText={'Creating\u2026'}
- clearFields={this.clearFields.bind(this)}
- enabled={true /* TODO: do some validation here */}>
- {webhookModalFields}
- ;
+ const newHookModal = (
+
{
+ return this.props.webhooks.dispatch(
+ WebhookActionTypes.CREATE,
+ hookRequestData(this.state)
+ );
+ }}
+ onClose={() => {
+ this.setState({ showNewWebhookModal: false });
+ }}
+ submitText="Create"
+ inProgressText={'Creating\u2026'}
+ clearFields={this.clearFields.bind(this)}
+ enabled={true /* TODO: do some validation here */}
+ >
+ {webhookModalFields}
+
+ );
- let editHookModal =
{
- return this.props.webhooks.dispatch(WebhookActionTypes.EDIT, hookRequestData(this.state));
- }}
- onClose={() => {
- this.setState({showEditWebhookModal: false});
- }}
- submitText='Save'
- inProgressText={'Saving\u2026'}
- clearFields={this.clearFields.bind(this)}
- enabled={true /* TODO: do some validation here */}>
- {webhookModalFields}
- ;
+ const editHookModal = (
+
{
+ return this.props.webhooks.dispatch(WebhookActionTypes.EDIT, hookRequestData(this.state));
+ }}
+ onClose={() => {
+ this.setState({ showEditWebhookModal: false });
+ }}
+ submitText="Save"
+ inProgressText={'Saving\u2026'}
+ clearFields={this.clearFields.bind(this)}
+ enabled={true /* TODO: do some validation here */}
+ >
+ {webhookModalFields}
+
+ );
- let deleteHookModal =
{
- if (this.state.hookType === 'function') {
- return this.props.webhooks.dispatch(WebhookActionTypes.DELETE, {
- functionName: this.state.functionName,
+ const deleteHookModal = (
+ {
+ if (this.state.hookType === 'function') {
+ return this.props.webhooks.dispatch(WebhookActionTypes.DELETE, {
+ functionName: this.state.functionName,
+ });
+ } else {
+ return this.props.webhooks.dispatch(WebhookActionTypes.DELETE, {
+ triggerName: this.state.hookType,
+ triggerClass: this.state.triggerClass,
+ });
+ }
+ }}
+ onClose={() => {
+ this.setState({ showDeleteWebhookModal: false });
+ }}
+ submitText="Delete"
+ inProgressText={'Deleting\u2026'}
+ clearFields={() => {
+ this.setState({
+ hookType: 'function',
+ functionName: '',
+ triggerClass: '',
+ hookURL: 'https://',
});
- } else {
- return this.props.webhooks.dispatch(WebhookActionTypes.DELETE, {
- triggerName: this.state.hookType,
- triggerClass: this.state.triggerClass,
- });
- }
- }}
- onClose={() => {
- this.setState({showDeleteWebhookModal: false});
- }}
- submitText='Delete'
- inProgressText={'Deleting\u2026'}
- clearFields={() => {
- this.setState({
- hookType: 'function',
- functionName: '',
- triggerClass: '',
- hookURL: 'https://',
- });
- }}
- enabled={true /* TODO: do some validation here */}>
- {webhookModalFields}
- ;
+ }}
+ enabled={true /* TODO: do some validation here */}
+ >
+ {webhookModalFields}
+
+ );
return [newHookModal, editHookModal, deleteHookModal];
}
renderRow(hook) {
- let showEdit = hook.url ? () => {
- this.setState({
- hookType: hook.functionName ? 'function' : hook.triggerName,
- functionName: hook.functionName,
- triggerClass: hook.className,
- hookURL: hook.url,
- showEditWebhookModal: true,
- });
- } : null;
+ const showEdit = hook.url
+ ? () => {
+ this.setState({
+ hookType: hook.functionName ? 'function' : hook.triggerName,
+ functionName: hook.functionName,
+ triggerClass: hook.className,
+ hookURL: hook.url,
+ showEditWebhookModal: true,
+ });
+ }
+ : null;
- let showDelete = hook.url ? () => {
- this.setState({
- hookType: hook.functionName ? 'function' : hook.triggerName,
- functionName: hook.functionName,
- triggerClass: hook.className,
- hookURL: hook.url,
- showDeleteWebhookModal: true,
- });
- } : null;
- let rowStyle = hook.url ? { cursor: 'pointer' } : {};
+ const showDelete = hook.url
+ ? () => {
+ this.setState({
+ hookType: hook.functionName ? 'function' : hook.triggerName,
+ functionName: hook.functionName,
+ triggerClass: hook.className,
+ hookURL: hook.url,
+ showDeleteWebhookModal: true,
+ });
+ }
+ : null;
+ const rowStyle = hook.url ? { cursor: 'pointer' } : {};
let deleteColumnContents = null;
if (hook.url) {
- deleteColumnContents =
-
- ;
+ deleteColumnContents = (
+
+
+
+ );
} else {
- let isOverridden = !!this.tableData().find(otherHook => otherHook.url &&
- otherHook.functionName == hook.functionName &&
- otherHook.triggerName == hook.triggerName);
+ const isOverridden = !!this.tableData().find(
+ otherHook =>
+ otherHook.url &&
+ otherHook.functionName == hook.functionName &&
+ otherHook.triggerName == hook.triggerName
+ );
if (isOverridden) {
- deleteColumnContents =
;
+ deleteColumnContents =
;
}
}
- return
- {hook.functionName ? 'Function' : 'Trigger'}
- {hook.className || ''}
- {hook.functionName || hook.triggerName}
- {hook.url || 'Cloud Code'}
- {deleteColumnContents}
- ;
+ return (
+
+
+ {hook.functionName ? 'Function' : 'Trigger'}
+
+
+ {hook.className || ''}
+
+
+ {hook.functionName || hook.triggerName}
+
+
+ {hook.url || 'Cloud Code'}
+
+ {deleteColumnContents}
+
+ );
}
renderHeaders() {
return [
-
Type ,
-
Class ,
-
Method ,
-
Destination ,
-
,
+
+ Type
+ ,
+
+ Class
+ ,
+
+ Method
+ ,
+
+ Destination
+ ,
+
+
+ ,
];
}
renderEmpty() {
- return
Use webhooks to run Cloud Code or connect Parse to your own server. Learn more .}
- icon='gears'
- cta='Create a Webhook'
- action={this.openNewWebhookModal.bind(this)} />
+ return (
+
+ Use webhooks to run Cloud Code or connect Parse to your own server.{' '}
+
+ Learn more
+
+ .
+
+ }
+ icon="gears"
+ cta="Create a Webhook"
+ action={this.openNewWebhookModal.bind(this)}
+ />
+ );
}
tableData() {
if (this.props.webhooks.data) {
- let hooks = this.props.webhooks.data.get('webhooks');
+ const hooks = this.props.webhooks.data.get('webhooks');
if (hooks) {
return hooks.toArray();
}
@@ -302,6 +418,6 @@ class Webhooks extends TableView {
}
openNewWebhookModal() {
- this.setState({showNewWebhookModal: true});
+ this.setState({ showNewWebhookModal: true });
}
}
diff --git a/src/dashboard/Push/PushAudiencesData.react.js b/src/dashboard/Push/PushAudiencesData.react.js
index 171435f5c3..49dd66ae94 100644
--- a/src/dashboard/Push/PushAudiencesData.react.js
+++ b/src/dashboard/Push/PushAudiencesData.react.js
@@ -6,16 +6,16 @@
* the root directory of this source tree.
*/
import * as PushAudiencesStore from 'lib/stores/PushAudiencesStore';
-import * as PushConstants from './PushConstants';
-import Button from 'components/Button/Button.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceDialog.react';
-import PushAudiencesSelector from 'components/PushAudiencesSelector/PushAudiencesSelector.react';
-import queryFromFilters from 'lib/queryFromFilters';
-import React from 'react';
-import styles from './PushAudiencesData.scss';
-import { List } from 'immutable';
-import { CurrentApp } from 'context/currentApp';
+import * as PushConstants from './PushConstants';
+import Button from 'components/Button/Button.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceDialog.react';
+import PushAudiencesSelector from 'components/PushAudiencesSelector/PushAudiencesSelector.react';
+import queryFromFilters from 'lib/queryFromFilters';
+import React from 'react';
+import styles from './PushAudiencesData.scss';
+import { List } from 'immutable';
+import { CurrentApp } from 'context/currentApp';
const XHR_KEY = 'PushAudiencesData';
@@ -39,9 +39,10 @@ export default class PushAudiencesData extends React.Component {
};
}
- componentWillMount(){
- if (this.props.loaded){ //case when data already fetched
- this.setState({ loading: false});
+ componentWillMount() {
+ if (this.props.loaded) {
+ //case when data already fetched
+ this.setState({ loading: false });
}
this.setState({
@@ -51,28 +52,33 @@ export default class PushAudiencesData extends React.Component {
count: 0,
objectId: 'everyone',
icon: 'users-solid',
- }
+ },
});
- this.context.fetchAvailableDevices().then(({ available_devices }) => {
- this.setState({
- availableDevices: available_devices
- });
- }, () => {
- this.setState({
- availableDevices: PushConstants.DEFAULT_DEVICES
- });
- });
+ this.context.fetchAvailableDevices().then(
+ ({ available_devices }) => {
+ this.setState({
+ availableDevices: available_devices,
+ });
+ },
+ () => {
+ this.setState({
+ availableDevices: PushConstants.DEFAULT_DEVICES,
+ });
+ }
+ );
}
componentWillReceiveProps(props) {
- if (props.loaded){
- this.setState({ loading: false});
+ if (props.loaded) {
+ this.setState({ loading: false });
}
}
componentWillUnmount() {
- this.props.pushAudiencesStore.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, { xhrKey: XHR_KEY});
+ this.props.pushAudiencesStore.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, {
+ xhrKey: XHR_KEY,
+ });
}
shouldComponentUpdate(nextProps, nextState) {
@@ -82,19 +88,20 @@ export default class PushAudiencesData extends React.Component {
return true;
}
- handleShowMoreClick(){
+ handleShowMoreClick() {
this.setState({ loading: true });
- this.props.pushAudiencesStore.dispatch(PushAudiencesStore.ActionTypes.FETCH,
- {
+ this.props.pushAudiencesStore
+ .dispatch(PushAudiencesStore.ActionTypes.FETCH, {
min: PushConstants.INITIAL_PAGE_SIZE,
limit: PushConstants.SHOW_MORE_LIMIT,
xhrKey: XHR_KEY,
- }).then(() => {
- this.setState({ loading: false });
- });
+ })
+ .then(() => {
+ this.setState({ loading: false });
+ });
}
- handleEditAudienceClick(audienceInfo){
+ handleEditAudienceClick(audienceInfo) {
this.setState({
showEditModal: true,
newSegmentInfo: audienceInfo,
@@ -116,7 +123,7 @@ export default class PushAudiencesData extends React.Component {
createErrorMessage: '',
});
let query = {};
- let parseQuery = queryFromFilters('_Installation', formattedFilters);
+ const parseQuery = queryFromFilters('_Installation', formattedFilters);
if (parseQuery && parseQuery.toJSON()) {
query = parseQuery.toJSON().where || {};
@@ -128,27 +135,37 @@ export default class PushAudiencesData extends React.Component {
// Ideally, we would pass a Parse.Query around everywhere.
parseQuery.containedIn('deviceType', platforms);
if (!saveForFuture) {
- this.props.onChange(PushConstants.NEW_SEGMENT_ID, parseQuery, 1 /* TODO: get the read device count */);
- }
+ this.props.onChange(
+ PushConstants.NEW_SEGMENT_ID,
+ parseQuery,
+ 1 /* TODO: get the read device count */
+ );
+ }
- if (saveForFuture){
- this.props.pushAudiencesStore.dispatch(PushAudiencesStore.ActionTypes.CREATE, {
- query: JSON.stringify(query),
- name,
- }).then(() => {
- let stateSettings = {};
- stateSettings[modalState] = false;
- stateSettings.newlyCreatedSegment = true;
- stateSettings.createProgress = false;
- this.setState(stateSettings);
- }, (e) => {
- this.setState({
- createErrorMessage: e.message,
- createProgress: false,
- });
- });
- } else { //saveAudience one time use audience
- let stateSettings = {
+ if (saveForFuture) {
+ this.props.pushAudiencesStore
+ .dispatch(PushAudiencesStore.ActionTypes.CREATE, {
+ query: JSON.stringify(query),
+ name,
+ })
+ .then(
+ () => {
+ const stateSettings = {};
+ stateSettings[modalState] = false;
+ stateSettings.newlyCreatedSegment = true;
+ stateSettings.createProgress = false;
+ this.setState(stateSettings);
+ },
+ e => {
+ this.setState({
+ createErrorMessage: e.message,
+ createProgress: false,
+ });
+ }
+ );
+ } else {
+ //saveAudience one time use audience
+ const stateSettings = {
newSegment: {
createdAt: new Date(),
name: 'New Segment',
@@ -156,7 +173,7 @@ export default class PushAudiencesData extends React.Component {
objectId: PushConstants.NEW_SEGMENT_ID,
query,
filters,
- }
+ },
};
stateSettings[modalState] = false;
stateSettings.createProgress = false;
@@ -166,50 +183,56 @@ export default class PushAudiencesData extends React.Component {
}
render() {
- let { pushAudiencesStore, current, ...otherProps } = this.props;
+ const { pushAudiencesStore, current, ...otherProps } = this.props;
- let pushAudienceData = pushAudiencesStore.data;
+ const pushAudienceData = pushAudiencesStore.data;
let audiences = null;
let showMore = false;
- if (pushAudienceData){
+ if (pushAudienceData) {
audiences = pushAudienceData.get('audiences') || new List();
showMore = pushAudienceData.get('showMore') || false;
}
- let showMoreContent = showMore ? (
+ const showMoreContent = showMore ? (
-
+
) : null;
- let createAudienceButton = (
+ const createAudienceButton = (
{
this.setState({
- showCreateModal: true
- });
- }}>
-
- {this.state.showCreateModal ?
{
- this.setState({
- showCreateModal: false,
- createErrorMessage: '',
+ showCreateModal: true,
});
- }}/> : null}
+ }}
+ >
+ {this.state.showCreateModal ? (
+ {
+ this.setState({
+ showCreateModal: false,
+ createErrorMessage: '',
+ });
+ }}
+ />
+ ) : null}
);
- let editAudienceModal = (
+ const editAudienceModal = (
{
this.setState({
showEditModal: false,
createErrorMessage: '',
});
- }}/>
+ }}
+ />
);
@@ -235,7 +259,7 @@ export default class PushAudiencesData extends React.Component {
_current = PushConstants.NEW_SEGMENT_ID;
this.newlyCreatedTempSegment = false;
} else if (this.state.newlyCreatedSegment) {
- _current = audiences.get(0).objectId;
+ _current = audiences.get(0).objectId;
this.setState({ newlyCreatedSegment: false });
} else {
_current = current;
@@ -243,20 +267,25 @@ export default class PushAudiencesData extends React.Component {
return (
-
+
+ {...otherProps}
+ >
{showMoreContent}
{createAudienceButton}
{this.state.showEditModal ? editAudienceModal : null}
- )
+ );
}
}
diff --git a/src/dashboard/Push/PushAudiencesIndex.react.js b/src/dashboard/Push/PushAudiencesIndex.react.js
index 67d1d7d31d..60529007e5 100644
--- a/src/dashboard/Push/PushAudiencesIndex.react.js
+++ b/src/dashboard/Push/PushAudiencesIndex.react.js
@@ -5,26 +5,26 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as PushAudiencesStore from 'lib/stores/PushAudiencesStore';
-import * as SchemaStore from 'lib/stores/SchemaStore';
-import * as PushConstants from './PushConstants';
-import Button from 'components/Button/Button.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import FormModal from 'components/FormModal/FormModal.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import Modal from 'components/Modal/Modal.react';
-import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceDialog.react';
-import PushAudiencesIndexRow from './PushAudiencesIndexRow.react';
-import queryFromFilters from 'lib/queryFromFilters';
-import React from 'react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import stylesTable from 'dashboard/TableView.scss';
-import subscribeTo from 'lib/subscribeTo';
-import TableHeader from 'components/Table/TableHeader.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import * as PushAudiencesStore from 'lib/stores/PushAudiencesStore';
+import * as SchemaStore from 'lib/stores/SchemaStore';
+import * as PushConstants from './PushConstants';
+import Button from 'components/Button/Button.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import FormModal from 'components/FormModal/FormModal.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import Modal from 'components/Modal/Modal.react';
+import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceDialog.react';
+import PushAudiencesIndexRow from './PushAudiencesIndexRow.react';
+import queryFromFilters from 'lib/queryFromFilters';
+import React from 'react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import stylesTable from 'dashboard/TableView.scss';
+import subscribeTo from 'lib/subscribeTo';
+import TableHeader from 'components/Table/TableHeader.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
import { formatAudienceSchema } from 'lib/PushUtils';
-import { List } from 'immutable';
+import { List } from 'immutable';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
@@ -38,7 +38,10 @@ class PushAudiencesIndex extends DashboardView {
super();
this.section = 'Push';
this.subsection = 'Audiences';
- this.action = new SidebarAction('Create an audience', this.handleCreateAudienceClick.bind(this));
+ this.action = new SidebarAction(
+ 'Create an audience',
+ this.handleCreateAudienceClick.bind(this)
+ );
this.state = {
availableDevices: [],
loading: true,
@@ -47,40 +50,45 @@ class PushAudiencesIndex extends DashboardView {
deletionAudienceId: null,
deleteionAudienceName: null,
showCreateAudienceModal: false,
- }
+ };
}
componentWillMount() {
this.props.schema.dispatch(SchemaStore.ActionTypes.FETCH);
- this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.FETCH,
- {
+ this.props.pushaudiences
+ .dispatch(PushAudiencesStore.ActionTypes.FETCH, {
limit: PushConstants.SHOW_MORE_LIMIT,
min: PushConstants.INITIAL_PAGE_SIZE,
xhrKey: XHR_KEY,
- }).then(() => {
-
- }).finally(() => {
- this.setState({ loading: false });
- });
- this.context.fetchAvailableDevices().then(({ available_devices }) => {
- this.setState({
- availableDevices: available_devices
- });
- }, () => {
- this.setState({
- availableDevices: PushConstants.DEFAULT_DEVICES
+ })
+ .then(() => {})
+ .finally(() => {
+ this.setState({ loading: false });
});
- });
+ this.context.fetchAvailableDevices().then(
+ ({ available_devices }) => {
+ this.setState({
+ availableDevices: available_devices,
+ });
+ },
+ () => {
+ this.setState({
+ availableDevices: PushConstants.DEFAULT_DEVICES,
+ });
+ }
+ );
}
componentWillReceiveProps(props) {
- if (props.loaded){
- this.setState({ loading: false});
+ if (props.loaded) {
+ this.setState({ loading: false });
}
}
componentWillUnmount() {
- this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, { xhrKey: XHR_KEY});
+ this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, {
+ xhrKey: XHR_KEY,
+ });
}
handleCreateAudienceClick() {
@@ -90,8 +98,8 @@ class PushAudiencesIndex extends DashboardView {
}
tableData() {
- let schema = formatAudienceSchema(this.props.schema.data.get('classes')) || {};
- let pushAudienceData = this.props.pushaudiences.data;
+ const schema = formatAudienceSchema(this.props.schema.data.get('classes')) || {};
+ const pushAudienceData = this.props.pushaudiences.data;
let audiences = undefined;
if (pushAudienceData) {
@@ -125,28 +133,43 @@ class PushAudiencesIndex extends DashboardView {
schema={this.schema}
timesUsed={audience.timesUsed}
onSendPush={this.handleSendPush.bind(this)}
- onDelete={this.handleDelete.bind(this)}/>
+ onDelete={this.handleDelete.bind(this)}
+ />
);
}
renderToolbar() {
return (
-
-
+
+
);
}
renderHeaders() {
return [
- Name ,
- Size ,
- Details ,
- Created On ,
- # of sends ,
- Action ,
+
+ Name
+ ,
+
+ Size
+ ,
+
+ Details
+ ,
+
+ Created On
+ ,
+
+ # of sends
+ ,
+
+ Action
+ ,
];
}
@@ -154,96 +177,103 @@ class PushAudiencesIndex extends DashboardView {
if (this.state.availableDevices.length === 0) {
return (
+ title="No registered devices"
+ description="You have no registered installations of your app. You can get started with our Quick Start guide."
+ icon="devices-solid"
+ cta="Push Quick Start"
+ action={'https://www.parse.com/apps/quickstart#parse_push'}
+ />
);
} else {
return (
{
this.setState({
- showCreateAudienceModal: true
+ showCreateAudienceModal: true,
});
- }} />
+ }}
+ />
);
}
}
- createAudience(modalState, { platforms, name, formattedFilters }){
+ createAudience(modalState, { platforms, name, formattedFilters }) {
let query = {};
- let parseQuery = queryFromFilters('_Installation', formattedFilters);
+ const parseQuery = queryFromFilters('_Installation', formattedFilters);
- if (parseQuery && parseQuery.toJSON()){
+ if (parseQuery && parseQuery.toJSON()) {
query = parseQuery.toJSON().where || {};
}
query.deviceType = { $in: platforms };
//TODO: handle fail case - need to modify/extend to handle custom footer
- this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.CREATE,{
- query: JSON.stringify(query),
- name,
- }).then(() => {
- this.setState({
- showCreateAudienceModal: false,
+ this.props.pushaudiences
+ .dispatch(PushAudiencesStore.ActionTypes.CREATE, {
+ query: JSON.stringify(query),
+ name,
+ })
+ .then(() => {
+ this.setState({
+ showCreateAudienceModal: false,
+ });
});
- });
}
renderContent() {
- let toolbar = this.renderToolbar();
- let data = this.tableData();
+ const toolbar = this.renderToolbar();
+ const data = this.tableData();
let content = null;
let headers = null;
- let createAudienceModal = this.state.showCreateAudienceModal ? (
+ const createAudienceModal = this.state.showCreateAudienceModal ? (
{
this.setState({
- showCreateAudienceModal: false
+ showCreateAudienceModal: false,
});
- }}/>
- ) :
- null;
+ }}
+ />
+ ) : null;
- let deleteSubtitle = (
+ const deleteSubtitle = (
Are you sure you want to delete {this.state.deleteionAudienceName} ?
);
- let deleteAudienceModal = {
- return this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.DESTROY,
- { objectId : this.state.deletionAudienceId });
- }}
- onSuccess={() => {
- this.setState({
- showDeleteAudienceModal: false,
- });
- }}
- onClose={() => {
- this.setState({ showDeleteAudienceModal: false, });
- }}>
-
+ const deleteAudienceModal = (
+ {
+ return this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.DESTROY, {
+ objectId: this.state.deletionAudienceId,
+ });
+ }}
+ onSuccess={() => {
+ this.setState({
+ showDeleteAudienceModal: false,
+ });
+ }}
+ onClose={() => {
+ this.setState({ showDeleteAudienceModal: false });
+ }}
+ >
+ );
if (typeof data !== 'undefined') {
if (data.size === 0) {
@@ -252,16 +282,14 @@ class PushAudiencesIndex extends DashboardView {
content = (
-
- {data.map((row) => this.renderRow(row))}
-
+ {data.map(row => this.renderRow(row))}
);
headers = this.renderHeaders();
}
}
- let extras = this.renderExtras ? this.renderExtras() : null;
+ const extras = this.renderExtras ? this.renderExtras() : null;
return (
diff --git a/src/dashboard/Push/PushAudiencesIndexRow.react.js b/src/dashboard/Push/PushAudiencesIndexRow.react.js
index 344a597d6f..0a463bb4f6 100644
--- a/src/dashboard/Push/PushAudiencesIndexRow.react.js
+++ b/src/dashboard/Push/PushAudiencesIndexRow.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as PushUtils from 'lib/PushUtils';
-import * as DateUtils from 'lib/DateUtils';
+import * as PushUtils from 'lib/PushUtils';
+import * as DateUtils from 'lib/DateUtils';
-import Icon from 'components/Icon/Icon.react';
-import PropTypes from 'lib/PropTypes';
+import Icon from 'components/Icon/Icon.react';
+import PropTypes from 'lib/PropTypes';
import PushAudiencesBaseRow from 'components/PushAudiencesSelector/PushAudiencesBaseRow.react';
-import React from 'react';
+import React from 'react';
-import styles from './PushAudiencesIndexRow.scss';
+import styles from './PushAudiencesIndexRow.scss';
export default class PushAudiencesIndexRow extends PushAudiencesBaseRow {
constructor() {
@@ -24,51 +24,54 @@ export default class PushAudiencesIndexRow extends PushAudiencesBaseRow {
expandedView: false,
approximate: false,
isNewSegment: false,
- }
+ };
}
render() {
- let detailsView = this.state.expandedView ?
- PushUtils.largeInfoBuilder(this.props.query, this.props.schema, styles) :
- (
-
- {PushUtils.shortInfoBuilder(this.props.query, this.props.schema)}
-
- );
- let countDetails = PushUtils.formatCountDetails(this.state.count, this.state.approximate);
+ const detailsView = this.state.expandedView ? (
+ PushUtils.largeInfoBuilder(this.props.query, this.props.schema, styles)
+ ) : (
+
+ {PushUtils.shortInfoBuilder(this.props.query, this.props.schema)}
+
+ );
+ const countDetails = PushUtils.formatCountDetails(this.state.count, this.state.approximate);
return (
{this.props.name}
- Send a new push
+ type="button"
+ onClick={this.props.onSendPush.bind(undefined, this.props.id)}
+ >
+ Send a new push
{countDetails}
{detailsView}
- {this.state.expandedView ? 'less details' : 'more details' }
+ type="button"
+ className={[styles.moreDetails, !this.props.query ? styles.hideMoreDetails : ''].join(
+ ' '
+ )}
+ onClick={this.handleDetailsToggle.bind(this, this.props.query, this.props.schema)}
+ >
+ {this.state.expandedView ? 'less details' : 'more details'}
{DateUtils.yearMonthDayFormatter(this.props.createdAt)}
-
- {this.props.timesUsed}
-
+ {this.props.timesUsed}
-
+ type="button"
+ onClick={this.props.onDelete.bind(undefined, this.props.id, this.props.name)}
+ >
+
@@ -77,28 +80,14 @@ export default class PushAudiencesIndexRow extends PushAudiencesBaseRow {
}
PushAudiencesIndexRow.propTypes = {
- id: PropTypes.string.isRequired.describe(
- 'The id of the push audience option.'
- ),
- name: PropTypes.string.describe(
- 'The name of the push audience option.'
- ),
- createdAt: PropTypes.instanceOf(Date).describe(
- 'The size of the push audience option.'
- ),
+ id: PropTypes.string.isRequired.describe('The id of the push audience option.'),
+ name: PropTypes.string.describe('The name of the push audience option.'),
+ createdAt: PropTypes.instanceOf(Date).describe('The size of the push audience option.'),
query: PropTypes.object.describe(
'Key value pair of installation condition info for the specific audience.'
),
- schema: PropTypes.object.describe(
- 'Schema of installation.'
- ),
- timesUsed: PropTypes.number.describe(
- 'Number of times audience has been used.'
- ),
- onDelete: PropTypes.func.describe(
- 'Callback to be executed on click of delete button.'
- ),
- onSendPush: PropTypes.func.describe(
- 'Callback to be executed on send push button.'
- ),
+ schema: PropTypes.object.describe('Schema of installation.'),
+ timesUsed: PropTypes.number.describe('Number of times audience has been used.'),
+ onDelete: PropTypes.func.describe('Callback to be executed on click of delete button.'),
+ onSendPush: PropTypes.func.describe('Callback to be executed on send push button.'),
};
diff --git a/src/dashboard/Push/PushComposerHelper.react.js b/src/dashboard/Push/PushComposerHelper.react.js
index c703f18a32..211c26a6a7 100644
--- a/src/dashboard/Push/PushComposerHelper.react.js
+++ b/src/dashboard/Push/PushComposerHelper.react.js
@@ -6,17 +6,17 @@
* the root directory of this source tree.
*/
import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Label from 'components/Label/Label.react';
-import Option from 'components/Dropdown/Option.react';
-import React from 'react';
-import Toggle from 'components/Toggle/Toggle.react';
-import { pad } from 'lib/DateUtils';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Option from 'components/Dropdown/Option.react';
+import React from 'react';
+import Toggle from 'components/Toggle/Toggle.react';
+import { pad } from 'lib/DateUtils';
export function setTimeFieldDescription(isLocal) {
return isLocal ? 'LOCAL TIME' : null;
-}
+}
/**
* Sets the field with or without 'Z' ending based on isLocal flag
@@ -28,13 +28,20 @@ export function setTimeFieldDescription(isLocal) {
*/
export function setPushTimeField(setField, field, value, isLocal) {
if (isLocal && value.constructor === Date) {
- let _value = value.getFullYear()
- + '-' + pad(value.getMonth() + 1)
- + '-' + pad(value.getDate())
- + 'T' + pad(value.getHours())
- + ':' + pad(value.getMinutes())
- + ':' + pad(value.getSeconds())
- + '.' + String((value.getMilliseconds()/1000).toFixed(3)).slice(2, 5);
+ const _value =
+ value.getFullYear() +
+ '-' +
+ pad(value.getMonth() + 1) +
+ '-' +
+ pad(value.getDate()) +
+ 'T' +
+ pad(value.getHours()) +
+ ':' +
+ pad(value.getMinutes()) +
+ ':' +
+ pad(value.getSeconds()) +
+ '.' +
+ String((value.getMilliseconds() / 1000).toFixed(3)).slice(2, 5);
setField(field, _value);
} else {
setField(field, value);
@@ -50,8 +57,8 @@ export function setPushTimeField(setField, field, value, isLocal) {
*/
export function localTimeFormater(setField, field, value, isLocal) {
if (value && value.constructor === Date) {
- let offset = value.getTimezoneOffset()*60*1000;
- let newDate = new Date(value.getTime() + (isLocal ? offset : -offset ));
+ const offset = value.getTimezoneOffset() * 60 * 1000;
+ const newDate = new Date(value.getTime() + (isLocal ? offset : -offset));
setField(field + '_iso', newDate);
setPushTimeField(setField, field, newDate, isLocal);
}
@@ -61,76 +68,95 @@ export function renderExpirationContent(fields, setField) {
if (!fields.push_expires) {
return null;
}
- let expirationContent = [
+ const expirationContent = [
}
- input={
- } />
+ key="expiration_time_type"
+ label={ }
+ input={
+
+ }
+ />,
];
if (fields.expiration_time_type === 'time') {
expirationContent.push(
}
+ key="expiration_time"
+ label={
+
+ }
input={
{
+ onChange={value => {
setField('expiration_time_iso', value);
setPushTimeField(setField, 'expiration_time', value, fields.local_time);
- }} />
- } />
+ }}
+ />
+ }
+ />
);
} else {
- let expirationIntervalNums = [];
- let unit = fields.expiration_interval_unit === 'hours' ? 24 : 30;
+ const expirationIntervalNums = [];
+ const unit = fields.expiration_interval_unit === 'hours' ? 24 : 30;
for (let i = 1; i <= unit; i++) {
expirationIntervalNums.push(
- {i}
+
+ {i}
+
);
}
- let expirationIntervalUnits = [
- {fields.expiration_interval_num === '1' ? 'Hour' : 'Hours'} ,
- {fields.expiration_interval_num === '1' ? 'Day' : 'Days'}
+ const expirationIntervalUnits = [
+
+ {fields.expiration_interval_num === '1' ? 'Hour' : 'Hours'}
+ ,
+
+ {fields.expiration_interval_num === '1' ? 'Day' : 'Days'}
+ ,
];
expirationContent.push(
}
+ key="expiration_interval"
+ label={ }
input={
+ onChange={setField.bind(null, 'expiration_interval_num')}
+ >
{expirationIntervalNums}
{
+ onChange={value => {
//handle case when interval num is out of expected range
- if (value === 'hours' && Number(fields.expiration_interval_num) > 24 ) {
+ if (value === 'hours' && Number(fields.expiration_interval_num) > 24) {
setField('expiration_interval_num', '24');
}
setField('expiration_interval_unit', value);
- }} >
+ }}
+ >
{expirationIntervalUnits}
- } />
+ }
+ />
);
}
return expirationContent;
diff --git a/src/dashboard/Push/PushDetails.react.js b/src/dashboard/Push/PushDetails.react.js
index 3ab30046bc..d3478f5c6a 100644
--- a/src/dashboard/Push/PushDetails.react.js
+++ b/src/dashboard/Push/PushDetails.react.js
@@ -5,76 +5,76 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as DateUtils from 'lib/DateUtils';
-import * as PushHelper from './PushComposerHelper.react';
-import * as SchemaStore from 'lib/stores/SchemaStore';
-import Chart from 'components/Chart/Chart.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FieldStyles from 'components/Field/Field.scss';
-import FlowView from 'components/FlowView/FlowView.react';
-import Label from 'components/Label/Label.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import Parse from 'parse';
-import prettyNumber from 'lib/prettyNumber';
+import * as DateUtils from 'lib/DateUtils';
+import * as PushHelper from './PushComposerHelper.react';
+import * as SchemaStore from 'lib/stores/SchemaStore';
+import Chart from 'components/Chart/Chart.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FieldStyles from 'components/Field/Field.scss';
+import FlowView from 'components/FlowView/FlowView.react';
+import Label from 'components/Label/Label.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import Parse from 'parse';
+import prettyNumber from 'lib/prettyNumber';
import PushExperimentDropdown from 'components/PushExperimentDropdown/PushExperimentDropdown.react';
-import PushOpenRate from 'components/PushOpenRate/PushOpenRate.react';
-import React from 'react';
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-import styles from './PushDetails.scss';
-import subscribeTo from 'lib/subscribeTo';
-import tableStyles from 'components/Table/Table.scss';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { Directions } from 'lib/Constants';
-import { Link } from 'react-router-dom';
-import { tableInfoBuilder } from 'lib/PushUtils';
+import PushOpenRate from 'components/PushOpenRate/PushOpenRate.react';
+import React from 'react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import styles from './PushDetails.scss';
+import subscribeTo from 'lib/subscribeTo';
+import tableStyles from 'components/Table/Table.scss';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { Directions } from 'lib/Constants';
+import { Link } from 'react-router-dom';
+import { tableInfoBuilder } from 'lib/PushUtils';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
const EXP_STATS_URL = 'http://docs.parseplatform.org/ios/guide/#push-experiments';
-let getMessage = (payload) => {
- if(payload) {
- let payloadJSON = JSON.parse(payload);
- if (payloadJSON.alert && payloadJSON.alert.body) {
- return payloadJSON.alert.body;
- } else if (payloadJSON.alert) {
- return payloadJSON.alert;
- } else {
- return payload;
- }
+const getMessage = payload => {
+ if (payload) {
+ const payloadJSON = JSON.parse(payload);
+ if (payloadJSON.alert && payloadJSON.alert.body) {
+ return payloadJSON.alert.body;
+ } else if (payloadJSON.alert) {
+ return payloadJSON.alert;
+ } else {
+ return payload;
+ }
}
return '';
-}
+};
-let getFormattedTime = ({ time, is_local }) => {
+const getFormattedTime = ({ time, is_local }) => {
let formattedTime = DateUtils.yearMonthDayTimeFormatter(new Date(time), !is_local);
- if(is_local){
- formattedTime += ' Local Time'
+ if (is_local) {
+ formattedTime += ' Local Time';
}
return formattedTime;
-}
+};
-let getSentInfo = (sendTime, expiration) => {
+const getSentInfo = (sendTime, expiration) => {
//expiration unit is in seconds :(
- if (!sendTime){
+ if (!sendTime) {
return '';
}
- let fmtSendTime = getFormattedTime({time: sendTime});
- let fmtExpiration = expiration ? getFormattedTime({time: expiration * 1000}) : null;
- if (expiration){
+ const fmtSendTime = getFormattedTime({ time: sendTime });
+ const fmtExpiration = expiration ? getFormattedTime({ time: expiration * 1000 }) : null;
+ if (expiration) {
return `Sent ${fmtSendTime} and expires ${fmtExpiration}`;
} else {
return `Sent ${fmtSendTime}`;
}
-}
+};
-let getDeliveryErrors = (deliveryErrors=[]) => {
- let res = [];
+const getDeliveryErrors = (deliveryErrors = []) => {
+ const res = [];
// NOTE: odd case when when deliverErrors === [null]
// End Point is returning this, should investigate why but defensive check for now.
@@ -83,23 +83,25 @@ let getDeliveryErrors = (deliveryErrors=[]) => {
}
deliveryErrors.forEach(({ Name, Message, Quantity }, i) => {
- if(Quantity > 0){
+ if (Quantity > 0) {
res.push(
{Name}
{Message}
- {Quantity}
+
+ {Quantity}
+
);
}
});
return res;
-}
+};
-let getStatusTable = (pushDetails, deferDeliveries) => {
- let deliverErrors = getDeliveryErrors(pushDetails.delivery_errors);
+const getStatusTable = (pushDetails, deferDeliveries) => {
+ const deliverErrors = getDeliveryErrors(pushDetails.delivery_errors);
if (deferDeliveries && deliverErrors.length === 0) {
return null;
@@ -109,34 +111,42 @@ let getStatusTable = (pushDetails, deferDeliveries) => {
Delivery Report
-
-
-
- Status
- Push Count
-
-
-
- {!deferDeliveries ?
-
-
- Successful Deliveries
- Give your test a memorable name so you remember what you were testing when you see the results.
-
- {pushDetails.get('numSent')}
- :
- null
- }
- {deliverErrors}
-
-
+
+
+
+
+ Status
+
+
+ Push Count
+
+
+
+
+ {!deferDeliveries ? (
+
+
+ Successful Deliveries
+
+ Give your test a memorable name so you remember what you were testing when you
+ see the results.
+
+
+
+ {pushDetails.get('numSent')}
+
+
+ ) : null}
+ {deliverErrors}
+
+
);
-}
+};
-let getTargetTable = (query, schema) => {
- let targetRows = tableInfoBuilder(query, schema, tableStyles);
+const getTargetTable = (query, schema) => {
+ const targetRows = tableInfoBuilder(query, schema, tableStyles);
if (!targetRows || targetRows.length === 0) {
return null;
} else {
@@ -144,50 +154,66 @@ let getTargetTable = (query, schema) => {
Target
-
-
-
- Grouping
- Operator
- Value
-
-
-
- {targetRows}
-
-
+
+
+
+
+ Grouping
+
+
+ Operator
+
+
+ Value
+
+
+
+ {targetRows}
+
);
}
-}
+};
-let getExperimentLoser = (winner) => {
+const getExperimentLoser = winner => {
return winner === 'A' ? 'B' : 'A';
-}
+};
-let getExperimentPartial = (pushDetails, type, isMessageType, style) => {
+const getExperimentPartial = (pushDetails, type, isMessageType, style) => {
return (
-
{isMessageType ? `GROUP ${type.toUpperCase()} MESSAGE` : `GROUP ${type.toUpperCase()} TIME`}
-
{isMessageType ? getMessage(pushDetails[`group_${type}`].payload) : getSentInfo(pushDetails[`group_${type}`].send_time, pushDetails[`group_${type}`].expiration)}
+
+ {isMessageType ? `GROUP ${type.toUpperCase()} MESSAGE` : `GROUP ${type.toUpperCase()} TIME`}
+
+
+ {isMessageType
+ ? getMessage(pushDetails[`group_${type}`].payload)
+ : getSentInfo(
+ pushDetails[`group_${type}`].send_time,
+ pushDetails[`group_${type}`].expiration
+ )}
+
- {isMessageType ? getSentInfo(pushDetails[`group_${type}`].send_time, pushDetails[`group_${type}`].expiration) : null}
+ {isMessageType
+ ? getSentInfo(
+ pushDetails[`group_${type}`].send_time,
+ pushDetails[`group_${type}`].expiration
+ )
+ : null}
);
-}
+};
-let getPushDetailUrl = (context, pushId) => generatePath(context, `push/${pushId}`);
+const getPushDetailUrl = (context, pushId) => generatePath(context, `push/${pushId}`);
-let formatAnalyticsData = (data) => {
+const formatAnalyticsData = data => {
if (!data) {
return [];
}
- return data.map((point) => (
- [Parse._decode('date', point[0]).getTime(), point[1]]
- ));
-}
+ return data.map(point => [Parse._decode('date', point[0]).getTime(), point[1]]);
+};
const COLOR_MAP = {
green: '#00db7c',
@@ -195,7 +221,7 @@ const COLOR_MAP = {
darkPurple: '#8D11BA',
blueGreen: '#11A4BA',
blue: '#169cee',
-}
+};
const DROPDOWN_KEY_GROUP_A = 'Group A';
const DROPDOWN_KEY_GROUP_B = 'Group B';
@@ -223,8 +249,8 @@ class PushDetails extends DashboardView {
componentWillMount() {
this.props.schema.dispatch(SchemaStore.ActionTypes.FETCH);
- let promise = this.context.fetchPushDetails(this.props.params.pushId);
- promise.then((pushDetails) => {
+ const promise = this.context.fetchPushDetails(this.props.params.pushId);
+ promise.then(pushDetails => {
if (!pushDetails) {
return null;
}
@@ -235,7 +261,8 @@ class PushDetails extends DashboardView {
groupColorB: pushDetails.statistics.winner !== 'A' ? COLOR_MAP.green : COLOR_MAP.red,
groupStatusA: pushDetails.statistics.winner === 'A' ? 'WINNER!' : '',
groupStatusB: pushDetails.statistics.winner !== 'A' ? 'WINNER!' : '',
- selectedGroup: pushDetails.statistics.winner === 'A' ? DROPDOWN_KEY_GROUP_A : DROPDOWN_KEY_GROUP_B,
+ selectedGroup:
+ pushDetails.statistics.winner === 'A' ? DROPDOWN_KEY_GROUP_A : DROPDOWN_KEY_GROUP_B,
});
}
@@ -252,24 +279,24 @@ class PushDetails extends DashboardView {
pushStatusID = pushDetails.id;
}
- let toDate = Math.round(new Date(pushDetails.to_date).getTime() / 1000);
- let fromDate = Math.round(new Date(pushDetails.from_date).getTime() / 1000);
+ const toDate = Math.round(new Date(pushDetails.to_date).getTime() / 1000);
+ const fromDate = Math.round(new Date(pushDetails.from_date).getTime() / 1000);
- let query = {
+ const query = {
endpoint: 'app_opened_from_push_with_id',
- stride:'hour',
+ stride: 'hour',
from: fromDate,
to: toDate,
- }
+ };
- let promiseList = [];
+ const promiseList = [];
if (pushDetails.is_exp) {
- let abortableRequestA = this.context.getAnalyticsTimeSeries({
+ const abortableRequestA = this.context.getAnalyticsTimeSeries({
...query,
pushStatusID: pushStatusIDA,
});
- let abortableRequestB = this.context.getAnalyticsTimeSeries({
+ const abortableRequestB = this.context.getAnalyticsTimeSeries({
...query,
pushStatusID: pushStatusIDB,
});
@@ -283,44 +310,50 @@ class PushDetails extends DashboardView {
this.xhrHandles.push(abortableRequestA.xhr);
this.xhrHandles.push(abortableRequestB.xhr);
- Promise.all(promiseList).then(([dataA, dataB]) => {
- let chartDataA = formatAnalyticsData(dataA);
- let chartDataB = formatAnalyticsData(dataB);
- if (chartDataA.length > 0 || chartDataB.length > 0) {
- this.setState({
- chartData: {
- A: {
- color: this.state.groupColorA,
- points: chartDataA
+ Promise.all(promiseList)
+ .then(([dataA, dataB]) => {
+ const chartDataA = formatAnalyticsData(dataA);
+ const chartDataB = formatAnalyticsData(dataB);
+ if (chartDataA.length > 0 || chartDataB.length > 0) {
+ this.setState({
+ chartData: {
+ A: {
+ color: this.state.groupColorA,
+ points: chartDataA,
+ },
+ B: {
+ color: this.state.groupColorB,
+ points: chartDataB,
+ },
},
- B: {
- color: this.state.groupColorB,
- points: chartDataB
- }
- }});
- }
- }).finally(() => {
- this.setState({ loading: false })
- });
+ });
+ }
+ })
+ .finally(() => {
+ this.setState({ loading: false });
+ });
} else {
- let { promise, xhr } = this.context.getAnalyticsTimeSeries({
+ const { promise, xhr } = this.context.getAnalyticsTimeSeries({
...query,
pushStatusID: pushStatusID,
});
- promise.then((data) => {
- let chartData = formatAnalyticsData(data);
- if (chartData.length > 0) {
- this.setState({
- chartData: {
- pushes: {
- color: this.state.standardColor,
- points: chartData
- }
- }});
- }
- }).finally(() => {
- this.setState({ loading: false })
- });
+ promise
+ .then(data => {
+ const chartData = formatAnalyticsData(data);
+ if (chartData.length > 0) {
+ this.setState({
+ chartData: {
+ pushes: {
+ color: this.state.standardColor,
+ points: chartData,
+ },
+ },
+ });
+ }
+ })
+ .finally(() => {
+ this.setState({ loading: false });
+ });
this.xhrHandles = [xhr];
}
});
@@ -331,13 +364,16 @@ class PushDetails extends DashboardView {
}
componentWillReceiveProps(nextProps) {
- if(this.props.params.pushId !== nextProps.params.pushId) {
- this.setState( {loading: true });
- this.context.fetchPushDetails(nextProps.params.pushId).then((pushDetails) => {
- this.setState({ pushDetails });
- }).finally(() => {
- this.setState({ loading: false })
- });
+ if (this.props.params.pushId !== nextProps.params.pushId) {
+ this.setState({ loading: true });
+ this.context
+ .fetchPushDetails(nextProps.params.pushId)
+ .then(pushDetails => {
+ this.setState({ pushDetails });
+ })
+ .finally(() => {
+ this.setState({ loading: false });
+ });
}
}
@@ -346,43 +382,69 @@ class PushDetails extends DashboardView {
let experimentInfo = null;
let flowFooterDetails = null;
- let pushDetails = this.state.pushDetails;
- let statistics = pushDetails.statistics;
+ const pushDetails = this.state.pushDetails;
+ const statistics = pushDetails.statistics;
- let learnMore = (
- Learn more
+ const learnMore = (
+
+ Learn more
+
);
- if (pushDetails.exp_type === 'time' || pushDetails.launch_info === null || (pushDetails.launch_info && pushDetails.launch_info.percent === 0)) {
- let headline = statistics.confidence_interval ?
- (
-
- Group {statistics.winner}
- is the winner!
-
- ) :
- 'Not enough data to automatically determine the winner';
- let subline = [];
+ if (
+ pushDetails.exp_type === 'time' ||
+ pushDetails.launch_info === null ||
+ (pushDetails.launch_info && pushDetails.launch_info.percent === 0)
+ ) {
+ const headline = statistics.confidence_interval ? (
+
+ Group {statistics.winner}
+ is the winner!
+
+ ) : (
+ 'Not enough data to automatically determine the winner'
+ );
+ const subline = [];
if (statistics.confidence_interval) {
subline.push(
-
- We are highly confident that if this experiment were repeated,
{pushDetails.exp_type} {statistics.winner} would yield an open rate that’s
-
{statistics.confidence_interval[0]} —
{statistics.confidence_interval[statistics.confidence_interval.length-1]}
- percentage points higher than sending
{getExperimentLoser(statistics.winner)} .
+
+ We are highly confident that if this experiment were repeated,{' '}
+
+ {pushDetails.exp_type} {statistics.winner}
+ {' '}
+ would yield an open rate that’s
+ {statistics.confidence_interval[0]} —
+ {statistics.confidence_interval[statistics.confidence_interval.length - 1]}
+ percentage points higher than sending{' '}
+
+ {getExperimentLoser(statistics.winner)}
+
+ .
);
}
if (pushDetails.launch_info === null) {
subline.push(
-
- You already sent { pushDetails.launch_choice === null ? 'a message' :
Message {pushDetails.launch_choice} } to the Launch Group (devices outside groups A & B). {
View Results}
+
+ You already sent{' '}
+ {pushDetails.launch_choice === null ? (
+ 'a message'
+ ) : (
+ Message {pushDetails.launch_choice}
+ )}{' '}
+ to the Launch Group (devices outside groups A & B).{' '}
+ {
+
+ View Results
+
+ }
);
} else {
subline.push(
-
+
You allocated all of this campaign’s devices to test groups A & B. {learnMore}
);
@@ -397,46 +459,53 @@ class PushDetails extends DashboardView {
} else if (statistics) {
isFlowView = true;
- let headline = statistics.confidence_interval ? 'Group {statistics.winner} is the winner!' : 'Not enough data to automatically determine the winner';
- let subline = [];
- let launchGroupFragment = 'These devices are outside test groups A & B.';
+ const headline = statistics.confidence_interval
+ ? 'Group {statistics.winner} is the winner!'
+ : 'Not enough data to automatically determine the winner';
+ const subline = [];
+ const launchGroupFragment = 'These devices are outside test groups A & B.';
subline.push(
-
Ready to send your campaign to the Launch Group?
- )
+
+ Ready to send your campaign to the Launch Group?
+
+ );
if (statistics.confidence_interval) {
subline.push(
-
- We are highly confident that sending {statistics.winner} to the Launch Group will yield an open rate that’s
-
{statistics.confidence_interval[0]} —
{statistics.confidence_interval[statistics.confidence_interval.length-1]}
- percentage points higher than sending {getExperimentLoser(statistics.winner)}. {learnMore}
+
+ We are highly confident that sending {statistics.winner} to the Launch Group will yield
+ an open rate that’s
+ {statistics.confidence_interval[0]} —
+ {statistics.confidence_interval[statistics.confidence_interval.length - 1]}
+ percentage points higher than sending {getExperimentLoser(statistics.winner)}.{' '}
+ {learnMore}
);
}
if (pushDetails.launch_info && pushDetails.launch_info.recipient_count) {
-
- Your Launch Group is {pushDetails.launch_info.percent}% (approximately {prettyNumber(pushDetails.launch_info.recipient_count)} ) of the devices in this campaign. {launchGroupFragment}
-
+
+ Your Launch Group is {pushDetails.launch_info.percent}% (approximately{' '}
+ {prettyNumber(pushDetails.launch_info.recipient_count)} ) of the devices
+ in this campaign. {launchGroupFragment}
+
;
} else {
subline.push(
-
- Your Launch Group is
{pushDetails.launch_info.percent}% of the devices in this campaign. {launchGroupFragment}
+
+ Your Launch Group is {pushDetails.launch_info.percent}% of the devices
+ in this campaign. {launchGroupFragment}
);
}
if (statistics.confidence_interval) {
- subline.push(
-
- Send the winner to the Launch Group.
-
- );
+ subline.push(
Send the winner to the Launch Group. );
} else {
subline.push(
-
- We recommend waiting for more data from test groups, but you can still choose a message and send it to the Launch Group.
+
+ We recommend waiting for more data from test groups, but you can still choose a message
+ and send it to the Launch Group.
);
}
@@ -458,21 +527,25 @@ class PushDetails extends DashboardView {
}
renderPushRates(experimentInfo) {
- let pushDetails = this.state.pushDetails;
+ const pushDetails = this.state.pushDetails;
if (!pushDetails.id) {
return null;
}
- let launchChoice = pushDetails.launch_choice;
- let isMessageType = pushDetails.exp_type === 'message';
+ const launchChoice = pushDetails.launch_choice;
+ const isMessageType = pushDetails.exp_type === 'message';
let res = null;
let prevLaunchGroup = null;
- let alert = getMessage(pushDetails.get('payload'));
+ const alert = getMessage(pushDetails.get('payload'));
if (pushDetails && pushDetails.experiment_push_id) {
prevLaunchGroup = (
- This push is the Launch Group for a previous experiment.
+ This push is the Launch Group for a previous{' '}
+
+ experiment
+
+ .
);
@@ -482,34 +555,42 @@ class PushDetails extends DashboardView {
res = (
- {getExperimentPartial(pushDetails, 'a', isMessageType, { color: this.state.groupColorA })}
- {getExperimentPartial(pushDetails, 'b', isMessageType, { color: this.state.groupColorB })}
+ {getExperimentPartial(pushDetails, 'a', isMessageType, {
+ color: this.state.groupColorA,
+ })}
+ {getExperimentPartial(pushDetails, 'b', isMessageType, {
+ color: this.state.groupColorB,
+ })}
- {
- !isMessageType ?
-
-
MESSAGE SENT
-
{getMessage(pushDetails.group_a.payload)}
-
:
- null
- }
+ {!isMessageType ? (
+
+
MESSAGE SENT
+
{getMessage(pushDetails.group_a.payload)}
+
+ ) : null}
{prevLaunchGroup}
{experimentInfo}
-
{this.state.groupStatusA}
+
+ {this.state.groupStatusA}
+
+ customColor={this.state.groupColorA}
+ />
-
{this.state.groupStatusB}
+
+ {this.state.groupStatusB}
+
+ customColor={this.state.groupColorB}
+ />
);
@@ -518,14 +599,14 @@ class PushDetails extends DashboardView {
MESSAGE SENT
- {
- (typeof alert === 'object') ?
-
-
{alert.title}
-
{alert.body}
-
:
-
{alert}
- }
+ {typeof alert === 'object' ? (
+
+
{alert.title}
+
{alert.body}
+
+ ) : (
+
{alert}
+ )}
{getSentInfo(pushDetails.get('pushTime'), pushDetails.get('expiration'))}
@@ -535,7 +616,8 @@ class PushDetails extends DashboardView {
+ customColor={this.state.standardColor}
+ />
);
}
@@ -552,20 +634,21 @@ class PushDetails extends DashboardView {
width={800}
height={400}
data={this.state.chartData}
- formatter={(value) => value + ' push' + (value !== 1 ? 'es' : '' )} />
+ formatter={value => value + ' push' + (value !== 1 ? 'es' : '')}
+ />
- )
+ );
} else {
return null;
}
}
renderTargetTable() {
- let classes = this.props.schema.data.get('classes');
- let schema = {};
- if(classes){
- let installations = classes.get('_Installation');
- if(typeof(installations) !== 'undefined'){
+ const classes = this.props.schema.data.get('classes');
+ const schema = {};
+ if (classes) {
+ const installations = classes.get('_Installation');
+ if (typeof installations !== 'undefined') {
installations.forEach((type, col) => {
schema[col] = type;
});
@@ -575,76 +658,92 @@ class PushDetails extends DashboardView {
}
renderStatusTable() {
- let pushDetails = this.state.pushDetails;
+ const pushDetails = this.state.pushDetails;
return getStatusTable(pushDetails, pushDetails.is_exp ? true : false);
}
handlePushSubmit(changes) {
- let promise = new Promise();
- this.context.launchExperiment(this.props.params.pushId, changes).then(({ error }) => {
- //navigate to push index page and clear cache once push store is created
- if (error) {
+ const promise = new Promise();
+ this.context.launchExperiment(this.props.params.pushId, changes).then(
+ ({ error }) => {
+ //navigate to push index page and clear cache once push store is created
+ if (error) {
+ promise.reject({ error });
+ } else {
+ this.props.navigate(generatePath(this.context, 'push/activity'));
+ }
+ },
+ error => {
promise.reject({ error });
- } else {
- this.props.navigate(generatePath(this.context, 'push/activity'));
}
- }, (error) => {
- promise.reject({ error });
- });
+ );
return promise;
}
renderDeliveryContent(fields, setField) {
- let deliveryContent = [];
+ const deliveryContent = [];
deliveryContent.push(
}
- input={
} />
+ label={
}
+ input={
+
+ }
+ />
);
- if(fields.push_time_type !== 'now'){
+ if (fields.push_time_type !== 'now') {
deliveryContent.push(
}
+ label={
}
input={
-
- } />
+
+ }
+ />
);
}
- if(fields.push_time_type !== 'now'){
+ if (fields.push_time_type !== 'now') {
return deliveryContent.concat([
}
- input={
- } />,
-
- Installations without a time zone will not receive this campaign.
-
+ key="localTime"
+ label={
+
+ }
+ input={
}
+ />,
+
+
+ Installations without a time zone will not receive this campaign.
+
+ ,
]);
} else {
return deliveryContent;
}
}
- renderSecondaryFooterButton({setField}) {
+ renderSecondaryFooterButton({ setField }) {
let color = this.state.standardColor;
if (this.state.selectedGroup === DROPDOWN_KEY_GROUP_A) {
color = this.state.groupColorA;
@@ -652,32 +751,45 @@ class PushDetails extends DashboardView {
color = this.state.groupColorB;
}
- let colorKey = Object.keys(COLOR_MAP).find((key) => {
+ const colorKey = Object.keys(COLOR_MAP).find(key => {
return COLOR_MAP[key] === color;
});
- return
{
- //TODO: (peterjs) we should avoid content matching
- let selectedGroupId = selectedGroup === DROPDOWN_KEY_GROUP_A ? this.state.pushDetails.group_a.id : this.state.pushDetails.group_b.id;
- setField('winning_message_id', selectedGroupId);
- this.setState({ selectedGroup });
- }}
- options={[
- {key: DROPDOWN_KEY_GROUP_A, style: { color: this.state.groupColorA }},
- {key: DROPDOWN_KEY_GROUP_B, style: { color: this.state.groupColorB }}]} />
+ return (
+ {
+ //TODO: (peterjs) we should avoid content matching
+ const selectedGroupId =
+ selectedGroup === DROPDOWN_KEY_GROUP_A
+ ? this.state.pushDetails.group_a.id
+ : this.state.pushDetails.group_b.id;
+ setField('winning_message_id', selectedGroupId);
+ this.setState({ selectedGroup });
+ }}
+ options={[
+ {
+ key: DROPDOWN_KEY_GROUP_A,
+ style: { color: this.state.groupColorA },
+ },
+ {
+ key: DROPDOWN_KEY_GROUP_B,
+ style: { color: this.state.groupColorB },
+ },
+ ]}
+ />
+ );
}
- renderForm(flowFooterDetails, { fields, setField }) {
- let classes = this.props.schema.data.get('classes');
- let schema = {};
- if(classes){
- let installations = classes.get('_Installation');
- if(typeof(installations) !== 'undefined'){
+ renderForm(flowFooterDetails, { fields, setField }) {
+ const classes = this.props.schema.data.get('classes');
+ const schema = {};
+ if (classes) {
+ const installations = classes.get('_Installation');
+ if (typeof installations !== 'undefined') {
installations.forEach((type, col) => {
schema[col] = type;
});
@@ -687,12 +799,16 @@ class PushDetails extends DashboardView {
return (
+ legend="Choose a delivery time"
+ description="We can send the campaign immediately, or any time in the next 2 weeks."
+ >
{this.renderDeliveryContent(fields, setField)}
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
{PushHelper.renderExpirationContent(fields, setField)}
@@ -704,7 +820,7 @@ class PushDetails extends DashboardView {
if (this.state.loading) {
return;
}
- let { isFlowView, experimentInfo, flowFooterDetails } = this.experimentInfoHelper();
+ const { isFlowView, experimentInfo, flowFooterDetails } = this.experimentInfoHelper();
return (
@@ -713,10 +829,10 @@ class PushDetails extends DashboardView {
{this.renderTargetTable()}
{this.renderStatusTable()}
- { isFlowView ?
+ {isFlowView ? (
this.handlePushSubmit(changes)}
initialChanges={{
@@ -735,11 +851,15 @@ class PushDetails extends DashboardView {
renderForm={this.renderForm.bind(this, flowFooterDetails)}
defaultFooterMessage={flowFooterDetails}
footerContents={() => flowFooterDetails}
- validate={() => this.state.selectedGroup === undefined ? 'use default' : '' }
- secondaryButton={args => this.renderSecondaryFooterButton(args)}/> :
- null
- }
-
+ validate={() => (this.state.selectedGroup === undefined ? 'use default' : '')}
+ secondaryButton={args => this.renderSecondaryFooterButton(args)}
+ />
+ ) : null}
+
);
}
diff --git a/src/dashboard/Push/PushIndex.react.js b/src/dashboard/Push/PushIndex.react.js
index 79f39d55ff..8ec1e8c801 100644
--- a/src/dashboard/Push/PushIndex.react.js
+++ b/src/dashboard/Push/PushIndex.react.js
@@ -6,20 +6,20 @@
* the root directory of this source tree.
*/
import * as PushConstants from './PushConstants';
-import * as DateUtils from 'lib/DateUtils';
-import Button from 'components/Button/Button.react';
-import CategoryList from 'components/CategoryList/CategoryList.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import EmptyState from 'components/EmptyState/EmptyState.react';
-import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import LoaderDots from 'components/LoaderDots/LoaderDots.react';
-import React from 'react';
-import SidebarAction from 'components/Sidebar/SidebarAction';
-import StatusIndicator from 'components/StatusIndicator/StatusIndicator.react';
-import styles from './PushIndex.scss';
-import stylesTable from 'dashboard/TableView.scss';
-import TableHeader from 'components/Table/TableHeader.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import * as DateUtils from 'lib/DateUtils';
+import Button from 'components/Button/Button.react';
+import CategoryList from 'components/CategoryList/CategoryList.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
+import LoaderDots from 'components/LoaderDots/LoaderDots.react';
+import React from 'react';
+import SidebarAction from 'components/Sidebar/SidebarAction';
+import StatusIndicator from 'components/StatusIndicator/StatusIndicator.react';
+import styles from './PushIndex.scss';
+import stylesTable from 'dashboard/TableView.scss';
+import TableHeader from 'components/Table/TableHeader.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
@@ -63,42 +63,42 @@ const EXPERIMENT_GROUP = {
A: 'GROUP B',
B: 'GROUP A',
launch: 'LAUNCH GROUP',
-}
+};
const DEFAULT_EMPTY_STATE_CONTENT = {
description: 'You may need to configure push notifications for your app.',
- cta: 'Get started with Parse Push'
+ cta: 'Get started with Parse Push',
};
-let getPushStatusType = (pushData) => {
- if(pushData[PushConstants.EXPERIMENT_FIELD]){
+const getPushStatusType = pushData => {
+ if (pushData[PushConstants.EXPERIMENT_FIELD]) {
return PUSH_TYPE_EXPERIMENT;
- } else if (pushData[PushConstants.TRANSLATION_ID_FIELD]){
+ } else if (pushData[PushConstants.TRANSLATION_ID_FIELD]) {
return PUSH_TYPE_TRANSLATION;
- } else if (pushData[PushConstants.SOURCE_FIELD] === 'webui'){
+ } else if (pushData[PushConstants.SOURCE_FIELD] === 'webui') {
return PUSH_TYPE_CAMPAIGN;
} else {
return PUSH_TYPE_API;
}
-}
+};
-let isChannelTargeted = (pushData) => {
- let query = pushData[PushConstants.QUERY_FIELD];
- if(!query) {
+const isChannelTargeted = pushData => {
+ const query = pushData[PushConstants.QUERY_FIELD];
+ if (!query) {
return false;
}
- let queryJSON = JSON.parse(query);
- let channels = queryJSON.channels;
+ const queryJSON = JSON.parse(query);
+ const channels = queryJSON.channels;
if (!channels) {
return false;
}
- let inClause = (channels.constructor === Object) && channels['$in'];
- let eqClause = channels.constructor === String;
+ const inClause = channels.constructor === Object && channels['$in'];
+ const eqClause = channels.constructor === String;
let additionalKeys = false;
- for (let key in queryJSON) {
+ for (const key in queryJSON) {
if (Object.prototype.hasOwnProperty.call(queryJSON, key)) {
if (key !== 'deviceType' && key !== 'channels') {
additionalKeys = true;
@@ -107,175 +107,174 @@ let isChannelTargeted = (pushData) => {
}
return (inClause || eqClause) && !additionalKeys;
-}
+};
-let getPushTarget = (pushData, availableDevices) => {
- if (isChannelTargeted(pushData)){
+const getPushTarget = (pushData, availableDevices) => {
+ if (isChannelTargeted(pushData)) {
return 'Channels';
}
if (availableDevices === undefined) {
- return ( );
+ return ;
}
- let query = JSON.parse(pushData.query);
- if (query.deviceType && query.deviceType['$in'] && query.deviceType['$in'].length < availableDevices.length) {
+ const query = JSON.parse(pushData.query);
+ if (
+ query.deviceType &&
+ query.deviceType['$in'] &&
+ query.deviceType['$in'].length < availableDevices.length
+ ) {
return 'Segment';
}
return 'Everyone';
-}
+};
-let getPushName = (pushData) => {
- let title = pushData[PushConstants.TITLE_FIELD];
- if(title){
- return (
- {title}
- );
+const getPushName = pushData => {
+ const title = pushData[PushConstants.TITLE_FIELD];
+ if (title) {
+ return {title} ;
} else {
let payload = pushData[PushConstants.PAYLOAD_FIELD] || '';
try {
payload = JSON.parse(payload);
- } catch(e) {/**/}
+ } catch (e) {
+ /**/
+ }
if (typeof payload === 'object') {
if (typeof payload.alert === 'string') {
return payload.alert;
- } else if (typeof payload.alert === 'object' && payload.alert.title !== undefined) {
- return payload.alert.title;
+ } else if (typeof payload.alert === 'object' && payload.alert.title !== undefined) {
+ return payload.alert.title;
}
return payload.alert ? JSON.stringify(payload.alert) : JSON.stringify(payload);
} else {
return '';
}
}
-}
+};
-let getPushCount = (pushData) => {
- let count = pushData[PushConstants.SENT_FIELD];
- if(count != undefined){
- return (
- {count}
- );
+const getPushCount = pushData => {
+ const count = pushData[PushConstants.SENT_FIELD];
+ if (count != undefined) {
+ return {count} ;
} else {
- return (
- N/A
- );
+ return N/A ;
}
-}
+};
-let emptyStateContent = {
- 'all': {
+const emptyStateContent = {
+ all: {
title: 'No pushes to display yet.',
description: DEFAULT_EMPTY_STATE_CONTENT.description,
cta: DEFAULT_EMPTY_STATE_CONTENT.cta,
},
- 'campaign': {
+ campaign: {
title: 'No push campaigns to display yet.',
description: DEFAULT_EMPTY_STATE_CONTENT.description,
cta: DEFAULT_EMPTY_STATE_CONTENT.cta,
},
- 'experiment': {
+ experiment: {
title: 'No push experiments to display yet',
description: DEFAULT_EMPTY_STATE_CONTENT.description,
cta: DEFAULT_EMPTY_STATE_CONTENT.cta,
},
- 'api': {
+ api: {
title: 'No API pushes to display yet',
description: DEFAULT_EMPTY_STATE_CONTENT.description,
cta: DEFAULT_EMPTY_STATE_CONTENT.cta,
- }
+ },
};
-let getExperimentInfo = (experiment) => {
- if(!experiment){
+const getExperimentInfo = experiment => {
+ if (!experiment) {
return '';
}
- return (
- {EXPERIMENT_GROUP[experiment.group]}
- );
-}
+ return {EXPERIMENT_GROUP[experiment.group]}
;
+};
-let getTranslationInfo = (translationLocale) => {
- if(!translationLocale){
+const getTranslationInfo = translationLocale => {
+ if (!translationLocale) {
return '';
}
- return (
- {translationLocale.toUpperCase()}
- );
-}
+ return {translationLocale.toUpperCase()}
;
+};
-let formatStatus = (status) => {
- let color = PUSH_STATUS_COLOR[status];
- let text = PUSH_STATUS_CONTENT[status];
- return (
-
- );
-}
+const formatStatus = status => {
+ const color = PUSH_STATUS_COLOR[status];
+ const text = PUSH_STATUS_CONTENT[status];
+ return ;
+};
-let getPushTime = (pushTime, updatedAt) => {
- let time = pushTime || updatedAt;
- let dateTime = new Date(time);
- let isLocal = typeof time === 'string' && time.indexOf('Z') === -1;
- let timeContent = DateUtils.yearMonthDayTimeFormatter(dateTime, !isLocal);
- let result = [];
+const getPushTime = (pushTime, updatedAt) => {
+ const time = pushTime || updatedAt;
+ const dateTime = new Date(time);
+ const isLocal = typeof time === 'string' && time.indexOf('Z') === -1;
+ const timeContent = DateUtils.yearMonthDayTimeFormatter(dateTime, !isLocal);
+ const result = [];
if (isLocal) {
result.push(
- LOCAL TIME
+
+ LOCAL TIME
+
);
}
- result.push(
- {timeContent}
- );
+ result.push({timeContent}
);
return result;
-}
+};
@withRouter
class PushIndex extends DashboardView {
constructor() {
super();
this.section = 'Push';
- this.subsection = 'Past Pushes'
+ this.subsection = 'Past Pushes';
this.action = new SidebarAction('Send a push', this.navigateToNew.bind(this));
this.state = {
pushes: [],
loading: true,
paginationInfo: undefined,
availableDevices: undefined,
- }
+ };
this.xhrHandle = null;
}
- handleFetch(category, page, limit){
+ handleFetch(category, page, limit) {
limit = limit || PUSH_DEFAULT_LIMIT;
page = page || 0;
- let promise = this.context.fetchPushNotifications(category, page, limit);
-
- promise.then((pushes) => {
- this.setState({
- paginationInfo: {
- has_more: (pushes.length == limit),
- page_num: page
- },
- pushes: this.state.pushes.concat(pushes),
- });
- }).finally(() => {
- this.setState({
- loading: false,
- showMoreLoading: false,
+ const promise = this.context.fetchPushNotifications(category, page, limit);
+
+ promise
+ .then(pushes => {
+ this.setState({
+ paginationInfo: {
+ has_more: pushes.length == limit,
+ page_num: page,
+ },
+ pushes: this.state.pushes.concat(pushes),
+ });
+ })
+ .finally(() => {
+ this.setState({
+ loading: false,
+ showMoreLoading: false,
+ });
});
- });
}
componentWillMount() {
this.handleFetch(this.props.params.category);
//TODO: make xhr map and generic abort for existing xhrs.
- this.context.fetchAvailableDevices().then(({ available_devices }) => {
- this.setState({
- availableDevices: available_devices
- });
- }, () => {
- this.setState({
- availableDevices: PushConstants.DEFAULT_DEVICES
- });
- });
+ this.context.fetchAvailableDevices().then(
+ ({ available_devices }) => {
+ this.setState({
+ availableDevices: available_devices,
+ });
+ },
+ () => {
+ this.setState({
+ availableDevices: PushConstants.DEFAULT_DEVICES,
+ });
+ }
+ );
}
componentWillUnmount() {
@@ -320,18 +319,21 @@ class PushIndex extends DashboardView {
}
renderSidebar() {
- let current = this.props.params.category || '';
+ const current = this.props.params.category || '';
return (
-
+
);
}
@@ -356,37 +358,55 @@ class PushIndex extends DashboardView {
renderToolbar() {
return (
-
+ details={'push'}
+ >
+
);
}
renderHeaders() {
return [
- Type ,
- Target ,
- Pushes Sent ,
- Name ,
- Time ,
- Status ,
+
+ Type
+ ,
+
+ Target
+ ,
+
+ Pushes Sent
+ ,
+
+ Name
+ ,
+
+ Time
+ ,
+
+ Status
+ ,
];
}
//NOTE: current solution is 'Show more' button. Can be changed to infinite scroll if req.
renderExtras() {
- let paginationInfo = this.state.paginationInfo;
+ const paginationInfo = this.state.paginationInfo;
if (!paginationInfo) {
return null;
}
- if(paginationInfo.has_more && !this.state.loading){
+ if (paginationInfo.has_more && !this.state.loading) {
return (
-
+
);
} else {
@@ -395,21 +415,22 @@ class PushIndex extends DashboardView {
}
renderEmpty() {
- let type = this.props.params.category || PUSH_TYPE_ALL;
+ const type = this.props.params.category || PUSH_TYPE_ALL;
return (
+ action={'http://docs.parseplatform.org/ios/guide/#push-notifications'}
+ />
);
}
//using custom renderContent as location of 'extras' are in different location
renderContent() {
- let toolbar = this.renderToolbar();
- let data = this.tableData();
+ const toolbar = this.renderToolbar();
+ const data = this.tableData();
let content = null;
let headers = null;
if (data !== undefined) {
@@ -422,9 +443,7 @@ class PushIndex extends DashboardView {
content = (
-
- {data.map((row) => this.renderRow(row))}
-
+ {data.map(row => this.renderRow(row))}
);
@@ -432,8 +451,8 @@ class PushIndex extends DashboardView {
}
}
}
- let extras = this.renderExtras ? this.renderExtras() : null;
- let loading = this.state ? this.state.loading : false;
+ const extras = this.renderExtras ? this.renderExtras() : null;
+ const loading = this.state ? this.state.loading : false;
return (
diff --git a/src/dashboard/Push/PushNew.react.js b/src/dashboard/Push/PushNew.react.js
index 48b056f5dd..87da1fa2b4 100644
--- a/src/dashboard/Push/PushNew.react.js
+++ b/src/dashboard/Push/PushNew.react.js
@@ -6,61 +6,68 @@
* the root directory of this source tree.
*/
import * as PushAudiencesStore from 'lib/stores/PushAudiencesStore';
-import * as PushConstants from './PushConstants';
-import * as PushHelper from './PushComposerHelper.react';
-import * as SchemaStore from 'lib/stores/SchemaStore';
-import Button from 'components/Button/Button.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FieldStyles from 'components/Field/Field.scss';
-import FlowView from 'components/FlowView/FlowView.react';
-import joinWithFinal from 'lib/joinWithFinal';
-import Label from 'components/Label/Label.react';
-import Option from 'components/Dropdown/Option.react';
-import Parse from 'parse';
-import pluralize from 'lib/pluralize';
-import PushAudiencesData from './PushAudiencesData.react';
-import PushPreview from 'components/PushPreview/PushPreview.react';
-import queryFromFilters from 'lib/queryFromFilters';
-import Range from 'components/Range/Range.react';
-import React from 'react';
-import SliderWrap from 'components/SliderWrap/SliderWrap.react';
-import styles from 'dashboard/Push/Push.scss';
-import subscribeTo from 'lib/subscribeTo';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import { Directions } from 'lib/Constants';
+import * as PushConstants from './PushConstants';
+import * as PushHelper from './PushComposerHelper.react';
+import * as SchemaStore from 'lib/stores/SchemaStore';
+import Button from 'components/Button/Button.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import DateTimeInput from 'components/DateTimeInput/DateTimeInput.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FieldStyles from 'components/Field/Field.scss';
+import FlowView from 'components/FlowView/FlowView.react';
+import joinWithFinal from 'lib/joinWithFinal';
+import Label from 'components/Label/Label.react';
+import Option from 'components/Dropdown/Option.react';
+import Parse from 'parse';
+import pluralize from 'lib/pluralize';
+import PushAudiencesData from './PushAudiencesData.react';
+import PushPreview from 'components/PushPreview/PushPreview.react';
+import queryFromFilters from 'lib/queryFromFilters';
+import Range from 'components/Range/Range.react';
+import React from 'react';
+import SliderWrap from 'components/SliderWrap/SliderWrap.react';
+import styles from 'dashboard/Push/Push.scss';
+import subscribeTo from 'lib/subscribeTo';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import { Directions } from 'lib/Constants';
import { extractExpiration, extractPushTime } from 'lib/extractTime';
import generatePath from 'lib/generatePath';
import { withRouter } from 'lib/withRouter';
const PARSE_SERVER_SUPPORTS_AB_TESTING = false;
-let formatErrorMessage = (emptyInputMessages, key) => {
- let boldMessages = emptyInputMessages.map((message) => {
- return {message}
+const formatErrorMessage = (emptyInputMessages, key) => {
+ const boldMessages = emptyInputMessages.map(message => {
+ return {message} ;
});
- return (Your {joinWithFinal(null, boldMessages, ', ', boldMessages.length < 3 ? ' and ' : ', and ')} can’t be empty.
);
-}
+ return (
+
+ Your {joinWithFinal(null, boldMessages, ', ', boldMessages.length < 3 ? ' and ' : ', and ')}{' '}
+ can’t be empty.
+
+ );
+};
-let isValidJSON = (input) => {
+const isValidJSON = input => {
let parsedJSON = null;
try {
parsedJSON = JSON.parse(input);
- } catch (e) {/**/}
+ } catch (e) {
+ /**/
+ }
if (parsedJSON !== null) {
return true;
} else {
return false;
}
-}
+};
-let LocalizedMessageField = ({
+const LocalizedMessageField = ({
monospace,
id,
onChangeValue,
@@ -71,28 +78,34 @@ let LocalizedMessageField = ({
data,
deviceCount,
}) => {
- let deviceCountSegment = pluralize(deviceCount, 'device');
+ const deviceCountSegment = pluralize(deviceCount, 'device');
return (
Localized message
- REMOVE
+
+ REMOVE
+
+ label={
}
+ input={
+
}
- input={
- } />
+ />
Which locale are you targeting?
- { deviceCount !== undefined ?
- {deviceCountSegment}
:
- null
- }
+ {deviceCount !== undefined ? (
+ {deviceCountSegment}
+ ) : null}
- } />
+ }
+ />
}
input={
- onChangeLocale.call(undefined, id, nextLocaleOption, data, currentLocaleOption)}>
- {localeOptions && localeOptions.length > 0 ? localeOptions.map((option) => {
- return ({option} );
- }) : null}
+
+ onChangeLocale.call(undefined, id, nextLocaleOption, data, currentLocaleOption)
+ }
+ >
+ {localeOptions && localeOptions.length > 0
+ ? localeOptions.map(option => {
+ return {option} ;
+ })
+ : null}
- } />
+ }
+ />
);
-}
+};
const XHR_KEY = 'PushNew';
@@ -142,41 +163,42 @@ class PushNew extends DashboardView {
availableLocales: [],
localeDeviceCountMap: {},
loadingLocale: true,
- }
+ };
}
componentWillMount() {
this.props.schema.dispatch(SchemaStore.ActionTypes.FETCH);
- let options = { xhrKey: XHR_KEY };
+ const options = { xhrKey: XHR_KEY };
const query = new URLSearchParams(this.props.location.search);
if (query.has('audienceId')) {
options.limit = PushConstants.SHOW_MORE_LIMIT;
options.min = PushConstants.INITIAL_PAGE_SIZE;
this.setState({ initialAudienceId: query.get('audienceId') });
}
- this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.FETCH,
- options).then(() => {
- this.setState({ pushAudiencesFetched :true });
+ this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.FETCH, options).then(() => {
+ this.setState({ pushAudiencesFetched: true });
});
const available = this.context.isLocalizationAvailable();
if (available) {
const locales = this.context.fetchPushLocales();
- const filteredLocales = locales.filter((locale) => !(locale === '' || locale === undefined));
+ const filteredLocales = locales.filter(locale => !(locale === '' || locale === undefined));
this.setState({
isLocalizationAvailable: true,
locales: filteredLocales,
- availableLocales: filteredLocales
+ availableLocales: filteredLocales,
});
}
this.setState({
- loadingLocale: false
+ loadingLocale: false,
});
}
componentWillUnmount() {
- this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, { xhrKey: XHR_KEY});
- this.xhrs.forEach((xhr) => {
+ this.props.pushaudiences.dispatch(PushAudiencesStore.ActionTypes.ABORT_FETCH, {
+ xhrKey: XHR_KEY,
+ });
+ this.xhrs.forEach(xhr => {
if (xhr.abort) {
xhr.abort();
}
@@ -186,7 +208,8 @@ class PushNew extends DashboardView {
//TODO: scroll audience row into view if req.
handlePushSubmit(changes) {
- let payload = changes.data_type === 'json' ? JSON.parse(changes.data) : { alert: changes.data };
+ const payload =
+ changes.data_type === 'json' ? JSON.parse(changes.data) : { alert: changes.data };
if (changes.increment_badge) {
payload.badge = 'Increment';
}
@@ -195,7 +218,7 @@ class PushNew extends DashboardView {
// Gather the translations, and inject into the payload
const needle = 'translation[';
- Object.keys(changes).forEach((key) => {
+ Object.keys(changes).forEach(key => {
// translations are stored as `tranlation[lang]` strings as keys,
// this is why we slice it this way
if (key.indexOf(needle) === 0) {
@@ -204,19 +227,21 @@ class PushNew extends DashboardView {
}
});
- let body = {
+ const body = {
data: payload,
where: changes.target || new Parse.Query(Parse.Installation),
push_time,
};
Object.assign(body, extractExpiration(changes));
- let audience_id = changes.audience_id;
+ const audience_id = changes.audience_id;
// Only set the audience ID if it is a saved audience.
if (audience_id != PushConstants.NEW_SEGMENT_ID && audience_id != 'everyone') {
body.audience_id = audience_id;
- const pushAudience = this.props.pushaudiences.data.get('audiences').toJS()
- .find((a) => a.objectId === audience_id);
+ const pushAudience = this.props.pushaudiences.data
+ .get('audiences')
+ .toJS()
+ .find(a => a.objectId === audience_id);
body.where = pushAudience.query;
}
@@ -242,51 +267,76 @@ class PushNew extends DashboardView {
if (!fields.exp_enable) {
return null;
}
- let experimentContent = [
+ const experimentContent = [
}
- input={
- } />,
+ label={
+
+ }
+ input={
+
+ }
+ />,
}
- input={
- } />
+ key="testVariable"
+ label={
+
+ }
+ input={
+
+ }
+ />,
];
if (fields.exp_type === 'message') {
experimentContent.push(
}
- input={ {
- setField('exp_size_in_percent', value);
- let recipientCount = Math.floor(value * 0.01 * this.state.deviceCount);
- this.setState({ recipientCount });
- }} />
- } />
+ key="testSize"
+ label={
+
+ }
+ input={
+ {
+ setField('exp_size_in_percent', value);
+ const recipientCount = Math.floor(value * 0.01 * this.state.deviceCount);
+ this.setState({ recipientCount });
+ }}
+ />
+ }
+ />
);
if (this.state.audienceSizeSuggestion) {
let testSizeText = '';
- let allDevicesText = 'Based on your campaign size, we recommend that you include all devices in the test audience for better results.';
- let suggestion = this.state.audienceSizeSuggestion;
+ const allDevicesText =
+ 'Based on your campaign size, we recommend that you include all devices in the test audience for better results.';
+ const suggestion = this.state.audienceSizeSuggestion;
if (suggestion !== null && this.state.recipientCount !== null) {
if (suggestion > 0) {
@@ -305,15 +355,25 @@ class PushNew extends DashboardView {
}
}
experimentContent.push(
-
+
{testSizeText}
);
experimentContent.push(
-
+
-
Your campaign will be tested with {this.state.recipientCount} {this.state.recipientCount === 1 ? 'device' : 'devices'}
-
(after your experiment is over, you’ll be able to send the winner to the rest)
+
+ Your campaign will be tested with {this.state.recipientCount} {' '}
+ {this.state.recipientCount === 1 ? 'device' : 'devices'}
+
+
+ (after your experiment is over, you’ll be able to send the winner to the rest)
+
);
@@ -323,71 +383,99 @@ class PushNew extends DashboardView {
}
renderDeliveryContent(fields, setField) {
- let deliveryContent = [];
+ const deliveryContent = [];
if (fields.exp_enable && fields.exp_type === 'time') {
deliveryContent.push(
}
+ label={
+
+ }
input={
{
+ onChange={value => {
setField('push_time_1_iso', value);
PushHelper.setPushTimeField(setField, 'push_time_1', value, fields.local_time);
- }} />
- } />
+ }}
+ />
+ }
+ />
);
deliveryContent.push(
}
+ label={
+
+ }
input={
{
+ onChange={value => {
setField('push_time_2_iso', value);
PushHelper.setPushTimeField(setField, 'push_time_2', value, fields.local_time);
- }} />
- } />
+ }}
+ />
+ }
+ />
);
} else {
deliveryContent.push(
}
- input={ } />
+ label={ }
+ input={
+
+ }
+ />
);
//immediately when exp off, or immediately when experiment on with type message
- if (fields.push_time_type !== 'now' && (!fields.exp_enable || fields.exp_enable && fields.exp_type === 'message')) {
+ if (
+ fields.push_time_type !== 'now' &&
+ (!fields.exp_enable || (fields.exp_enable && fields.exp_type === 'message'))
+ ) {
deliveryContent.push(
}
+ label={
+
+ }
input={
{
+ onChange={value => {
setField('push_time_iso', value);
PushHelper.setPushTimeField(setField, 'push_time', value, fields.local_time);
- }} />
- } />
+ }}
+ />
+ }
+ />
);
}
}
@@ -396,21 +484,51 @@ class PushNew extends DashboardView {
if (fields.push_time_type !== 'now' || (fields.exp_enable && fields.exp_type === 'time')) {
return deliveryContent.concat([
}
- input={ {
- PushHelper.localTimeFormater(setField, 'push_time', fields.push_time_iso, value);
- PushHelper.localTimeFormater(setField, 'push_time_1', fields.push_time_1_iso, value);
- PushHelper.localTimeFormater(setField, 'push_time_2', fields.push_time_2_iso, value);
- PushHelper.localTimeFormater(setField, 'expiration_time', fields.expiration_time_iso, value);
- setField('local_time', value);
- }} />
- } />,
-
- Installations without a time zone will not receive this campaign.
-
+ key="local_time"
+ label={
+
+ }
+ input={
+ {
+ PushHelper.localTimeFormater(setField, 'push_time', fields.push_time_iso, value);
+ PushHelper.localTimeFormater(
+ setField,
+ 'push_time_1',
+ fields.push_time_1_iso,
+ value
+ );
+ PushHelper.localTimeFormater(
+ setField,
+ 'push_time_2',
+ fields.push_time_2_iso,
+ value
+ );
+ PushHelper.localTimeFormater(
+ setField,
+ 'expiration_time',
+ fields.expiration_time_iso,
+ value
+ );
+ setField('local_time', value);
+ }}
+ />
+ }
+ />,
+
+
+ Installations without a time zone will not receive this campaign.
+
+ ,
]);
} else {
return deliveryContent;
@@ -418,119 +536,143 @@ class PushNew extends DashboardView {
}
renderMessageContent(fields, setField) {
- let monospace = fields.data_type === 'json';
- let monospaceA = fields.data_type_1 === 'json';
- let monospaceB = fields.data_type_2 === 'json';
+ const monospace = fields.data_type === 'json';
+ const monospaceA = fields.data_type_1 === 'json';
+ const monospaceB = fields.data_type_2 === 'json';
if (fields.exp_enable && fields.exp_type === 'message') {
return [
}
- input={
- } />,
+ label={ }
+ input={
+
+ }
+ />,
}
- input={
- } />,
+ label={ }
+ input={
+
+ }
+ />,
}
- input={
- } />,
+ label={ }
+ input={
+
+ }
+ />,
}
- input={
- } />,
+ label={ }
+ input={
+
+ }
+ />,
];
}
return [
}
- input={
- } />,
+ label={ }
+ input={
+
+ }
+ />,
}
- input={
- } />
+ label={ }
+ input={
+
+ }
+ />,
];
}
renderForm({ fields, setField }) {
- let multiMessage = (fields.exp_enable && fields.exp_type === 'message');
+ const multiMessage = fields.exp_enable && fields.exp_type === 'message';
- let classes = this.props.schema.data.get('classes');
- let schema = {};
+ const classes = this.props.schema.data.get('classes');
+ const schema = {};
if (classes) {
- let installations = classes.get('_Installation');
- if (typeof(installations) !== 'undefined') {
+ const installations = classes.get('_Installation');
+ if (typeof installations !== 'undefined') {
installations.forEach((type, col) => {
schema[col] = type;
});
}
}
- let translationSegment = [];
+ const translationSegment = [];
if (this.state.isLocalizationAvailable && !fields.exp_enable) {
translationSegment.push(
}
- input={ {
- setField('translation_enable', value || null);
- }} />} />
+ key="slider"
+ label={ }
+ input={
+ {
+ setField('translation_enable', value || null);
+ }}
+ />
+ }
+ />
);
if (fields.translation_enable) {
//locales change based on existing selection
@@ -539,201 +681,244 @@ class PushNew extends DashboardView {
// add in another textarea input for localized message
translationSegment.push(
+ key="addLocalizationButton"
+ className={[FieldStyles.field, styles.localizationSegment].join(' ')}
+ >
{this.state.localizedMessages.map((message, i) => {
- return ( {
- let localizedMessages = this.state.localizedMessages;
- let availableLocales = this.state.availableLocales;
- localizedMessages.splice(id, 1);
- availableLocales.unshift(currentLocaleOption);
- this.setState({
- localizedMessages,
- availableLocales: availableLocales.sort(),
- });
- }}
- deviceCount={this.state.localeDeviceCountMap[message.locale]}
- onChangeValue={(id, locale, value) => {
- let localizedMessages = this.state.localizedMessages;
- localizedMessages[id] = {
- locale,
- value
- };
- this.setState({
- localizedMessages,
- });
- setField(`translation[${locale}]`, value);
- }}
- onChangeLocale={(id, locale, value, prevLocale) => {
- let localizedMessages = this.state.localizedMessages;
- let availableLocales = this.state.availableLocales;
- localizedMessages[id] = {
- locale,
- value
- };
-
- availableLocales.splice(availableLocales.indexOf(locale));
- availableLocales.unshift(prevLocale);
- setField(`translation[${prevLocale}]`, null);
-
- let {xhr, promise} = this.context.fetchPushLocaleDeviceCount(fields.audience_id, fields.target, this.state.locales);
- promise.then((localeDeviceCountMap) => {
- this.setState({ localeDeviceCountMap })
- });
- this.xhrs.push(xhr);
+ return (
+ {
+ const localizedMessages = this.state.localizedMessages;
+ const availableLocales = this.state.availableLocales;
+ localizedMessages.splice(id, 1);
+ availableLocales.unshift(currentLocaleOption);
+ this.setState({
+ localizedMessages,
+ availableLocales: availableLocales.sort(),
+ });
+ }}
+ deviceCount={this.state.localeDeviceCountMap[message.locale]}
+ onChangeValue={(id, locale, value) => {
+ const localizedMessages = this.state.localizedMessages;
+ localizedMessages[id] = {
+ locale,
+ value,
+ };
+ this.setState({
+ localizedMessages,
+ });
+ setField(`translation[${locale}]`, value);
+ }}
+ onChangeLocale={(id, locale, value, prevLocale) => {
+ const localizedMessages = this.state.localizedMessages;
+ const availableLocales = this.state.availableLocales;
+ localizedMessages[id] = {
+ locale,
+ value,
+ };
- this.setState({
- localizedMessages,
- availableLocales: availableLocales.sort(),
- });
- setField(`translation[${locale}]`, value);
- }}
- key={i} />);
+ availableLocales.splice(availableLocales.indexOf(locale));
+ availableLocales.unshift(prevLocale);
+ setField(`translation[${prevLocale}]`, null);
+
+ const { xhr, promise } = this.context.fetchPushLocaleDeviceCount(
+ fields.audience_id,
+ fields.target,
+ this.state.locales
+ );
+ promise.then(localeDeviceCountMap => {
+ this.setState({ localeDeviceCountMap });
+ });
+ this.xhrs.push(xhr);
+
+ this.setState({
+ localizedMessages,
+ availableLocales: availableLocales.sort(),
+ });
+ setField(`translation[${locale}]`, value);
+ }}
+ key={i}
+ />
+ );
})}
- { !this.state.loadingLocale && this.state.locales.length === 0 ?
+ {!this.state.loadingLocale && this.state.locales.length === 0 ? (
Please follow this guide to setup the push locales feature
- :
- !this.state.loadingLocale && this.state.availableLocales.length === 0 ? null :
+
+ ) : !this.state.loadingLocale && this.state.availableLocales.length === 0 ? null : (
{
- let currentLocale = this.state.availableLocales[0];
- this.setState({
- localizedMessages: this.state.localizedMessages.concat([{
- locale: currentLocale
- }]),
- availableLocales: this.state.availableLocales.slice(1)
- }, () => {
- let {xhr, promise} = this.context.fetchPushLocaleDeviceCount(fields.audience_id, fields.target, this.state.locales);
- promise.then((localeDeviceCountMap) => {
- this.setState({ localeDeviceCountMap })
- });
- this.xhrs.push(xhr);
- });
- }} />
- }
+ const currentLocale = this.state.availableLocales[0];
+ this.setState(
+ {
+ localizedMessages: this.state.localizedMessages.concat([
+ {
+ locale: currentLocale,
+ },
+ ]),
+ availableLocales: this.state.availableLocales.slice(1),
+ },
+ () => {
+ const { xhr, promise } = this.context.fetchPushLocaleDeviceCount(
+ fields.audience_id,
+ fields.target,
+ this.state.locales
+ );
+ promise.then(localeDeviceCountMap => {
+ this.setState({ localeDeviceCountMap });
+ });
+ this.xhrs.push(xhr);
+ }
+ );
+ }}
+ />
+ )}
);
}
}
- const recipientsFields =
- {
- this.setState({ deviceCount, audienceId });
- setField('audience_id', audienceId);
- if (audienceId === PushConstants.NEW_SEGMENT_ID) {
- // Horrible code here is due to old rails code that sent pushes through it's own endpoint, while Parse Server sends through Parse.Push.
- // Ideally, we would pass a Parse.Query around everywhere.
- if (queryOrFilters instanceof Parse.Query) {
- setField('target', queryOrFilters);
- } else {
- setField('target', queryFromFilters('_Installation', queryOrFilters));
+ const recipientsFields = (
+
+ {
+ this.setState({ deviceCount, audienceId });
+ setField('audience_id', audienceId);
+ if (audienceId === PushConstants.NEW_SEGMENT_ID) {
+ // Horrible code here is due to old rails code that sent pushes through it's own endpoint, while Parse Server sends through Parse.Push.
+ // Ideally, we would pass a Parse.Query around everywhere.
+ if (queryOrFilters instanceof Parse.Query) {
+ setField('target', queryOrFilters);
+ } else {
+ setField('target', queryFromFilters('_Installation', queryOrFilters));
+ }
}
- }
- }} />
-
+ }}
+ />
+
+ );
- const abTestingFields = PARSE_SERVER_SUPPORTS_AB_TESTING ?
- }
- input={ {
- if (!this.state.audienceSizeSuggestion) {
- this.context.fetchPushAudienceSizeSuggestion().then(({ audience_size }) => {
- this.setState({
- audienceSizeSuggestion: audience_size
- });
- });
- // calculate initial recipient count
- this.setState({
- recipientCount: Math.floor(this.state.deviceCount * 0.5)
- });
- }
- // disable translation if experiment is enabled
- if (fields.translation_enable && value) {
- setField('translation_enable',null);
+ const abTestingFields = PARSE_SERVER_SUPPORTS_AB_TESTING ? (
+
+ }
+ input={
+ {
+ if (!this.state.audienceSizeSuggestion) {
+ this.context.fetchPushAudienceSizeSuggestion().then(({ audience_size }) => {
+ this.setState({
+ audienceSizeSuggestion: audience_size,
+ });
+ });
+ // calculate initial recipient count
+ this.setState({
+ recipientCount: Math.floor(this.state.deviceCount * 0.5),
+ });
+ }
+ // disable translation if experiment is enabled
+ if (fields.translation_enable && value) {
+ setField('translation_enable', null);
+ }
+ setField('exp_enable', value || null);
+ }}
+ />
}
- setField('exp_enable', value || null);
- }} />} />
- {this.renderExperimentContent(fields, setField)}
- : null;
+ />
+ {this.renderExperimentContent(fields, setField)}
+
+ ) : null;
- const {push} = this.context.serverInfo.features;
+ const { push } = this.context.serverInfo.features;
const hasScheduledPushSupport = push && push.scheduledPush;
- const timeFieldsLegend = hasScheduledPushSupport ?
- 'Choose a delivery time' :
- 'Choose expiry';
+ const timeFieldsLegend = hasScheduledPushSupport ? 'Choose a delivery time' : 'Choose expiry';
- const timeFieldsDescription = hasScheduledPushSupport ?
- 'We can send the campaign immediately, or any time in the next 2 weeks.' :
- 'If your push hasn\'t been send by this time, it won\'t get sent.';
+ const timeFieldsDescription = hasScheduledPushSupport
+ ? 'We can send the campaign immediately, or any time in the next 2 weeks.'
+ : 'If your push hasn\'t been send by this time, it won\'t get sent.';
- const deliveryTimeFields = hasScheduledPushSupport ?
- {hasScheduledPushSupport ? this.renderDeliveryContent(fields, setField) : null}
- }
- input={ } />
- {PushHelper.renderExpirationContent(fields, setField)}
- : null;
-
- const messageFields =
-
- {this.renderMessageContent(fields, setField)}
+ const deliveryTimeFields = hasScheduledPushSupport ? (
+
+ {hasScheduledPushSupport ? this.renderDeliveryContent(fields, setField) : null}
+ }
+ input={
+
+ }
+ />
+ {PushHelper.renderExpirationContent(fields, setField)}
+
+ ) : null;
+
+ const messageFields = (
+
+
+ {this.renderMessageContent(fields, setField)}
+
+ }
+ input={
+ {
+ setField('increment_badge', value || null);
+ }}
+ />
+ }
+ />
+ {translationSegment}
+
+ );
+
+ const previewFields = (
+
+
+
+ );
+
+ return (
+
+ {recipientsFields}
+ {abTestingFields}
+ {deliveryTimeFields}
+ {messageFields}
+ {previewFields}
+
-
}
- input={
{
- setField('increment_badge', value || null);
- }} />} />
- {translationSegment}
-
-
- const previewFields =
-
-
-
- return
- {recipientsFields}
- {abTestingFields}
- {deliveryTimeFields}
- {messageFields}
- {previewFields}
-
-
+ );
}
valid(changes) {
- let emptyInputMessages = [];
- let invalidInputMessages = [];
+ const emptyInputMessages = [];
+ const invalidInputMessages = [];
if (!this.state.audienceId) {
emptyInputMessages.push('target audience');
@@ -745,7 +930,11 @@ class PushNew extends DashboardView {
}
// push expiration date
- if (changes.push_expires && changes.expiration_time_type === 'time' && !changes.expiration_time) {
+ if (
+ changes.push_expires &&
+ changes.expiration_time_type === 'time' &&
+ !changes.expiration_time
+ ) {
emptyInputMessages.push('expiration time');
}
@@ -756,13 +945,17 @@ class PushNew extends DashboardView {
emptyInputMessages.push('message');
} else if (changes.data_type === 'json') {
if (!isValidJSON(changes.data)) {
- invalidInputMessages.push(Your message is not valid JSON. );
+ invalidInputMessages.push(
+
+ Your message is not valid JSON.
+
+ );
}
}
// localized message is empty
if (changes.translation_enable) {
- this.state.localizedMessages.forEach((message) => {
+ this.state.localizedMessages.forEach(message => {
if (!message.value || message.value.trim() === '') {
emptyInputMessages.push(`message for ${message.locale} locale`);
}
@@ -784,14 +977,22 @@ class PushNew extends DashboardView {
emptyInputMessages.push('message for Group A');
} else if (changes.data_type_1 === 'json') {
if (!isValidJSON(changes.data)) {
- invalidInputMessages.push(Your message for Group A is not valid JSON. );
+ invalidInputMessages.push(
+
+ Your message for Group A is not valid JSON.
+
+ );
}
}
if (changes.data2.trim() === '') {
emptyInputMessages.push('message for Group B');
} else if (changes.data_type_2 === 'json') {
if (!isValidJSON(changes.data)) {
- invalidInputMessages.push(Your message for Group B is not valid JSON. );
+ invalidInputMessages.push(
+
+ Your message for Group B is not valid JSON.
+
+ );
}
}
// non-immediate delivery time
@@ -809,7 +1010,11 @@ class PushNew extends DashboardView {
emptyInputMessages.push('message');
} else if (changes.data_type === 'json') {
if (!isValidJSON(changes.data)) {
- invalidInputMessages.push(You message is not valid JSON. );
+ invalidInputMessages.push(
+
+ You message is not valid JSON.
+
+ );
}
}
// delivery time for group a
@@ -824,75 +1029,96 @@ class PushNew extends DashboardView {
}
if (emptyInputMessages.length > 0) {
- return {formatErrorMessage(emptyInputMessages, 'empty-inputs')} {invalidInputMessages}
+ return (
+
+ {formatErrorMessage(emptyInputMessages, 'empty-inputs')} {invalidInputMessages}
+
+ );
} else {
return invalidInputMessages;
}
}
renderContent() {
- return this.handlePushSubmit(changes)}
- inProgressText={'Sending\u2026'}
- renderForm={this.renderForm.bind(this)}
- validate={({ changes }) => this.valid(changes)}
- footerContents={({changes}) => {
- let deviceNote = null;
- if(this.state.deviceCount){
- let count = this.state.deviceCount;
-
- if (changes.exp_enable && changes.exp_type === 'message') {
- count = Math.min(count, this.state.recipientCount);
- }
+ return (
+ this.handlePushSubmit(changes)}
+ inProgressText={'Sending\u2026'}
+ renderForm={this.renderForm.bind(this)}
+ validate={({ changes }) => this.valid(changes)}
+ footerContents={({ changes }) => {
+ let deviceNote = null;
+ if (this.state.deviceCount) {
+ let count = this.state.deviceCount;
- deviceNote = (
-
- This notification will be sent to {`${count} ${count === 1 ? 'device' : 'devices'}`} .
-
- );
- }
- let timeNote = null;
- if(changes.push_time_type === 'time' && changes.push_time !== null || changes.push_time_type === 'now') {
- timeNote = (
-
- It will be sent {changes.push_time_type === 'now' ? 'immediately' : String(changes.push_time)} .
-
+ if (changes.exp_enable && changes.exp_type === 'message') {
+ count = Math.min(count, this.state.recipientCount);
+ }
+
+ deviceNote = (
+
+ This notification will be sent to{' '}
+ {`${count} ${count === 1 ? 'device' : 'devices'}`}
+ .
+
+ );
+ }
+ let timeNote = null;
+ if (
+ (changes.push_time_type === 'time' && changes.push_time !== null) ||
+ changes.push_time_type === 'now'
+ ) {
+ timeNote = (
+
+ It will be sent{' '}
+
+ {changes.push_time_type === 'now' ? 'immediately' : String(changes.push_time)}
+
+ .
+
+ );
+ }
+ return (
+
+ {deviceNote}
+ {timeNote}
+
);
- }
- return {deviceNote}{timeNote}
;
- }}/>;
+ }}
+ />
+ );
}
}
diff --git a/src/dashboard/Settings/AppleCerts.react.js b/src/dashboard/Settings/AppleCerts.react.js
index b083758de6..a05158d37c 100644
--- a/src/dashboard/Settings/AppleCerts.react.js
+++ b/src/dashboard/Settings/AppleCerts.react.js
@@ -5,9 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import Modal from 'components/Modal/Modal.react';
-import PushCerts from 'components/PushCerts/PushCerts.react';
-import React from 'react';
+import Modal from 'components/Modal/Modal.react';
+import PushCerts from 'components/PushCerts/PushCerts.react';
+import React from 'react';
import { CurrentApp } from 'context/currentApp';
export default class AppleCerts extends React.Component {
@@ -18,13 +18,13 @@ export default class AppleCerts extends React.Component {
this.state = {
certs: undefined,
deletePending: null,
- error: null
+ error: null,
};
}
componentDidMount() {
this.mounted = true;
- this.context.getAppleCerts().then((certs) => {
+ this.context.getAppleCerts().then(certs => {
if (this.mounted) {
this.setState({ certs });
}
@@ -36,12 +36,15 @@ export default class AppleCerts extends React.Component {
}
handleUpload(file) {
- this.context.uploadAppleCert(file).then((cert) => {
- this.state.certs.unshift(cert);
- this.setState({ uploadPending: false });
- }, (err) => {
- this.setState({ uploadPending: false, error: err.error });
- });
+ this.context.uploadAppleCert(file).then(
+ cert => {
+ this.state.certs.unshift(cert);
+ this.setState({ uploadPending: false });
+ },
+ err => {
+ this.setState({ uploadPending: false, error: err.error });
+ }
+ );
this.setState({ uploadPending: true, error: null });
}
@@ -57,28 +60,34 @@ export default class AppleCerts extends React.Component {
error={this.state.error}
uploadPending={this.state.uploadPending}
onUpload={this.handleUpload.bind(this)}
- onDelete={this.handleDelete.bind(this)} />
- {this.state.deletePending === null ? null :
+ onDelete={this.handleDelete.bind(this)}
+ />
+ {this.state.deletePending === null ? null : (
this.setState({ deletePending: null })}
onConfirm={() => {
- let id = this.state.deletePending;
- this.context.deleteAppleCert(id).then(() => {
- for (let i = 0; i < this.state.certs.length; i++) {
- if (this.state.certs[i].id === id) {
- this.state.certs.splice(i, 1);
- return this.setState({ deletePending: null });
+ const id = this.state.deletePending;
+ this.context.deleteAppleCert(id).then(
+ () => {
+ for (let i = 0; i < this.state.certs.length; i++) {
+ if (this.state.certs[i].id === id) {
+ this.state.certs.splice(i, 1);
+ return this.setState({ deletePending: null });
+ }
}
+ },
+ err => {
+ this.setState({ deletePending: null, error: err.error });
}
- }, (err) => {
- this.setState({ deletePending: null, error: err.error });
- })
- }}/>}
+ );
+ }}
+ />
+ )}
);
}
diff --git a/src/dashboard/Settings/Collaborators.react.js b/src/dashboard/Settings/Collaborators.react.js
index 486038c7ff..395ecd0fdf 100644
--- a/src/dashboard/Settings/Collaborators.react.js
+++ b/src/dashboard/Settings/Collaborators.react.js
@@ -5,18 +5,18 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AccountManager from 'lib/AccountManager';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FormTable from 'components/FormTable/FormTable.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import InlineSubmitInput from 'components/InlineSubmitInput/InlineSubmitInput.react';
-import Label from 'components/Label/Label.react';
-import PropTypes from 'lib/PropTypes';
-import React from 'react';
-import TextInput from 'components/TextInput/TextInput.react';
+import AccountManager from 'lib/AccountManager';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FormTable from 'components/FormTable/FormTable.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import InlineSubmitInput from 'components/InlineSubmitInput/InlineSubmitInput.react';
+import Label from 'components/Label/Label.react';
+import PropTypes from 'lib/PropTypes';
+import React from 'react';
+import TextInput from 'components/TextInput/TextInput.react';
import validateEmailFormat from 'lib/validateEmailFormat';
-import { CurrentApp } from 'context/currentApp';
+import { CurrentApp } from 'context/currentApp';
// Component for displaying and modifying an app's collaborator emails.
// There is a single input field for new collaborator emails. As soon as the
@@ -39,70 +39,94 @@ export default class Collaborators extends React.Component {
handleAdd(newEmail) {
//TODO: Show some in-progress thing while the collaborator is being validated, or maybe have some sort of
//async validator in the parent form. Currently if you mash the add button, they same collaborator gets added many times.
- return this.context.validateCollaborator(newEmail).then((response) => {
- // lastError logic assumes we only have 1 input field
- if (response.success) {
- let newCollaborators = this.props.collaborators.concat({ userEmail: newEmail })
- this.setState({ lastError: '' });
- this.props.onAdd(newEmail, newCollaborators);
- return true;
- } else if (response.error) {
- this.setState({ lastError: response.error });
- return false;
- }
- }).catch(({ error }) => {
- this.setState({ lastError: error });
- });
+ return this.context
+ .validateCollaborator(newEmail)
+ .then(response => {
+ // lastError logic assumes we only have 1 input field
+ if (response.success) {
+ const newCollaborators = this.props.collaborators.concat({
+ userEmail: newEmail,
+ });
+ this.setState({ lastError: '' });
+ this.props.onAdd(newEmail, newCollaborators);
+ return true;
+ } else if (response.error) {
+ this.setState({ lastError: response.error });
+ return false;
+ }
+ })
+ .catch(({ error }) => {
+ this.setState({ lastError: error });
+ });
}
handleDelete(collaborator) {
- let newCollaborators = this.props.collaborators.filter(oldCollaborator => oldCollaborator.userEmail !== collaborator.userEmail);
+ const newCollaborators = this.props.collaborators.filter(
+ oldCollaborator => oldCollaborator.userEmail !== collaborator.userEmail
+ );
this.props.onRemove(collaborator, newCollaborators);
}
validateEmail(email) {
// We allow mixed-case emails for Parse accounts
- let isExistingCollaborator = !!this.props.collaborators.find(collab => email.toLowerCase() === collab.userEmail.toLowerCase());
- return validateEmailFormat(email) &&
+ const isExistingCollaborator = !!this.props.collaborators.find(
+ collab => email.toLowerCase() === collab.userEmail.toLowerCase()
+ );
+ return (
+ validateEmailFormat(email) &&
!isExistingCollaborator &&
- AccountManager.currentUser().email.toLowerCase() !== email.toLowerCase();
+ AccountManager.currentUser().email.toLowerCase() !== email.toLowerCase()
+ );
}
render() {
return (
- {this.props.viewer_email === this.props.owner_email ? Collaborators will have read/write access but cannot delete the app or add more collaborators.}/>}
- input={ this.validateEmail(email)}
- placeholder='What's their email?'
- onSubmit={this.handleAdd.bind(this)}
- submitButtonText='ADD' />} /> : }
- input={ {}}
- disabled={true}
- />}
- />}
- {this.state.lastError !== '' ?
- (
+ {this.props.viewer_email === this.props.owner_email ? (
+
+ Collaborators will have read/write access but cannot delete the app or
+ add more collaborators.
+
+ }
+ />
+ }
+ input={
+ this.validateEmail(email)}
+ placeholder="What's their email?"
+ onSubmit={this.handleAdd.bind(this)}
+ submitButtonText="ADD"
+ />
+ }
+ />
+ ) : (
+ }
+ input={ {}} disabled={true} />}
+ />
+ )}
+ {this.state.lastError !== '' ? (
+
{this.state.lastError}
- ) : null}
- {this.props.collaborators.length > 0 ?
+
+ ) : null}
+ {this.props.collaborators.length > 0 ? (
}
+ label={ }
labelWidth={62}
- input={ {
- let canDelete = this.props.viewer_email === this.props.owner_email || collaborator.userEmail === this.props.viewer_email;
+ input={
+ {
+ const canDelete =
+ this.props.viewer_email === this.props.owner_email ||
+ collaborator.userEmail === this.props.viewer_email;
//TODO(drewgross): add a warning modal for when you are removing yourself as a collaborator, as that is irreversable
return {
title: collaborator.userName || collaborator.userEmail,
@@ -112,28 +136,33 @@ export default class Collaborators extends React.Component {
{
key: 'Email',
value: collaborator.userEmail,
- }
+ },
],
};
- })
- }/>} />
-
- : null}
+ })}
+ />
+ }
+ />
+ ) : null}
- )
+ );
}
}
Collaborators.propTypes = {
- legend: PropTypes.string.isRequired.describe(
- 'Title of this section'
- ),
+ legend: PropTypes.string.isRequired.describe('Title of this section'),
description: PropTypes.string.isRequired.describe(
'Description fo this section (shows below title)'
),
- collaborators: PropTypes.arrayOf(PropTypes.any).isRequired.describe('An array of current collaborators of this app'),
- owner_email: PropTypes.string.describe('The email of the owner, to be displayed if the viewer is a collaborator.'),
- viewer_email: PropTypes.string.describe('The email of the viewer, if the viewer is a collaborator, they will not be able to remove collaborators except themselves.'),
+ collaborators: PropTypes.arrayOf(PropTypes.any).isRequired.describe(
+ 'An array of current collaborators of this app'
+ ),
+ owner_email: PropTypes.string.describe(
+ 'The email of the owner, to be displayed if the viewer is a collaborator.'
+ ),
+ viewer_email: PropTypes.string.describe(
+ 'The email of the viewer, if the viewer is a collaborator, they will not be able to remove collaborators except themselves.'
+ ),
onAdd: PropTypes.func.isRequired.describe(
'A function that will be called whenever a user adds a valid collaborator email. It receives the new email and an updated array of all collaborators for this app.'
),
diff --git a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
index c528c19fd0..c199d99117 100644
--- a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
+++ b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
@@ -42,7 +42,7 @@ export default class DashboardSettings extends DashboardView {
copyData: {
data: '',
show: false,
- type: ''
+ type: '',
},
newUser: {
data: '',
@@ -55,14 +55,22 @@ export default class DashboardSettings extends DashboardView {
getColumns() {
const data = ColumnPreferences.getAllPreferences(this.context.applicationId);
this.setState({
- copyData: { data: JSON.stringify(data, null, 2), show: true, type: 'Column Preferences' },
+ copyData: {
+ data: JSON.stringify(data, null, 2),
+ show: true,
+ type: 'Column Preferences',
+ },
});
}
getClasses() {
const data = ClassPreferences.getAllPreferences(this.context.applicationId);
this.setState({
- copyData: { data: JSON.stringify(data, null, 2), show: true, type: 'Class Preferences' },
+ copyData: {
+ data: JSON.stringify(data, null, 2),
+ show: true,
+ type: 'Class Preferences',
+ },
});
}
@@ -131,8 +139,8 @@ export default class DashboardSettings extends DashboardView {
}
generatePassword() {
- let chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- let pwordLength = 20;
+ const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ const pwordLength = 20;
let password = '';
const array = new Uint32Array(chars.length);
@@ -161,31 +169,89 @@ export default class DashboardSettings extends DashboardView {
renderForm() {
const createUserInput = (
- } input={ this.setState({ username })} />} />
+ }
+ input={
+ this.setState({ username })}
+ />
+ }
+ />
Password
- this.setState({ passwordHidden: !this.state.passwordHidden })}>
-
+
+ this.setState({
+ passwordHidden: !this.state.passwordHidden,
+ })
+ }
+ >
+
}
description={
this.generatePassword()}>Generate strong password }
/>
}
- input={
this.setState({ password })} />}
+ input={
+ this.setState({ password })}
+ />
+ }
+ />
+ }
+ input={
+ this.setState({ encrypt })}
+ />
+ }
+ />
+ }
+ input={
+ this.setState({ mfa })}
+ />
+ }
/>
- } input={ this.setState({ encrypt })} />} />
- } input={ this.setState({ mfa })} />} />
{this.state.mfa && (
}
input={
- this.setState({ mfaAlgorithm })}>
- {['SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'SHA3-224', 'SHA3-256', 'SHA3-384', 'SHA3-512'].map((column) => (
+ this.setState({ mfaAlgorithm })}
+ >
+ {[
+ 'SHA1',
+ 'SHA224',
+ 'SHA256',
+ 'SHA384',
+ 'SHA512',
+ 'SHA3-224',
+ 'SHA3-256',
+ 'SHA3-384',
+ 'SHA3-512',
+ ].map(column => (
{column}
@@ -194,9 +260,37 @@ export default class DashboardSettings extends DashboardView {
}
/>
)}
- {this.state.mfa && } input={ this.setState({ mfaDigits })} />} />}
- {this.state.mfa && } input={ this.setState({ mfaPeriod })} />} />}
- this.createUser()} />} />
+ {this.state.mfa && (
+
+ }
+ input={
+ this.setState({ mfaDigits })}
+ />
+ }
+ />
+ )}
+ {this.state.mfa && (
+ }
+ input={
+ this.setState({ mfaPeriod })}
+ />
+ }
+ />
+ )}
+ this.createUser()} />
+ }
+ />
);
const copyData = (
@@ -205,37 +299,88 @@ export default class DashboardSettings extends DashboardView {
- this.copy(this.state.copyData.data, this.state.copyData.type)} />
- this.setState({ copyData: { data: '', show: false } })} />
+ this.copy(this.state.copyData.data, this.state.copyData.type)}
+ />
+ this.setState({ copyData: { data: '', show: false } })}
+ />
);
const userData = (
Add the following data to your Parse Dashboard configuration "users":
- {this.state.encrypt &&
Make sure the dashboard option useEncryptedPasswords is set to true.
}
+ {this.state.encrypt && (
+
Make sure the dashboard option useEncryptedPasswords is set to true.
+ )}
{this.state.mfa && (
)}
- this.copy(this.state.newUser.data, 'New User')} />
- this.setState({ username: '', password: '', passwordHidden: true, mfaAlgorithm: 'SHA1', mfaDigits: 6, mfaPeriod: 30, encrypt: true, createUserInput: false, newUser: { data: '', show: false } })} />
+ this.copy(this.state.newUser.data, 'New User')}
+ />
+
+ this.setState({
+ username: '',
+ password: '',
+ passwordHidden: true,
+ mfaAlgorithm: 'SHA1',
+ mfaDigits: 6,
+ mfaPeriod: 30,
+ encrypt: true,
+ createUserInput: false,
+ newUser: { data: '', show: false },
+ })
+ }
+ />
);
return (
- } input={ this.getColumns()} />} />
- } input={ this.getClasses()} />} />
- } input={ this.setState({ createUserInput: true })} />} />
+ }
+ input={ this.getColumns()} />}
+ />
+ }
+ input={ this.getClasses()} />}
+ />
+ }
+ input={
+ this.setState({ createUserInput: true })}
+ />
+ }
+ />
{this.state.copyData.show && copyData}
{this.state.createUserInput && createUserInput}
@@ -247,6 +392,14 @@ export default class DashboardSettings extends DashboardView {
}
renderContent() {
- return
{}} onSubmit={() => {}} renderForm={() => this.renderForm()} />;
+ return (
+ {}}
+ onSubmit={() => {}}
+ renderForm={() => this.renderForm()}
+ />
+ );
}
}
diff --git a/src/dashboard/Settings/GeneralSettings.react.js b/src/dashboard/Settings/GeneralSettings.react.js
index 9a9981ab54..87e64def34 100644
--- a/src/dashboard/Settings/GeneralSettings.react.js
+++ b/src/dashboard/Settings/GeneralSettings.react.js
@@ -5,92 +5,107 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AccountManager from 'lib/AccountManager';
-import AppsManager from 'lib/AppsManager';
-import Collaborators from 'dashboard/Settings/Collaborators.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import Dropdown from 'components/Dropdown/Dropdown.react';
-import DropdownOption from 'components/Dropdown/Option.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormButton from 'components/FormButton/FormButton.react';
-import FormModal from 'components/FormModal/FormModal.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import getSiteDomain from 'lib/getSiteDomain';
-import joinWithFinal from 'lib/joinWithFinal';
-import KeyField from 'components/KeyField/KeyField.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import MultiSelect from 'components/MultiSelect/MultiSelect.react';
-import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
-import pluck from 'lib/pluck';
-import Range from 'components/Range/Range.react';
-import React from 'react';
-import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
-import setDifference from 'lib/setDifference';
-import settingsStyles from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import unique from 'lib/unique';
+import AccountManager from 'lib/AccountManager';
+import AppsManager from 'lib/AppsManager';
+import Collaborators from 'dashboard/Settings/Collaborators.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import DropdownOption from 'components/Dropdown/Option.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormButton from 'components/FormButton/FormButton.react';
+import FormModal from 'components/FormModal/FormModal.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import getSiteDomain from 'lib/getSiteDomain';
+import joinWithFinal from 'lib/joinWithFinal';
+import KeyField from 'components/KeyField/KeyField.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import MultiSelect from 'components/MultiSelect/MultiSelect.react';
+import MultiSelectOption from 'components/MultiSelect/MultiSelectOption.react';
+import pluck from 'lib/pluck';
+import Range from 'components/Range/Range.react';
+import React from 'react';
+import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
+import setDifference from 'lib/setDifference';
+import settingsStyles from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import unique from 'lib/unique';
import validateAndSubmitConnectionString from 'lib/validateAndSubmitConnectionString';
-import styles from 'dashboard/Settings/GeneralSettings.scss';
-import { Link, useNavigate } from 'react-router-dom';
+import styles from 'dashboard/Settings/GeneralSettings.scss';
+import { Link, useNavigate } from 'react-router-dom';
import { withRouter } from 'lib/withRouter';
const DEFAULT_SETTINGS_LABEL_WIDTH = 62;
-let numJobsFromRequestLimit = (limit) => Math.floor((limit-10)/20);
+const numJobsFromRequestLimit = limit => Math.floor((limit - 10) / 20);
-let CurrentPlan = ({requestLimit}) => {
- let costString = requestLimit === 30 ?
- 'Free' :
- '$' + ((requestLimit-30) * 10).toString();
+const CurrentPlan = ({ requestLimit }) => {
+ const costString = requestLimit === 30 ? 'Free' : '$' + ((requestLimit - 30) * 10).toString();
return (
{costString}
-
{requestLimit.toString() + ' requests per second'} {numJobsFromRequestLimit(requestLimit).toString() + ' background job' + (numJobsFromRequestLimit(requestLimit) > 1 ? 's' : '')}
+
+ {requestLimit.toString() + ' requests per second'}
+
+ {numJobsFromRequestLimit(requestLimit).toString() +
+ ' background job' +
+ (numJobsFromRequestLimit(requestLimit) > 1 ? 's' : '')}
+
-)};
-
-let CurrentPlanFields = ({
- visible,
- requestLimit,
- setRequestLimit,
-}) => visible ?
- }
- input={ {
- let numJobs = numJobsFromRequestLimit(value);
- return value + 'req/s & ' + numJobs + ' job' + (numJobs == 1 ? '' : 's')
- }}
- width={220}
- onChange={limit => {
- if (limit < 30) {
- limit = 30;
+ );
+};
+
+const CurrentPlanFields = ({ visible, requestLimit, setRequestLimit }) =>
+ visible ? (
+
+
}
- setRequestLimit(limit);
- }} />} />
- }
- input={ } />
- : ;
-
-let AppInformationFields = ({
+ input={
+ {
+ const numJobs = numJobsFromRequestLimit(value);
+ return value + 'req/s & ' + numJobs + ' job' + (numJobs == 1 ? '' : 's');
+ }}
+ width={220}
+ onChange={limit => {
+ if (limit < 30) {
+ limit = 30;
+ }
+ setRequestLimit(limit);
+ }}
+ />
+ }
+ />
+ }
+ input={ }
+ />
+
+ ) : (
+
+ );
+
+const AppInformationFields = ({
appName,
setAppName,
inProduction,
@@ -105,86 +120,86 @@ let AppInformationFields = ({
setWebAppURL,
otherURL,
setOtherURL,
-}) =>
- }
- input={
- } />
- }
- input={
- } />
- { inProduction ?
- }
- input={
- } />
+}) => (
+
}
- input={
- } />
+ label={ }
+ input={ }
+ />
}
- input={
- } />
- }
- input={
- } />
- }
- input={
- } />
- : null }
- ;
-
-let CollaboratorsFields = ({
+ labelWidth={58}
+ label={
+
+ }
+ input={ }
+ />
+ {inProduction ? (
+
+ }
+ input={ }
+ />
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+ }
+ input={ }
+ />
+ }
+ input={ }
+ />
+
+ ) : null}
+
+);
+
+const CollaboratorsFields = ({
collaborators,
ownerEmail,
viewerEmail,
addCollaborator,
removeCollaborator,
-}) => ;
-
-let ManageAppFields = ({
+}) => (
+
+);
+
+const ManageAppFields = ({
isCollaborator,
hasCollaborators,
mongoURL,
@@ -207,135 +222,177 @@ let ManageAppFields = ({
const navigate = useNavigate();
let migrateAppField = null;
if (!mongoURL && !hasInProgressMigration) {
- migrateAppField =
- }
- input={
- } />;
+ migrateAppField = (
+
+ }
+ input={ }
+ />
+ );
} else if (hasInProgressMigration) {
- migrateAppField = }
- input={ navigate(`/apps/${appSlug}/migration`)}
- value='View progress' />} />
+ migrateAppField = (
+
+ }
+ input={
+ navigate(`/apps/${appSlug}/migration`)}
+ value="View progress"
+ />
+ }
+ />
+ );
} else {
- migrateAppField = [ }
- //TODO: KeyField bascially does what we want, but is maybe too specialized. Maybe at some point we should have a component dedicated to semi-secret stuff that we want to prevent shoulder surfers from seeing, and emphasizing that stuff something should be secret.
- input={
- {}} //Make propTypes happy
- disabled={true}
- monospace={true}
- />
- }
- />,
- }
- input={ } />
+ migrateAppField = [
+
+ }
+ //TODO: KeyField bascially does what we want, but is maybe too specialized. Maybe at some point we should have a component dedicated to semi-secret stuff that we want to prevent shoulder surfers from seeing, and emphasizing that stuff something should be secret.
+ input={
+
+ {}} //Make propTypes happy
+ disabled={true}
+ monospace={true}
+ />
+
+ }
+ />,
+
+ }
+ input={
+
+ }
+ />,
];
}
return (
-
- This will delete any files that are not referenced by any objects.} />}
- input={ } />
- {cleanUpFilesMessage ?
- {cleanUpFilesMessage}
- : null}
- }
- //TODO: Add export progress view when designs are ready.
- input={ } />
- {exportDataMessage ?
- {exportDataMessage}
- : null}
- {migrateAppField}
- Choose what you want to carry over and create a copy of this Parse app.} />}
- input={
- } />
- {cloneAppMessage ?
- {cloneAppMessage} Check out the progress on your apps page!
- : null}
- {!isCollaborator ? Give an existing collaborator ownership over this app.} />
- }
- input={
- } /> : null}
- {transferAppMessage ?
- {transferAppMessage}
- : null}
- {!isCollaborator ? Completely remove any trace of this app's existence.} />}
- input={
- } /> : null}
- );
-}
+
+
+ This will delete any files that
+ are not referenced by any objects.
+
+ }
+ />
+ }
+ input={ }
+ />
+ {cleanUpFilesMessage ? (
+
+ {cleanUpFilesMessage}
+
+ ) : null}
+
+ }
+ //TODO: Add export progress view when designs are ready.
+ input={ }
+ />
+ {exportDataMessage ? (
+
+ {exportDataMessage}
+
+ ) : null}
+ {migrateAppField}
+
+ Choose what you want to carry over
+ and create a copy of this Parse app.
+
+ }
+ />
+ }
+ input={ }
+ />
+ {cloneAppMessage ? (
+
+
+ {cloneAppMessage} Check out the progress on your apps page!
+
+
+ ) : null}
+ {!isCollaborator ? (
+
+ Give an existing collaborator
+ ownership over this app.
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+ ) : null}
+ {transferAppMessage ? {transferAppMessage} : null}
+ {!isCollaborator ? (
+
+ Completely remove any trace
+ of this app's existence.
+
+ }
+ />
+ }
+ input={ }
+ />
+ ) : null}
+
+ );
+};
@withRouter
class GeneralSettings extends DashboardView {
@@ -361,7 +418,7 @@ class GeneralSettings extends DashboardView {
showCloneAppModal: false,
cloneAppMessage: '',
- cloneAppName:'',
+ cloneAppName: '',
cloneOptionsSelection: ['schema', 'app_settings', 'config', 'cloud_code'],
showMigrateAppModal: false,
@@ -379,221 +436,319 @@ class GeneralSettings extends DashboardView {
renderContent() {
if (!this.props.initialFields) {
- return
+ return ;
}
- let passwordField = (
+ const passwordField = (
+ label={
+
}
- input={ {
- this.setState({password: newValue});
- }} />} />
- )
-
- let closeModalWithConnectionString = () => this.setState({
- showChangeConnectionStringModal: false,
- showMigrateAppModal: false,
- showMongoConnectionValidationErrors: false,
- migrationWarnings: [],
- });
-
- let migrateAppModal = 0 ? 'Migrate anyway' : 'Migrate'}
- inProgressText={'Migrating\u2026'}
- showErrors={this.state.showMongoConnectionValidationErrors}
- width={900}
- onSubmit={() => {
- let promise = validateAndSubmitConnectionString(
- this.state.migrationMongoURL,
- this.state.migrationWarnings,
- warnings => this.setState({migrationWarnings: warnings}),
- connectionString => this.context.beginMigration(connectionString)
- );
- promise.catch(({ error }) => this.setState({showMongoConnectionValidationErrors: error !== 'Warnings'}));
- return promise;
- }}
- onClose={closeModalWithConnectionString}
- onSuccess={() => this.props.navigate(`/apps/${this.context.slug}/migration`)}
- clearFields={() => this.setState({
- migrationMongoURL: '',
+ input={
+ {
+ this.setState({ password: newValue });
+ }}
+ />
+ }
+ />
+ );
+
+ const closeModalWithConnectionString = () =>
+ this.setState({
+ showChangeConnectionStringModal: false,
+ showMigrateAppModal: false,
+ showMongoConnectionValidationErrors: false,
migrationWarnings: [],
- })}>
- This database must be prepared to handle all of your app's queries and data. Read our migration guide to learn how to create a database.} />
+ });
+
+ const migrateAppModal = (
+ 0
+ ? 'Migrate anyway'
+ : 'Migrate'
}
- input={ this.setState({
- migrationMongoURL: value,
+ inProgressText={'Migrating\u2026'}
+ showErrors={this.state.showMongoConnectionValidationErrors}
+ width={900}
+ onSubmit={() => {
+ const promise = validateAndSubmitConnectionString(
+ this.state.migrationMongoURL,
+ this.state.migrationWarnings,
+ warnings => this.setState({ migrationWarnings: warnings }),
+ connectionString => this.context.beginMigration(connectionString)
+ );
+ promise.catch(({ error }) =>
+ this.setState({
+ showMongoConnectionValidationErrors: error !== 'Warnings',
+ })
+ );
+ return promise;
+ }}
+ onClose={closeModalWithConnectionString}
+ onSuccess={() => this.props.navigate(`/apps/${this.context.slug}/migration`)}
+ clearFields={() =>
+ this.setState({
+ migrationMongoURL: '',
migrationWarnings: [],
- showMongoConnectionValidationErrors: false,
- })} />} />
- {this.state.migrationWarnings.map(warning => {warning} )}
- ;
-
- let changeConnectionStringModal = {
- let promise = validateAndSubmitConnectionString(
- this.state.newConnectionString,
- this.state.migrationWarnings,
- warnings => this.setState({migrationWarnings: warnings}),
- connectionString => this.context.changeConnectionString(connectionString)
- );
- promise.catch(({ error }) => this.setState({showMongoConnectionValidationErrors: error !== 'Warnings'}));
- return promise;
- }}
- onClose={closeModalWithConnectionString}
- type={Modal.Types.DANGER}
- submitText={this.state.migrationWarnings && this.state.migrationWarnings.length > 0 ? 'Change anyway' : 'Change'}
- inProgressText={'Changing\u2026'}
- showErrors={this.state.showMongoConnectionValidationErrors}
- width={900}
- clearFields={() => this.setState({
- migrationMongoURL: '',
- migrationWarnings: [],
- })}>
- }
- input={ this.setState({
- newConnectionString: value,
+ })
+ }
+ >
+
+ This database must be prepared to handle all of your app's queries and data. Read{' '}
+ our migration guide {' '}
+ to learn how to create a database.
+
+ }
+ />
+ }
+ input={
+
+ this.setState({
+ migrationMongoURL: value,
+ migrationWarnings: [],
+ showMongoConnectionValidationErrors: false,
+ })
+ }
+ />
+ }
+ />
+ {this.state.migrationWarnings.map(warning => (
+
+ {warning}
+
+ ))}
+
+ );
+
+ const changeConnectionStringModal = (
+ {
+ const promise = validateAndSubmitConnectionString(
+ this.state.newConnectionString,
+ this.state.migrationWarnings,
+ warnings => this.setState({ migrationWarnings: warnings }),
+ connectionString => this.context.changeConnectionString(connectionString)
+ );
+ promise.catch(({ error }) =>
+ this.setState({
+ showMongoConnectionValidationErrors: error !== 'Warnings',
+ })
+ );
+ return promise;
+ }}
+ onClose={closeModalWithConnectionString}
+ type={Modal.Types.DANGER}
+ submitText={
+ this.state.migrationWarnings && this.state.migrationWarnings.length > 0
+ ? 'Change anyway'
+ : 'Change'
+ }
+ inProgressText={'Changing\u2026'}
+ showErrors={this.state.showMongoConnectionValidationErrors}
+ width={900}
+ clearFields={() =>
+ this.setState({
+ migrationMongoURL: '',
migrationWarnings: [],
- showMongoConnectionValidationErrors: false,
- })} />} />
- {this.state.migrationWarnings.map(warning => {warning} )}
-
-
- let transferAppModal = 0 || !AccountManager.currentUser().has_password)
- && this.state.transferNewOwner.length > 0
- }
- onSubmit={() => AppsManager.transferApp(this.context.slug, this.state.transferNewOwner, this.state.password)}
- onClose={() => this.setState({showTransferAppModal: false})}
- onSuccess={({ message }) => this.setState({transferAppSuccessMessage: message})}
- clearFields={() => this.setState({
- password: '',
- transferNewOwner: '',
- })}>
-
+ })
}
- input={ this.setState({transferNewOwner: collaborator})}>
- {((this.props.initialFields||{}).collaborators||[]).map(collaborator =>
- {collaborator.userEmail}
- )}
- } />
- {AccountManager.currentUser().has_password ? passwordField : null}
- ;
-
- let deleteAppModal = 0}
- onSubmit={() => AppsManager.deleteApp(this.context.slug, this.state.password)}
- onSuccess={() => this.props.navigate('/apps')}
- onClose={() => this.setState({showDeleteAppModal: false})}
- clearFields={() => this.setState({password: ''})}>
- {passwordField}
-
-
- let cloneAppModal = 0}
- onSubmit={() => {
- this.setState({
- cloneAppMessage: '',
- });
- return AppsManager.cloneApp(this.context.slug, this.state.cloneAppName, this.state.cloneOptionsSelection)
- }}
- onSuccess={({notice}) => this.setState({cloneAppMessage: notice})}
- onClose={() => this.setState({showCloneAppModal: false})}
- clearFields={() => this.setState({
- cloneAppName: '',
- cloneOptionsSelection: ['schema', 'app_settings', 'config', 'cloud_code'],
- })}>
- }
- input={ this.setState({cloneAppName: value})
- } /> } />
- }
- input={ this.setState({cloneOptionsSelection: options})}
- >
- Schema
- App Settings
- Config
- Cloud Code
- Background Jobs
- } />
- ;
-
- let iosUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'ios');
- let anrdoidUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'android');
- let windowsUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'win');
- let webUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'web');
- let otherURL = this.props.initialFields.urls.find(({ platform }) => platform === 'other');
-
- let initialFields = {
+ >
+
+ }
+ input={
+
+ this.setState({
+ newConnectionString: value,
+ migrationWarnings: [],
+ showMongoConnectionValidationErrors: false,
+ })
+ }
+ />
+ }
+ />
+ {this.state.migrationWarnings.map(warning => (
+
+ {warning}
+
+ ))}
+
+ );
+
+ const transferAppModal = (
+ 0 || !AccountManager.currentUser().has_password) &&
+ this.state.transferNewOwner.length > 0
+ }
+ onSubmit={() =>
+ AppsManager.transferApp(
+ this.context.slug,
+ this.state.transferNewOwner,
+ this.state.password
+ )
+ }
+ onClose={() => this.setState({ showTransferAppModal: false })}
+ onSuccess={({ message }) => this.setState({ transferAppSuccessMessage: message })}
+ clearFields={() =>
+ this.setState({
+ password: '',
+ transferNewOwner: '',
+ })
+ }
+ >
+
+ }
+ input={
+ this.setState({ transferNewOwner: collaborator })}
+ >
+ {((this.props.initialFields || {}).collaborators || []).map(collaborator => (
+
+ {collaborator.userEmail}
+
+ ))}
+
+ }
+ />
+ {AccountManager.currentUser().has_password ? passwordField : null}
+
+ );
+
+ const deleteAppModal = (
+ 0}
+ onSubmit={() => AppsManager.deleteApp(this.context.slug, this.state.password)}
+ onSuccess={() => this.props.navigate('/apps')}
+ onClose={() => this.setState({ showDeleteAppModal: false })}
+ clearFields={() => this.setState({ password: '' })}
+ >
+ {passwordField}
+
+ );
+
+ const cloneAppModal = (
+ 0}
+ onSubmit={() => {
+ this.setState({
+ cloneAppMessage: '',
+ });
+ return AppsManager.cloneApp(
+ this.context.slug,
+ this.state.cloneAppName,
+ this.state.cloneOptionsSelection
+ );
+ }}
+ onSuccess={({ notice }) => this.setState({ cloneAppMessage: notice })}
+ onClose={() => this.setState({ showCloneAppModal: false })}
+ clearFields={() =>
+ this.setState({
+ cloneAppName: '',
+ cloneOptionsSelection: ['schema', 'app_settings', 'config', 'cloud_code'],
+ })
+ }
+ >
+ }
+ input={
+ this.setState({ cloneAppName: value })}
+ />
+ }
+ />
+ }
+ input={
+ this.setState({ cloneOptionsSelection: options })}
+ >
+ Schema
+ App Settings
+ Config
+ Cloud Code
+ Background Jobs
+
+ }
+ />
+
+ );
+
+ const iosUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'ios');
+ const anrdoidUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'android');
+ const windowsUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'win');
+ const webUrl = this.props.initialFields.urls.find(({ platform }) => platform === 'web');
+ const otherURL = this.props.initialFields.urls.find(({ platform }) => platform === 'other');
+
+ const initialFields = {
requestLimit: this.props.initialFields.pricing_plan.request_limit,
appName: this.context.name,
inProduction: this.context.production,
@@ -606,20 +761,45 @@ class GeneralSettings extends DashboardView {
mongoURL: this.context.settings.fields.fields.opendb_connection_string,
};
- let collaboratorRemovedWarningModal = this.state.removedCollaborators.length > 0 ? this.setState({removedCollaborators: []})}
- buttonsInCenter={true}
- textModal={true}>
- We have removed {joinWithFinal('', this.state.removedCollaborators.map(c => c.userName || c.userEmail), ', ', ' and ')} from this app. If they had saved the master key, they may still have access via an SDK or the API. To be sure, you can reset your master key in the Keys section of app settings.
- : null;
- let setCollaborators = (setField, unused, allCollabs) => {
- let addedCollaborators = setDifference(allCollabs, initialFields.collaborators, compareCollaborators);
- let removedCollaborators = setDifference(initialFields.collaborators, allCollabs, compareCollaborators);
+ const collaboratorRemovedWarningModal =
+ this.state.removedCollaborators.length > 0 ? (
+ this.setState({ removedCollaborators: [] })}
+ buttonsInCenter={true}
+ textModal={true}
+ >
+
+ We have removed{' '}
+
+ {joinWithFinal(
+ '',
+ this.state.removedCollaborators.map(c => c.userName || c.userEmail),
+ ', ',
+ ' and '
+ )}
+ {' '}
+ from this app. If they had saved the master key, they may still have access via an SDK
+ or the API. To be sure, you can reset your master key in the Keys section of app
+ settings.
+
+
+ ) : null;
+ const setCollaborators = (setField, unused, allCollabs) => {
+ const addedCollaborators = setDifference(
+ allCollabs,
+ initialFields.collaborators,
+ compareCollaborators
+ );
+ const removedCollaborators = setDifference(
+ initialFields.collaborators,
+ allCollabs,
+ compareCollaborators
+ );
if (addedCollaborators.length === 0 && removedCollaborators.length === 0) {
//This is neccessary because the footer computes whether or not show a change by reference equality.
allCollabs = initialFields.collaborators;
@@ -627,142 +807,182 @@ class GeneralSettings extends DashboardView {
setField('collaborators', allCollabs);
};
- return
-
renderFlowFooterChanges(changes, initialFields, generalFieldsOptions)}
- onSubmit={({ changes }) => {
- let promiseList = [];
- if (changes.requestLimit !== undefined) {
- promiseList.push(this.context.setRequestLimit(changes.requestLimit));
- }
- if (changes.appName !== undefined) {
- promiseList.push(this.context.setAppName(changes.appName));
- }
- if (changes.inProduction !== undefined) {
- promiseList.push(this.context.setInProduction(changes.inProduction));
- }
-
- let addedCollaborators = setDifference(changes.collaborators, initialFields.collaborators, compareCollaborators);
- addedCollaborators.forEach(({ userEmail }) => {
- promiseList.push(this.context.addCollaborator(userEmail));
- });
-
- let removedCollaborators = setDifference(initialFields.collaborators, changes.collaborators, compareCollaborators);
- removedCollaborators.forEach(({ id }) => {
- promiseList.push(this.context.removeCollaboratorById(id));
- });
-
- let urlKeys = {
- iTunesURL: 'ios',
- googlePlayURL: 'android',
- windowsAppStoreURL: 'win',
- webAppURL: 'web',
- otherURL: 'other',
+ return (
+
+
+ renderFlowFooterChanges(changes, initialFields, generalFieldsOptions)
}
-
- Object.keys(urlKeys).forEach(key => {
- if (changes[key] !== undefined) {
- promiseList.push(this.context.setAppStoreURL(urlKeys[key], changes[key]));
+ onSubmit={({ changes }) => {
+ const promiseList = [];
+ if (changes.requestLimit !== undefined) {
+ promiseList.push(this.context.setRequestLimit(changes.requestLimit));
+ }
+ if (changes.appName !== undefined) {
+ promiseList.push(this.context.setAppName(changes.appName));
+ }
+ if (changes.inProduction !== undefined) {
+ promiseList.push(this.context.setInProduction(changes.inProduction));
}
- });
- return Promise.all(promiseList).then(() => {
- this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
- this.setState({ removedCollaborators: removedCollaborators });
- }).catch(errors => {
- return Promise.reject({ error: unique(pluck(errors, 'error')).join(' ')});
- });
- }}
- renderForm={({ fields, setField }) => {
- let isCollaborator = AccountManager.currentUser().email !== this.props.initialFields.owner_email;
- return
-
-
-
-
this.setState({showChangeConnectionStringModal: true})}
- isCollaborator={isCollaborator}
- hasCollaborators={initialFields.collaborators.length > 0}
- startMigration={() => this.setState({showMigrateAppModal: true})}
- hasInProgressMigration={!!this.context.migration}
- appSlug={this.context.slug}
- cleanUpFiles={() => this.context.cleanUpFiles().then(result => {
- this.setState({
- cleanupFilesMessage: result.notice,
- cleanupNoteColor: 'orange',
- });
- }).catch((e) => {
- this.setState({
- cleanupFilesMessage: e.error,
- cleanupNoteColor: 'red',
- });
- })}
- cleanUpFilesMessage={this.state.cleanupFilesMessage}
- cleanUpMessageColor={this.state.cleanupNoteColor}
- exportData={() => this.context.exportData().then((result) => {
- this.setState({
- exportDataMessage: result.notice,
- exportDataColor: 'orange',
- });
- }).catch((e) => {
- this.setState({
- exportDataMessage: e.error,
- exportDataColor: 'red',
+ const addedCollaborators = setDifference(
+ changes.collaborators,
+ initialFields.collaborators,
+ compareCollaborators
+ );
+ addedCollaborators.forEach(({ userEmail }) => {
+ promiseList.push(this.context.addCollaborator(userEmail));
+ });
+
+ const removedCollaborators = setDifference(
+ initialFields.collaborators,
+ changes.collaborators,
+ compareCollaborators
+ );
+ removedCollaborators.forEach(({ id }) => {
+ promiseList.push(this.context.removeCollaboratorById(id));
+ });
+
+ const urlKeys = {
+ iTunesURL: 'ios',
+ googlePlayURL: 'android',
+ windowsAppStoreURL: 'win',
+ webAppURL: 'web',
+ otherURL: 'other',
+ };
+
+ Object.keys(urlKeys).forEach(key => {
+ if (changes[key] !== undefined) {
+ promiseList.push(this.context.setAppStoreURL(urlKeys[key], changes[key]));
+ }
+ });
+
+ return Promise.all(promiseList)
+ .then(() => {
+ this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
+ this.setState({ removedCollaborators: removedCollaborators });
+ })
+ .catch(errors => {
+ return Promise.reject({
+ error: unique(pluck(errors, 'error')).join(' '),
});
- })}
- exportDataMessage={this.state.exportDataMessage}
- exportMessageColor={this.state.exportDataColor}
- cloneApp={() => this.setState({
- showCloneAppModal: true,
- cloneAppMessage: '',
- })}
- cloneAppMessage={this.state.cloneAppMessage}
- transferApp={() => this.setState({
- showTransferAppModal: true,
- transferAppSuccessMessage: '',
- })}
- transferAppMessage={this.state.transferAppSuccessMessage}
- deleteApp={() => this.setState({showDeleteAppModal: true})}/>
- ;
- }} />
- {migrateAppModal}
- {transferAppModal}
- {deleteAppModal}
- {cloneAppModal}
- {collaboratorRemovedWarningModal}
- {changeConnectionStringModal}
-
- ;
+ });
+ }}
+ renderForm={({ fields, setField }) => {
+ const isCollaborator =
+ AccountManager.currentUser().email !== this.props.initialFields.owner_email;
+ return (
+
+
+
+
+
+ this.setState({ showChangeConnectionStringModal: true })
+ }
+ isCollaborator={isCollaborator}
+ hasCollaborators={initialFields.collaborators.length > 0}
+ startMigration={() => this.setState({ showMigrateAppModal: true })}
+ hasInProgressMigration={!!this.context.migration}
+ appSlug={this.context.slug}
+ cleanUpFiles={() =>
+ this.context
+ .cleanUpFiles()
+ .then(result => {
+ this.setState({
+ cleanupFilesMessage: result.notice,
+ cleanupNoteColor: 'orange',
+ });
+ })
+ .catch(e => {
+ this.setState({
+ cleanupFilesMessage: e.error,
+ cleanupNoteColor: 'red',
+ });
+ })
+ }
+ cleanUpFilesMessage={this.state.cleanupFilesMessage}
+ cleanUpMessageColor={this.state.cleanupNoteColor}
+ exportData={() =>
+ this.context
+ .exportData()
+ .then(result => {
+ this.setState({
+ exportDataMessage: result.notice,
+ exportDataColor: 'orange',
+ });
+ })
+ .catch(e => {
+ this.setState({
+ exportDataMessage: e.error,
+ exportDataColor: 'red',
+ });
+ })
+ }
+ exportDataMessage={this.state.exportDataMessage}
+ exportMessageColor={this.state.exportDataColor}
+ cloneApp={() =>
+ this.setState({
+ showCloneAppModal: true,
+ cloneAppMessage: '',
+ })
+ }
+ cloneAppMessage={this.state.cloneAppMessage}
+ transferApp={() =>
+ this.setState({
+ showTransferAppModal: true,
+ transferAppSuccessMessage: '',
+ })
+ }
+ transferAppMessage={this.state.transferAppSuccessMessage}
+ deleteApp={() => this.setState({ showDeleteAppModal: true })}
+ />
+
+ );
+ }}
+ />
+ {migrateAppModal}
+ {transferAppModal}
+ {deleteAppModal}
+ {cloneAppModal}
+ {collaboratorRemovedWarningModal}
+ {changeConnectionStringModal}
+
+
+ );
}
}
-let compareCollaborators = (collab1, collab2) => collab1.userEmail === collab2.userEmail;
+const compareCollaborators = (collab1, collab2) => collab1.userEmail === collab2.userEmail;
-let generalFieldsOptions = {
+const generalFieldsOptions = {
requestLimit: {
friendlyName: 'request limit',
showTo: true,
diff --git a/src/dashboard/Settings/HostingSettings.react.js b/src/dashboard/Settings/HostingSettings.react.js
index bc4c7087b2..fb5db840fb 100644
--- a/src/dashboard/Settings/HostingSettings.react.js
+++ b/src/dashboard/Settings/HostingSettings.react.js
@@ -5,367 +5,525 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DashboardView from 'dashboard/DashboardView.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FileInput from 'components/FileInput/FileInput.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormNote from 'components/FormNote/FormNote.react';
-import getSiteDomain from 'lib/getSiteDomain';
-import Label from 'components/Label/Label.react';
-import React from 'react';
+import DashboardView from 'dashboard/DashboardView.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FileInput from 'components/FileInput/FileInput.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormNote from 'components/FormNote/FormNote.react';
+import getSiteDomain from 'lib/getSiteDomain';
+import Label from 'components/Label/Label.react';
+import React from 'react';
import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
-import styles from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import unique from 'lib/unique';
+import styles from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import unique from 'lib/unique';
export default class HostingSettings extends DashboardView {
- constructor() {
- super();
- this.section = 'App Settings';
- this.subsection = 'Hosting and Emails';
+ constructor() {
+ super();
+ this.section = 'App Settings';
+ this.subsection = 'Hosting and Emails';
- this.state = {
- sslPublicCertUploading: false,
- sslPublicCertError: '',
+ this.state = {
+ sslPublicCertUploading: false,
+ sslPublicCertError: '',
- sslPrivateKeyUploading: false,
- sslPrivateKeyError:'',
- };
- }
+ sslPrivateKeyUploading: false,
+ sslPrivateKeyError: '',
+ };
+ }
- renderForm({fields, setField}) {
- let hostingSubdomainFields =
- }
- input={
- }
- />
- ;
+ renderForm({ fields, setField }) {
+ const hostingSubdomainFields = (
+
+
+ }
+ input={
+
+ }
+ />
+
+ );
- let customDomainsFields =
- }
- input={ }
- />
- }
- input={ {}}/>
- } />
-
- }
- input={ {
- this.setState({
- sslPublicCertUploading: true,
- sslPublicCertError: '',
- });
- //TODO: do something here to indicate success and/or upload when you click the FlowView save button rather than immediately
- this.context.uploadSSLPublicCertificate(file).catch(({ error }) => {
- this.setState({ sslPublicCertError: error });
- }).finally(() => {
- this.setState({ sslPublicCertUploading: false });
- });
- }} />
- } />
- 0}
- color='red'>
- {this.state.sslPublicCertError}
-
-
- }
- input={ {
- this.setState({
- sslPrivateKeyUploading: true,
- sslPrivateKeyError: '',
- });
- //TODO: do something here to indicate success and/or upload when you click the FlowView save button rather than immediately
- this.context.uploadSSLPrivateKey(file).catch(({ error }) => {
- this.setState({ sslPrivateKeyError: error });
- }).finally(() => {
- this.setState({ sslPrivateKeyUploading: false });
- });
- }} />
- } />
- {this.state.sslPrivateKeyError.length > 0 ? 0}
- color='red'>
- {this.state.sslPrivateKeyError}
- : null}
- ;
+ const customDomainsFields = (
+
+
+ }
+ input={
+
+ }
+ />
+
+ }
+ input={
+ {}}
+ />
+ }
+ />
+
+ }
+ input={
+ {
+ this.setState({
+ sslPublicCertUploading: true,
+ sslPublicCertError: '',
+ });
+ //TODO: do something here to indicate success and/or upload when you click the FlowView save button rather than immediately
+ this.context
+ .uploadSSLPublicCertificate(file)
+ .catch(({ error }) => {
+ this.setState({ sslPublicCertError: error });
+ })
+ .finally(() => {
+ this.setState({ sslPublicCertUploading: false });
+ });
+ }}
+ />
+ }
+ />
+ 0} color="red">
+ {this.state.sslPublicCertError}
+
+
+ }
+ input={
+ {
+ this.setState({
+ sslPrivateKeyUploading: true,
+ sslPrivateKeyError: '',
+ });
+ //TODO: do something here to indicate success and/or upload when you click the FlowView save button rather than immediately
+ this.context
+ .uploadSSLPrivateKey(file)
+ .catch(({ error }) => {
+ this.setState({ sslPrivateKeyError: error });
+ })
+ .finally(() => {
+ this.setState({ sslPrivateKeyUploading: false });
+ });
+ }}
+ />
+ }
+ />
+ {this.state.sslPrivateKeyError.length > 0 ? (
+ 0} color="red">
+ {this.state.sslPrivateKeyError}
+
+ ) : null}
+
+ );
- let emailSettingsFields =
-
- }
- input={
- }
- />
-
- }
- input={
- }
- />
-
- }
- input={
- }
- />
- ;
+ const emailSettingsFields = (
+
+
+ }
+ input={
+
+ }
+ />
+
+ }
+ input={
+
+ }
+ />
+
+ }
+ input={
+
+ }
+ />
+
+ );
- let emailTemplatesFields =
- }
- input={
- } />
- }
- input={
- } />
- }
- input={
- } />
- }
- input={
- } />
- ;
+ const emailTemplatesFields = (
+
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ );
- let userFacingPagesFields =
- This page will be loaded when users click on a reset password link. Download the template .} />
- }
- input={
- } />
- This page will be loaded when users successfully change their password. Download the template .} />
- }
- input={
- } />
- This page will be loaded when users verify their email address. Download the template .} />
- }
- input={
- } />
- This page will be loaded whenever users mistype the reset password or verify email links. Download the template .} />
- }
- input={
- } />
-
+ const userFacingPagesFields = (
+
+
+ This page will be loaded when users click on a reset password link.{' '}
+
+ Download the template
+
+ .
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ This page will be loaded when users successfully change their password.{' '}
+
+ Download the template
+
+ .
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ This page will be loaded when users verify their email address.{' '}
+
+ Download the template
+
+ .
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ This page will be loaded whenever users mistype the reset password or verify email
+ links.{' '}
+
+ Download the template
+
+ .
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ );
- let whiteLabelURLsFields =
- Upload this file to your server and tell us where you put it.} />
- }
- input={
- } />
- ;
- return
- {hostingSubdomainFields}
- {customDomainsFields}
- {emailSettingsFields}
- {emailTemplatesFields}
- {userFacingPagesFields}
- {whiteLabelURLsFields}
-
-
;
- }
+ const whiteLabelURLsFields = (
+
+
+ Upload{' '}
+
+ this file
+ {' '}
+ to your server and tell us where you put it.
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ );
+ return (
+
+ {hostingSubdomainFields}
+ {customDomainsFields}
+ {emailSettingsFields}
+ {emailTemplatesFields}
+ {userFacingPagesFields}
+ {whiteLabelURLsFields}
+
+
+ );
+ }
- renderContent() {
- let initialFields = {
- ...(this.props.initialFields || {})
- };
- return renderFlowFooterChanges(changes, initialFields, hostingFieldOptions)}
- onSubmit={({ changes, setField }) => {
- return this.props.saveChanges(changes).then(({ successes, failures }) => {
- for (let k in successes) {
- setField(k, successes[k]);
- }
- if (Object.keys(failures).length > 0) {
- return Promise.reject({ error: Object.values(failures).join(' ') });
- }
- }).catch(({ error, failures = {} }) => {
- return Promise.reject({ error: unique(Object.values(failures).concat(error)).join(' ') });
- });
- }}
- validate={() => '' /*TODO: do some validation*/}
- renderForm={this.renderForm.bind(this)}
- />;
- }
+ renderContent() {
+ const initialFields = {
+ ...(this.props.initialFields || {}),
+ };
+ return (
+
+ renderFlowFooterChanges(changes, initialFields, hostingFieldOptions)
+ }
+ onSubmit={({ changes, setField }) => {
+ return this.props
+ .saveChanges(changes)
+ .then(({ successes, failures }) => {
+ for (const k in successes) {
+ setField(k, successes[k]);
+ }
+ if (Object.keys(failures).length > 0) {
+ return Promise.reject({
+ error: Object.values(failures).join(' '),
+ });
+ }
+ })
+ .catch(({ error, failures = {} }) => {
+ return Promise.reject({
+ error: unique(Object.values(failures).concat(error)).join(' '),
+ });
+ });
+ }}
+ validate={() => '' /*TODO: do some validation*/}
+ renderForm={this.renderForm.bind(this)}
+ />
+ );
+ }
}
-let hostingFieldOptions = {
- external_frame_link: {
- friendlyName: 'parse frame URL',
- showTo: true,
- },
- invalid_link_link: {
- friendlyName: 'custom 404 page',
- },
- choose_password_link: {
- friendlyName: 'custom "choose a new password" page',
- },
- email_verification_link: {
- friendlyName: 'custom "email verified" page'
- },
- password_updated_link: {
- friendlyName: 'custom "password changed" page',
- },
+const hostingFieldOptions = {
+ external_frame_link: {
+ friendlyName: 'parse frame URL',
+ showTo: true,
+ },
+ invalid_link_link: {
+ friendlyName: 'custom 404 page',
+ },
+ choose_password_link: {
+ friendlyName: 'custom "choose a new password" page',
+ },
+ email_verification_link: {
+ friendlyName: 'custom "email verified" page',
+ },
+ password_updated_link: {
+ friendlyName: 'custom "password changed" page',
+ },
- subdomain_name: {
- friendlyName: 'subdomain name',
- showFrom: true,
- showTo: true,
- },
+ subdomain_name: {
+ friendlyName: 'subdomain name',
+ showFrom: true,
+ showTo: true,
+ },
- send_email_address: {
- friendlyName: 'reply-to address',
- showTo: true,
- },
- sender_display_name: {
- friendlyName: 'display name',
- showTo: true,
- showFrom: true,
- },
- verify_emails: {
- friendlyName: 'user email verification',
- type: 'boolean'
- },
+ send_email_address: {
+ friendlyName: 'reply-to address',
+ showTo: true,
+ },
+ sender_display_name: {
+ friendlyName: 'display name',
+ showTo: true,
+ showFrom: true,
+ },
+ verify_emails: {
+ friendlyName: 'user email verification',
+ type: 'boolean',
+ },
- host_name: {
- friendlyName: 'host name',
- showTo: true,
- },
+ host_name: {
+ friendlyName: 'host name',
+ showTo: true,
+ },
- email_verification_mail_subject: {
- friendlyName: 'verification email subject',
- },
- email_verification_mail_body: {
- friendlyName: 'verification email body',
- },
- reset_password_mail_subject: {
- friendlyName: 'password reset subject',
- },
- reset_password_mail_body: {
- friendlyName: 'password reset email body',
- },
-}
+ email_verification_mail_subject: {
+ friendlyName: 'verification email subject',
+ },
+ email_verification_mail_body: {
+ friendlyName: 'verification email body',
+ },
+ reset_password_mail_subject: {
+ friendlyName: 'password reset subject',
+ },
+ reset_password_mail_body: {
+ friendlyName: 'password reset email body',
+ },
+};
diff --git a/src/dashboard/Settings/PushSettings.react.js b/src/dashboard/Settings/PushSettings.react.js
index bef1074fc1..ef95259676 100644
--- a/src/dashboard/Settings/PushSettings.react.js
+++ b/src/dashboard/Settings/PushSettings.react.js
@@ -5,24 +5,24 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import AppleCerts from 'dashboard/Settings/AppleCerts.react';
-import DashboardView from 'dashboard/DashboardView.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormButton from 'components/FormButton/FormButton.react';
-import FormTable from 'components/FormTable/FormTable.react';
-import getSiteDomain from 'lib/getSiteDomain';
-import Label from 'components/Label/Label.react';
-import pluck from 'lib/pluck';
-import React from 'react';
+import AppleCerts from 'dashboard/Settings/AppleCerts.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormButton from 'components/FormButton/FormButton.react';
+import FormTable from 'components/FormTable/FormTable.react';
+import getSiteDomain from 'lib/getSiteDomain';
+import Label from 'components/Label/Label.react';
+import pluck from 'lib/pluck';
+import React from 'react';
import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
-import setDifference from 'lib/setDifference';
-import styles from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import unique from 'lib/unique';
+import setDifference from 'lib/setDifference';
+import styles from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import unique from 'lib/unique';
const DEFAULT_LABEL_WIDTH = 60;
@@ -37,83 +37,125 @@ export default class PushSettings extends DashboardView {
window.open(`${getSiteDomain()}/apps/` + this.context.slug + '/edit#push', '_blank');
}
- renderForm({fields, setField}) {
- let pushSettingsFields =
- }
- input={}/>
- }
- input={}/>
- ;
+ renderForm({ fields, setField }) {
+ const pushSettingsFields = (
+
+
+ }
+ input={
+
+ }
+ />
+
+ }
+ input={
+
+ }
+ />
+
+ );
- let androidPushFields =
- }
- input={ } />
- }
- input={ } />
- {fields.gcmCredentials.length > 0 ? }
- input={
- ( {
- return {
- title: 'Credential #' + (index + 1).toString(),
- onDelete: () => setField('gcmCredentials', fields.gcmCredentials.filter(oldCred =>
- !compareGCMCredentials(oldCred, credential)
- )),
- notes: [
- {
- key: 'Sender ID',
- value: credential.sender_id,
- },
- {
- key: 'API Key',
- value: credential.api_key,
- }
- ],
- };
- })}/>)}/> : null}
- ;
+ const androidPushFields = (
+
+
+ }
+ input={
+
+ }
+ />
+
+ }
+ input={
+
+ }
+ />
+ {fields.gcmCredentials.length > 0 ? (
+ }
+ input={
+ {
+ return {
+ title: 'Credential #' + (index + 1).toString(),
+ onDelete: () =>
+ setField(
+ 'gcmCredentials',
+ fields.gcmCredentials.filter(
+ oldCred => !compareGCMCredentials(oldCred, credential)
+ )
+ ),
+ notes: [
+ {
+ key: 'Sender ID',
+ value: credential.sender_id,
+ },
+ {
+ key: 'API Key',
+ value: credential.api_key,
+ },
+ ],
+ };
+ })}
+ />
+ }
+ />
+ ) : null}
+
+ );
- let windowsPushFields =
- }
- input={ } />
- {/* TODO(drewgross): make Push Credentials component
+ const windowsPushFields = (
+
+ }
+ input={ }
+ />
+ {/* TODO(drewgross): make Push Credentials component
}
@@ -129,15 +171,18 @@ export default class PushSettings extends DashboardView {
placeholder='Client Secret'
onChange={setField.bind(this, 'windowsClientSecret')} />} />
*/}
- ;
+
+ );
- return
- {pushSettingsFields}
-
- {androidPushFields}
- {windowsPushFields}
-
-
+ return (
+
+ {pushSettingsFields}
+
+ {androidPushFields}
+ {windowsPushFields}
+
+
+ );
}
renderContent() {
@@ -145,7 +190,7 @@ export default class PushSettings extends DashboardView {
return null;
}
- let initialFields = {
+ const initialFields = {
enableClientPush: this.props.initialFields.client_push_enabled,
enableRestPush: this.props.initialFields.rest_push_enabled,
gcmCredentials: this.props.initialFields.gcm_credentials,
@@ -153,49 +198,67 @@ export default class PushSettings extends DashboardView {
customGCMAPIKey: '',
};
- return renderFlowFooterChanges(changes, initialFields, pushFieldOptions)}
- onSubmit={({ changes }) => {
- let promiseList = [];
- if (changes.enableClientPush !== undefined) {
- promiseList.push(this.context.setEnableClientPush(changes.enableClientPush));
- }
- if (changes.enableRestPush !== undefined) {
- promiseList.push(this.context.setEnableRestPush(changes.enableRestPush));
+ return (
+
+ renderFlowFooterChanges(changes, initialFields, pushFieldOptions)
}
- if (changes.customGCMSenderID && changes.customGCMAPIKey) {
- promiseList.push(this.context.addGCMCredentials(changes.customGCMSenderID, changes.customGCMAPIKey));
- }
- if (changes.gcmCredentials) { //Added creds don't show up in "changes"
- let removedGCMcredentials = setDifference(initialFields.gcmCredentials, changes.gcmCredentials, compareGCMCredentials);
- removedGCMcredentials.forEach(({ sender_id }) => {
- promiseList.push(this.context.deleteGCMPushCredentials(sender_id));
- });
- }
- return Promise.all(promiseList).then(() => {
- this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
- }).catch(errors => {
- return Promise.reject({ error: unique(pluck(errors, 'error')).join(' ') });
- });
- }}
- afterSave={({ setField }) => {
- setField('customGCMSenderID', '', true);
- setField('customGCMAPIKey', '', true);
- }}
- validate={({ changes }) => {
- if (changes.customGCMAPIKey && !changes.customGCMSenderID
- || !changes.customGCMAPIKey && changes.customGCMSenderID) {
- return 'A GCM Sender ID and API Key are both required.';
- }
- }}
- renderForm={this.renderForm.bind(this)} />;
+ onSubmit={({ changes }) => {
+ const promiseList = [];
+ if (changes.enableClientPush !== undefined) {
+ promiseList.push(this.context.setEnableClientPush(changes.enableClientPush));
+ }
+ if (changes.enableRestPush !== undefined) {
+ promiseList.push(this.context.setEnableRestPush(changes.enableRestPush));
+ }
+ if (changes.customGCMSenderID && changes.customGCMAPIKey) {
+ promiseList.push(
+ this.context.addGCMCredentials(changes.customGCMSenderID, changes.customGCMAPIKey)
+ );
+ }
+ if (changes.gcmCredentials) {
+ //Added creds don't show up in "changes"
+ const removedGCMcredentials = setDifference(
+ initialFields.gcmCredentials,
+ changes.gcmCredentials,
+ compareGCMCredentials
+ );
+ removedGCMcredentials.forEach(({ sender_id }) => {
+ promiseList.push(this.context.deleteGCMPushCredentials(sender_id));
+ });
+ }
+ return Promise.all(promiseList)
+ .then(() => {
+ this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
+ })
+ .catch(errors => {
+ return Promise.reject({
+ error: unique(pluck(errors, 'error')).join(' '),
+ });
+ });
+ }}
+ afterSave={({ setField }) => {
+ setField('customGCMSenderID', '', true);
+ setField('customGCMAPIKey', '', true);
+ }}
+ validate={({ changes }) => {
+ if (
+ (changes.customGCMAPIKey && !changes.customGCMSenderID) ||
+ (!changes.customGCMAPIKey && changes.customGCMSenderID)
+ ) {
+ return 'A GCM Sender ID and API Key are both required.';
+ }
+ }}
+ renderForm={this.renderForm.bind(this)}
+ />
+ );
}
}
-let compareGCMCredentials = (c1, c2) => c1.sender_id === c2.sender_id;
+const compareGCMCredentials = (c1, c2) => c1.sender_id === c2.sender_id;
-let pushFieldOptions = {
+const pushFieldOptions = {
enableClientPush: {
friendlyName: 'client push',
type: 'boolean',
@@ -224,4 +287,4 @@ let pushFieldOptions = {
windowsClientSecret: {
friendlyName: 'Windows client secret',
},
-}
+};
diff --git a/src/dashboard/Settings/Security/Security.react.js b/src/dashboard/Settings/Security/Security.react.js
new file mode 100644
index 0000000000..19b0f3ad28
--- /dev/null
+++ b/src/dashboard/Settings/Security/Security.react.js
@@ -0,0 +1,125 @@
+import TableView from 'dashboard/TableView.react';
+import Button from 'components/Button/Button.react';
+import EmptyState from 'components/EmptyState/EmptyState.react';
+import React from 'react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import styles from './Security.scss';
+import Parse from 'parse';
+import TableHeader from 'components/Table/TableHeader.react';
+
+export default class Security extends TableView {
+ constructor() {
+ super();
+ this.section = 'App Settings';
+ this.subsection = 'Security';
+ this.state = {
+ loading: false,
+ data: {},
+ error: '',
+ };
+ }
+
+ componentWillMount() {
+ this.reload();
+ }
+
+ componentWillReceiveProps(nextProps, nextContext) {
+ if (this.context !== nextContext) {
+ this.reload();
+ }
+ }
+
+ renderToolbar() {
+ return (
+
+ this.reload()} />
+
+ );
+ }
+
+ renderRow(security) {
+ return (
+
+
+ {security.check}
+
+
+ {security.i !== undefined ? '' : (security.status === 'success' ? '✅' : '❌')}
+
+
+ {security.issue}
+
+
+ {security.solution}
+
+
+ );
+ }
+
+ renderHeaders() {
+ return [
+
+ Check
+ ,
+
+ Status
+ ,
+
+ Issue
+ ,
+
+ Solution
+ ,
+ ];
+ }
+
+ renderEmpty() {
+ return {this.state.error}} icon="gears" cta="Reload" action={() => this.reload()} />;
+ }
+
+ tableData() {
+ const data = [];
+ if (this.state.data.state) {
+ data.push({
+ check: 'Overall status',
+ status: this.state.data.state,
+ header: true
+ }),
+ data.push({i: -1})
+ }
+ for (let i = 0; i < this.state.data?.groups?.length; i++) {
+ const group = this.state.data.groups[i]
+ data.push({
+ check: group.name,
+ status: group.state,
+ issue: '',
+ solution: '',
+ header: true
+ });
+ for (const check of group.checks) {
+ data.push({
+ check: check.title,
+ status: check.state,
+ issue: check.warning,
+ solution: check.solution,
+ });
+ }
+ if (i !== this.state.data.groups.length - 1) {
+ data.push({i});
+ }
+ }
+ return data;
+ }
+
+ async reload() {
+ if (!this.context.enableSecurityChecks) {
+ this.setState({ error: 'Enable Dashboard option `enableSecurityChecks` to run security check.' });
+ return;
+ }
+ this.setState({ loading: true });
+ const result = await Parse._request('GET', 'security', {}, { useMasterKey: true }).catch((e) => {
+ this.setState({ error: e?.message || e });
+ });
+ this.setState({ loading: false, data: result?.report || {} });
+ }
+}
diff --git a/src/dashboard/Settings/Security/Security.scss b/src/dashboard/Settings/Security/Security.scss
new file mode 100644
index 0000000000..568c7f5ad7
--- /dev/null
+++ b/src/dashboard/Settings/Security/Security.scss
@@ -0,0 +1,4 @@
+ @import 'stylesheets/globals.scss';
+.tableData {
+ white-space: normal !important;
+}
diff --git a/src/dashboard/Settings/SecuritySettings.react.js b/src/dashboard/Settings/SecuritySettings.react.js
index 059f61947d..c182c11e9b 100644
--- a/src/dashboard/Settings/SecuritySettings.react.js
+++ b/src/dashboard/Settings/SecuritySettings.react.js
@@ -6,20 +6,20 @@
* the root directory of this source tree.
*/
import AccountManager from 'lib/AccountManager';
-import DashboardView from 'dashboard/DashboardView.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormButton from 'components/FormButton/FormButton.react';
-import FormModal from 'components/FormModal/FormModal.react';
-import KeyField from 'components/KeyField/KeyField.react';
-import Label from 'components/Label/Label.react';
-import Modal from 'components/Modal/Modal.react';
-import React from 'react';
-import styles from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
+import DashboardView from 'dashboard/DashboardView.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormButton from 'components/FormButton/FormButton.react';
+import FormModal from 'components/FormModal/FormModal.react';
+import KeyField from 'components/KeyField/KeyField.react';
+import Label from 'components/Label/Label.react';
+import Modal from 'components/Modal/Modal.react';
+import React from 'react';
+import styles from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
export default class SecuritySettings extends DashboardView {
constructor() {
@@ -32,97 +32,239 @@ export default class SecuritySettings extends DashboardView {
dataFetched: false,
showResetDialog: false,
resetError: false,
- passwordInput: ''
+ passwordInput: '',
};
}
- renderForm({ fields, setField}) {
- let currentApp = this.context;
- let resetDialog = 0 || !AccountManager.currentUser().has_password}
- onSubmit={() => currentApp.resetMasterKey(this.state.passwordInput)}
- onClose={() => this.setState({showResetDialog: false})}
- clearFields={() => {this.setState({passwordInput: ''})}}
- buttonsInCenter={!AccountManager.currentUser().has_password}>
- {AccountManager.currentUser().has_password ? }
- input={
- this.setState({ passwordInput })}
- placeholder='Password' />
- } /> : null}
-
- let permissions = this.props.initialFields ?
-
+ renderForm({ fields, setField }) {
+ const currentApp = this.context;
+ const resetDialog = (
+ 0 || !AccountManager.currentUser().has_password}
+ onSubmit={() => currentApp.resetMasterKey(this.state.passwordInput)}
+ onClose={() => this.setState({ showResetDialog: false })}
+ clearFields={() => {
+ this.setState({ passwordInput: '' });
+ }}
+ buttonsInCenter={!AccountManager.currentUser().has_password}
+ >
+ {AccountManager.currentUser().has_password ? (
+
+ }
+ input={
+ this.setState({ passwordInput })}
+ placeholder="Password"
+ />
+ }
+ />
+ ) : null}
+
+ );
+ const permissions = this.props.initialFields ? (
+
}
+ label={
+
+ }
input={
setField('client_class_creation_enabled', allow)}/>
- } />
- : null;
+ onChange={allow => setField('client_class_creation_enabled', allow)}
+ />
+ }
+ />
+
+ ) : null;
return (
-
+
Main ID that uniquely specifies this app. Used with one of the keys below.} />}
- input={{currentApp.applicationId} } />
+ label={
+
+ Main ID that uniquely specifies this app.
+ Used with one of the keys below.
+
+ }
+ />
+ }
+ input={{currentApp.applicationId} }
+ />
Use this in consumer clients, such as the iOS or Android SDKs.} />}
- input={{currentApp.clientKey} } />
+ label={
+
+ Use this in consumer clients, such as
+ the iOS or Android SDKs.
+
+ }
+ />
+ }
+ input={{currentApp.clientKey} }
+ />
}
- input={{currentApp.javascriptKey} } />
+ label={
+
+ }
+ input={{currentApp.javascriptKey} }
+ />
Use this when making requests from Windows, Xamarin, or Unity clients.} />}
- input={{currentApp.windowsKey} } />
+ label={
+
+ Use this when making requests from
+ Windows, Xamarin, or Unity clients.
+
+ }
+ />
+ }
+ input={{currentApp.windowsKey} }
+ />
}
- input={{currentApp.restKey} } />
+ label={
+
+ }
+ input={
+
+ {currentApp.restKey}
+
+ }
+ />
}
- input={{currentApp.webhookKey} } />
+ label={
+
+ }
+ input={
+
+ {currentApp.webhookKey}
+
+ }
+ />
}
- input={{currentApp.fileKey} } />
+ label={
+
+ }
+ input={
+
+ {currentApp.fileKey}
+
+ }
+ />
}
- input={{currentApp.masterKey} } />
+ label={
+
+ }
+ input={
+
+ {currentApp.masterKey}
+
+ }
+ />
-
+
This will permanently reset the master key to a newly generated key.} />}
- input={ this.setState({ showResetDialog: true })} />} />
+ label={
+
+ This will permanently reset the master
+ key to a newly generated key.
+
+ }
+ />
+ }
+ input={
+ this.setState({ showResetDialog: true })}
+ />
+ }
+ />
{permissions}
{resetDialog}
-
+
);
}
renderContent() {
- return You've {changes.client_class_creation_enabled ? '' : 'dis'}allowed class creation on clients. }
- onSubmit={({changes}) => this.props.saveChanges({client_class_creation_enabled: changes.client_class_creation_enabled})}
- renderForm={this.renderForm.bind(this)} />;
+ return (
+ (
+
+ You've {changes.client_class_creation_enabled ? '' : 'dis'}allowed {' '}
+ class creation on clients.
+
+ )}
+ onSubmit={({ changes }) =>
+ this.props.saveChanges({
+ client_class_creation_enabled: changes.client_class_creation_enabled,
+ })
+ }
+ renderForm={this.renderForm.bind(this)}
+ />
+ );
}
}
diff --git a/src/dashboard/Settings/SettingsData.react.js b/src/dashboard/Settings/SettingsData.react.js
index 161ea0614b..ce7e7bba7e 100644
--- a/src/dashboard/Settings/SettingsData.react.js
+++ b/src/dashboard/Settings/SettingsData.react.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
+import React from 'react';
import { CurrentApp } from 'context/currentApp';
import { Outlet } from 'react-router-dom';
@@ -15,7 +15,7 @@ export default class SettingsData extends React.Component {
super();
this.state = {
- fields: undefined
+ fields: undefined,
};
}
@@ -35,10 +35,10 @@ export default class SettingsData extends React.Component {
}
saveChanges(changes) {
- let promise = this.context.saveSettingsFields(changes)
- promise.then(({successes}) => {
- let newFields = {...this.state.fields, ...successes};
- this.setState({fields: newFields});
+ const promise = this.context.saveSettingsFields(changes);
+ promise.then(({ successes }) => {
+ const newFields = { ...this.state.fields, ...successes };
+ this.setState({ fields: newFields });
});
return promise;
}
@@ -48,7 +48,7 @@ export default class SettingsData extends React.Component {
);
diff --git a/src/dashboard/Settings/UsersSettings.react.js b/src/dashboard/Settings/UsersSettings.react.js
index f9c396e120..a22173b7be 100644
--- a/src/dashboard/Settings/UsersSettings.react.js
+++ b/src/dashboard/Settings/UsersSettings.react.js
@@ -5,20 +5,20 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DashboardView from 'dashboard/DashboardView.react';
-import Field from 'components/Field/Field.react';
-import Fieldset from 'components/Fieldset/Fieldset.react';
-import FlowView from 'components/FlowView/FlowView.react';
-import FormTable from 'components/FormTable/FormTable.react';
-import Label from 'components/Label/Label.react';
-import pluck from 'lib/pluck';
-import React from 'react';
+import DashboardView from 'dashboard/DashboardView.react';
+import Field from 'components/Field/Field.react';
+import Fieldset from 'components/Fieldset/Fieldset.react';
+import FlowView from 'components/FlowView/FlowView.react';
+import FormTable from 'components/FormTable/FormTable.react';
+import Label from 'components/Label/Label.react';
+import pluck from 'lib/pluck';
+import React from 'react';
import renderFlowFooterChanges from 'lib/renderFlowFooterChanges';
-import style from 'dashboard/Settings/Settings.scss';
-import TextInput from 'components/TextInput/TextInput.react';
-import Toggle from 'components/Toggle/Toggle.react';
-import Toolbar from 'components/Toolbar/Toolbar.react';
-import unique from 'lib/unique';
+import style from 'dashboard/Settings/Settings.scss';
+import TextInput from 'components/TextInput/TextInput.react';
+import Toggle from 'components/Toggle/Toggle.react';
+import Toolbar from 'components/Toolbar/Toolbar.react';
+import unique from 'lib/unique';
const DEFAULT_SETTINGS_LABEL_WIDTH = 62;
@@ -30,337 +30,441 @@ export default class UsersSettings extends DashboardView {
}
renderContent() {
- let toolbar =
- if (!this.props.initialFields) {
- return toolbar;
- }
- let initialFields = {
- requireRevocableSessions: this.props.initialFields.require_revocable_session,
- expireInactiveSessions: this.props.initialFields.expire_revocable_session,
- revokeSessionOnPasswordChange: this.props.initialFields.revoke_on_password_reset,
+ const toolbar = ;
+ if (!this.props.initialFields) {
+ return toolbar;
+ }
+ const initialFields = {
+ requireRevocableSessions: this.props.initialFields.require_revocable_session,
+ expireInactiveSessions: this.props.initialFields.expire_revocable_session,
+ revokeSessionOnPasswordChange: this.props.initialFields.revoke_on_password_reset,
- enableNewMethodsByDefault: this.props.initialFields.auth_options_attributes._enable_by_default,
- allowUsernameAndPassword: this.props.initialFields.auth_options_attributes.username.enabled,
- allowAnonymousUsers: this.props.initialFields.auth_options_attributes.anonymous.enabled,
- allowCustomAuthentication: (this.props.initialFields.auth_options_attributes.custom || {enabled: false}).enabled,
+ enableNewMethodsByDefault:
+ this.props.initialFields.auth_options_attributes._enable_by_default,
+ allowUsernameAndPassword: this.props.initialFields.auth_options_attributes.username.enabled,
+ allowAnonymousUsers: this.props.initialFields.auth_options_attributes.anonymous.enabled,
+ allowCustomAuthentication: (
+ this.props.initialFields.auth_options_attributes.custom || {
+ enabled: false,
+ }
+ ).enabled,
- allowFacebookAuthentication: this.props.initialFields.auth_options_attributes.facebook.enabled,
- facebookAppIDList: this.props.initialFields.auth_options_attributes.facebook.app_ids || [],
- facebookAppSecretList: this.props.initialFields.auth_options_attributes.facebook.app_secrets || [],
+ allowFacebookAuthentication:
+ this.props.initialFields.auth_options_attributes.facebook.enabled,
+ facebookAppIDList: this.props.initialFields.auth_options_attributes.facebook.app_ids || [],
+ facebookAppSecretList:
+ this.props.initialFields.auth_options_attributes.facebook.app_secrets || [],
- facebookAppID: '',
- facebookAppSecret: '',
+ facebookAppID: '',
+ facebookAppSecret: '',
- allowTwitterAuthentication: this.props.initialFields.auth_options_attributes.twitter.enabled,
- twitterConsumerKeysList: this.props.initialFields.auth_options_attributes.twitter.consumer_keys || [],
+ allowTwitterAuthentication: this.props.initialFields.auth_options_attributes.twitter.enabled,
+ twitterConsumerKeysList:
+ this.props.initialFields.auth_options_attributes.twitter.consumer_keys || [],
- twitterConsumerKey: '',
- };
- let renderForm = ({fields, setField}) => {
- let userSessionsFields = This feature allows for better security and management of sessions for users.Learn more }>
-
Disable legacy session tokens to make your app more secure. If enabled, all requests with legacy tokens will error. Learn more }
- />}
- input={ }
- />
- We’ll automatically delete revocable sessions that have not made an API request in the last year.}
- />}
- input={ }
- />
- When a user changes or resets their password, we’ll automatically delete all Session objects associated with this user.}
- />}
- input={ }
- />
-
+ twitterConsumerKey: '',
+ };
+ const renderForm = ({ fields, setField }) => {
+ const userSessionsFields = (
+
+ This feature allows for better security and management
+ of sessions for users.Learn more
+
+ }
+ >
+
+ Disable legacy session tokens to make your app more secure. If enabled, all
+ requests with legacy tokens will error. Learn more
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ We’ll automatically delete revocable sessions that have not made an API request
+ in the last year.
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ When a user changes or resets their password, we’ll
+ automatically delete all Session objects associated with this user.
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ );
- let userAuthenticationFields =
- }
- input={ }
- />
- }
- input={ }
- />
- }
- input={ }
- />
-
+ const userAuthenticationFields = (
+
+
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+ }
+ input={
+
+ }
+ />
+
+ );
- let socialLoginFields =
- }
- input={ {
- setField('allowFacebookAuthentication', enabled);
- if (!enabled) {
- setField('facebookAppID', '');
- setField('facebookAppSecret', '');
- }
- }}
- value={fields.allowFacebookAuthentication}
- />}
- />
- {fields.allowFacebookAuthentication ? }
- input={[
- ,
- ,
- ]}
- /> : null}
- {fields.facebookAppIDList.length > 0 ? }
- input={ ({
- title: 'App #' + (index + 1).toString(),
- color: 'green',
- onDelete: () => {
- let newFacebookAppIDList = fields.facebookAppIDList.slice();
- newFacebookAppIDList.splice(index, 1);
+ const socialLoginFields = (
+
+ }
+ input={
+ {
+ setField('allowFacebookAuthentication', enabled);
+ if (!enabled) {
+ setField('facebookAppID', '');
+ setField('facebookAppSecret', '');
+ }
+ }}
+ value={fields.allowFacebookAuthentication}
+ />
+ }
+ />
+ {fields.allowFacebookAuthentication ? (
+ }
+ input={[
+ ,
+ ,
+ ]}
+ />
+ ) : null}
+ {fields.facebookAppIDList.length > 0 ? (
+ }
+ input={
+ ({
+ title: 'App #' + (index + 1).toString(),
+ color: 'green',
+ onDelete: () => {
+ const newFacebookAppIDList = fields.facebookAppIDList.slice();
+ newFacebookAppIDList.splice(index, 1);
- let newFacebookAppSecretList = fields.facebookAppSecretList.slice();
- newFacebookAppSecretList.splice(index, 1);
+ const newFacebookAppSecretList = fields.facebookAppSecretList.slice();
+ newFacebookAppSecretList.splice(index, 1);
- //TODO(drewgross): handle errors, display progress, etc.
- this.context.setConnectedFacebookApps(newFacebookAppIDList, newFacebookAppSecretList).then(() => {
- this.forceUpdate();
- });
- },
- notes: [
- {
- key: 'App ID',
- value: fields.facebookAppIDList[index],
- },
- {
- key: 'App Secret',
- value: fields.facebookAppSecretList[index],
- }
- ],
- }))}
- />}
- /> : null}
- }
- input={ {
- setField('allowTwitterAuthentication', enabled);
- if (!enabled) {
- setField('twitterConsumerKey', '');
- }
- }}
- value={fields.allowTwitterAuthentication}
- />} />
- {fields.allowTwitterAuthentication ? }
- input={
- }/> : null}
- {fields.twitterConsumerKeysList.length > 0 ? }
- input={ ({
- title: 'App #' + (index + 1).toString(),
- color: 'green',
- onDelete: () => {
- let twitterConsumerKeyList = fields.twitterConsumerKeysList.slice();
- twitterConsumerKeyList.splice(index, 1);
+ //TODO(drewgross): handle errors, display progress, etc.
+ this.context
+ .setConnectedFacebookApps(newFacebookAppIDList, newFacebookAppSecretList)
+ .then(() => {
+ this.forceUpdate();
+ });
+ },
+ notes: [
+ {
+ key: 'App ID',
+ value: fields.facebookAppIDList[index],
+ },
+ {
+ key: 'App Secret',
+ value: fields.facebookAppSecretList[index],
+ },
+ ],
+ }))}
+ />
+ }
+ />
+ ) : null}
+ }
+ input={
+ {
+ setField('allowTwitterAuthentication', enabled);
+ if (!enabled) {
+ setField('twitterConsumerKey', '');
+ }
+ }}
+ value={fields.allowTwitterAuthentication}
+ />
+ }
+ />
+ {fields.allowTwitterAuthentication ? (
+ }
+ input={
+
+ }
+ />
+ ) : null}
+ {fields.twitterConsumerKeysList.length > 0 ? (
+ }
+ input={
+ ({
+ title: 'App #' + (index + 1).toString(),
+ color: 'green',
+ onDelete: () => {
+ const twitterConsumerKeyList = fields.twitterConsumerKeysList.slice();
+ twitterConsumerKeyList.splice(index, 1);
- //TODO(drewgross): handle errors, display progress, etc.
- this.context.setConnectedTwitterApps(twitterConsumerKeyList).then(() => {
- this.forceUpdate();
- });
- },
- notes: [
- {
- key: 'Consumer Key',
- value: fields.twitterConsumerKeysList[index],
- },
- ],
- }))}
- />}
- /> : null}
-
+ //TODO(drewgross): handle errors, display progress, etc.
+ this.context.setConnectedTwitterApps(twitterConsumerKeyList).then(() => {
+ this.forceUpdate();
+ });
+ },
+ notes: [
+ {
+ key: 'Consumer Key',
+ value: fields.twitterConsumerKeysList[index],
+ },
+ ],
+ }))}
+ />
+ }
+ />
+ ) : null}
+
+ );
- return
- {userSessionsFields}
- {userAuthenticationFields}
- {socialLoginFields}
- {toolbar}
-
;
- }
+ return (
+
+ {userSessionsFields}
+ {userAuthenticationFields}
+ {socialLoginFields}
+ {toolbar}
+
+ );
+ };
- return renderFlowFooterChanges(changes, initialFields, userFieldOptions)}
- onSubmit={({ changes, setField }) => {
- let promiseList = [];
- if (changes.requireRevocableSessions !== undefined) {
- promiseList.push(this.context.setRequireRevocableSessions(changes.requireRevocableSessions));
- }
- if (changes.expireInactiveSessions !== undefined) {
- promiseList.push(this.context.setExpireInactiveSessions(changes.expireInactiveSessions));
- }
- if (changes.revokeSessionOnPasswordChange !== undefined) {
- promiseList.push(this.context.setRevokeSessionOnPasswordChange(changes.revokeSessionOnPasswordChange));
- }
- if (changes.enableNewMethodsByDefault !== undefined) {
- promiseList.push(this.context.setEnableNewMethodsByDefault(changes.enableNewMethodsByDefault));
- }
- if (changes.allowUsernameAndPassword !== undefined) {
- promiseList.push(this.context.setAllowUsernameAndPassword(changes.allowUsernameAndPassword));
- }
- if (changes.allowAnonymousUsers !== undefined) {
- promiseList.push(this.context.setAllowAnonymousUsers(changes.allowAnonymousUsers));
- }
- if (changes.allowFacebookAuthentication !== undefined) {
- promiseList.push(this.context.setAllowFacebookAuth(changes.allowFacebookAuthentication));
- }
- if (changes.allowCustomAuthentication !== undefined) {
- promiseList.push(this.context.setAllowCustomAuthentication(changes.allowCustomAuthentication));
- }
- if (changes.facebookAppID !== undefined && changes.facebookAppSecret !== undefined) {
- let fbAppPromise = this.context.addConnectedFacebookApp(changes.facebookAppID, changes.facebookAppSecret);
- fbAppPromise.then(() => {
- setField('facebookAppID', '');
- setField('facebookAppSecret', '');
- })
- promiseList.push(fbAppPromise);
- }
- if (changes.twitterConsumerKey !== undefined) {
- let twitterAppPromise = this.context.addConnectedTwitterApp(changes.twitterConsumerKey);
- twitterAppPromise.then(() => {
- setField('twitterConsumerKey', '');
- });
- promiseList.push(twitterAppPromise);
- }
- if (changes.allowTwitterAuthentication !== undefined) {
- promiseList.push(this.context.setAllowTwitterAuth(changes.allowTwitterAuthentication));
- }
+ return (
+
+ renderFlowFooterChanges(changes, initialFields, userFieldOptions)
+ }
+ onSubmit={({ changes, setField }) => {
+ const promiseList = [];
+ if (changes.requireRevocableSessions !== undefined) {
+ promiseList.push(
+ this.context.setRequireRevocableSessions(changes.requireRevocableSessions)
+ );
+ }
+ if (changes.expireInactiveSessions !== undefined) {
+ promiseList.push(
+ this.context.setExpireInactiveSessions(changes.expireInactiveSessions)
+ );
+ }
+ if (changes.revokeSessionOnPasswordChange !== undefined) {
+ promiseList.push(
+ this.context.setRevokeSessionOnPasswordChange(changes.revokeSessionOnPasswordChange)
+ );
+ }
+ if (changes.enableNewMethodsByDefault !== undefined) {
+ promiseList.push(
+ this.context.setEnableNewMethodsByDefault(changes.enableNewMethodsByDefault)
+ );
+ }
+ if (changes.allowUsernameAndPassword !== undefined) {
+ promiseList.push(
+ this.context.setAllowUsernameAndPassword(changes.allowUsernameAndPassword)
+ );
+ }
+ if (changes.allowAnonymousUsers !== undefined) {
+ promiseList.push(this.context.setAllowAnonymousUsers(changes.allowAnonymousUsers));
+ }
+ if (changes.allowFacebookAuthentication !== undefined) {
+ promiseList.push(
+ this.context.setAllowFacebookAuth(changes.allowFacebookAuthentication)
+ );
+ }
+ if (changes.allowCustomAuthentication !== undefined) {
+ promiseList.push(
+ this.context.setAllowCustomAuthentication(changes.allowCustomAuthentication)
+ );
+ }
+ if (changes.facebookAppID !== undefined && changes.facebookAppSecret !== undefined) {
+ const fbAppPromise = this.context.addConnectedFacebookApp(
+ changes.facebookAppID,
+ changes.facebookAppSecret
+ );
+ fbAppPromise.then(() => {
+ setField('facebookAppID', '');
+ setField('facebookAppSecret', '');
+ });
+ promiseList.push(fbAppPromise);
+ }
+ if (changes.twitterConsumerKey !== undefined) {
+ const twitterAppPromise = this.context.addConnectedTwitterApp(
+ changes.twitterConsumerKey
+ );
+ twitterAppPromise.then(() => {
+ setField('twitterConsumerKey', '');
+ });
+ promiseList.push(twitterAppPromise);
+ }
+ if (changes.allowTwitterAuthentication !== undefined) {
+ promiseList.push(this.context.setAllowTwitterAuth(changes.allowTwitterAuthentication));
+ }
- return Promise.all(promiseList).then(() => {
- this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
- }).catch(errors => {
- return Promise.reject({ error: unique(pluck(errors, 'error')).join(' ') });
- });
- }}
- />;
+ return Promise.all(promiseList)
+ .then(() => {
+ this.forceUpdate(); //Need to forceUpdate to see changes applied to source ParseApp
+ })
+ .catch(errors => {
+ return Promise.reject({
+ error: unique(pluck(errors, 'error')).join(' '),
+ });
+ });
+ }}
+ />
+ );
}
}
const userFieldOptions = {
- requireRevocableSessions: {
- friendlyName: 'require revocable sessions',
- type: 'boolean',
- },
- expireInactiveSessions: {
- friendlyName: 'expire inactive sessions',
- type: 'boolean',
- },
- revokeSessionOnPasswordChange: {
- friendlyName: 'revoke session on password change',
- type: 'boolean',
- },
- enableNewMethodsByDefault: {
- friendlyName: 'new authentication methods by default',
- type: 'boolean',
- },
- allowUsernameAndPassword: {
- friendlyName: 'user name and password login',
- type: 'boolean',
- },
- allowAnonymousUsers: {
- friendlyName: 'annonymous users',
- type: 'boolean',
- },
- allowFacebookAuthentication: {
- friendlyName: 'Facebook authentication',
- type: 'boolean',
- },
- facebookAppID: {
- friendlyName: 'Facebook app ID',
- type: 'addition',
- },
- facebookAppSecret: {
- friendlyName: 'Facebook app secret',
- type: 'addition',
- },
- allowTwitterAuthentication: {
- friendlyName: 'Twitter authentication',
- type: 'boolean',
- },
- twitterConsumerKey: {
- friendlyName: 'Twitter Consumer Key',
- type: 'addition',
- },
- allowCustomAuthentication: {
- friendlyName: 'custom authentication',
- type: 'boolean',
- },
+ requireRevocableSessions: {
+ friendlyName: 'require revocable sessions',
+ type: 'boolean',
+ },
+ expireInactiveSessions: {
+ friendlyName: 'expire inactive sessions',
+ type: 'boolean',
+ },
+ revokeSessionOnPasswordChange: {
+ friendlyName: 'revoke session on password change',
+ type: 'boolean',
+ },
+ enableNewMethodsByDefault: {
+ friendlyName: 'new authentication methods by default',
+ type: 'boolean',
+ },
+ allowUsernameAndPassword: {
+ friendlyName: 'user name and password login',
+ type: 'boolean',
+ },
+ allowAnonymousUsers: {
+ friendlyName: 'annonymous users',
+ type: 'boolean',
+ },
+ allowFacebookAuthentication: {
+ friendlyName: 'Facebook authentication',
+ type: 'boolean',
+ },
+ facebookAppID: {
+ friendlyName: 'Facebook app ID',
+ type: 'addition',
+ },
+ facebookAppSecret: {
+ friendlyName: 'Facebook app secret',
+ type: 'addition',
+ },
+ allowTwitterAuthentication: {
+ friendlyName: 'Twitter authentication',
+ type: 'boolean',
+ },
+ twitterConsumerKey: {
+ friendlyName: 'Twitter Consumer Key',
+ type: 'addition',
+ },
+ allowCustomAuthentication: {
+ friendlyName: 'custom authentication',
+ type: 'boolean',
+ },
};
diff --git a/src/dashboard/SidebarBuilder.js b/src/dashboard/SidebarBuilder.js
index 6af38979af..7a3e1286fb 100644
--- a/src/dashboard/SidebarBuilder.js
+++ b/src/dashboard/SidebarBuilder.js
@@ -5,31 +5,29 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import React from 'react';
-import Sidebar from 'components/Sidebar/Sidebar.react';
+import React from 'react';
+import Sidebar from 'components/Sidebar/Sidebar.react';
-let accountSidebarSections = [
+const accountSidebarSections = [
{
name: 'Your Apps',
icon: 'blank-app-outline',
- link: '/apps'
- }, /*{
+ link: '/apps',
+ } /*{
name: 'Account Settings',
icon: 'users-solid',
link: '/account',
- }*/
+ }*/,
];
export function buildAccountSidebar(options) {
- let {
- section,
- subsection
- } = options;
+ const { section, subsection } = options;
return (
+ prefix={''}
+ />
);
}
diff --git a/src/dashboard/TableView.react.js b/src/dashboard/TableView.react.js
index 679912835a..95427ee7fb 100644
--- a/src/dashboard/TableView.react.js
+++ b/src/dashboard/TableView.react.js
@@ -5,15 +5,15 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import DashboardView from 'dashboard/DashboardView.react';
+import DashboardView from 'dashboard/DashboardView.react';
import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
-import React from 'react';
-import styles from 'dashboard/TableView.scss';
+import React from 'react';
+import styles from 'dashboard/TableView.scss';
export default class TableView extends DashboardView {
columnWidths(keys) {
- let equalWidth = 100 / keys.length + '%';
- let widths = {};
+ const equalWidth = 100 / keys.length + '%';
+ const widths = {};
// leave the last key undefined, so it fills the remaining space
for (let i = 0; i < keys.length - 1; i++) {
widths[keys[i]] = equalWidth;
@@ -26,9 +26,9 @@ export default class TableView extends DashboardView {
}
renderContent() {
- let toolbar = this.renderToolbar();
- let data = this.tableData();
- let footer = this.renderFooter();
+ const toolbar = this.renderToolbar();
+ const data = this.tableData();
+ const footer = this.renderFooter();
let content = null;
let headers = null;
if (data !== undefined) {
@@ -41,9 +41,7 @@ export default class TableView extends DashboardView {
content = (
-
- {data.map((row) => this.renderRow(row))}
-
+ {data.map(row => this.renderRow(row))}
{footer}
@@ -52,8 +50,8 @@ export default class TableView extends DashboardView {
}
}
}
- let extras = this.renderExtras ? this.renderExtras() : null;
- let loading = this.state ? this.state.loading : false;
+ const extras = this.renderExtras ? this.renderExtras() : null;
+ const loading = this.state ? this.state.loading : false;
return (
diff --git a/src/dashboard/index.js b/src/dashboard/index.js
index 10ce7596c7..539e71d8c3 100644
--- a/src/dashboard/index.js
+++ b/src/dashboard/index.js
@@ -7,15 +7,15 @@
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';
-import Immutable from 'immutable';
+import Immutable from 'immutable';
import installDevTools from 'immutable-devtools';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Dashboard from './Dashboard';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Dashboard from './Dashboard';
require('stylesheets/fonts.scss');
-require('graphiql/graphiql.min.css')
+require('graphiql/graphiql.min.css');
installDevTools(Immutable);
-var path = window.PARSE_DASHBOARD_PATH || '/';
-ReactDOM.render(, document.getElementById('browser_mount'));
+const path = window.PARSE_DASHBOARD_PATH || '/';
+ReactDOM.render( , document.getElementById('browser_mount'));
diff --git a/src/lib/AJAX.js b/src/lib/AJAX.js
index 74824324ce..febd6aef5c 100644
--- a/src/lib/AJAX.js
+++ b/src/lib/AJAX.js
@@ -6,25 +6,34 @@
* the root directory of this source tree.
*/
import * as CSRFManager from 'lib/CSRFManager';
-import encodeFormData from 'lib/encodeFormData';
+import encodeFormData from 'lib/encodeFormData';
let basePath = '';
export function setBasePath(newBasePath) {
basePath = newBasePath || '';
if (basePath.endsWith('/')) {
- basePath = basePath.slice(0, basePath.length-1);
+ basePath = basePath.slice(0, basePath.length - 1);
}
}
// abortable flag used to pass xhr reference so user can abort accordingly
-export function request(method, url, body, abortable = false, withCredentials = true, useRequestedWith = true) {
- if (!url.startsWith('http://')
- && !url.startsWith('https://')
- && basePath.length
- && !url.startsWith(basePath + '/')) {
+export function request(
+ method,
+ url,
+ body,
+ abortable = false,
+ withCredentials = true,
+ useRequestedWith = true
+) {
+ if (
+ !url.startsWith('http://') &&
+ !url.startsWith('https://') &&
+ basePath.length &&
+ !url.startsWith(basePath + '/')
+ ) {
url = basePath + url;
}
- let xhr = new XMLHttpRequest();
+ const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
xhr.setRequestHeader('X-CSRF-Token', CSRFManager.getToken());
@@ -35,7 +44,7 @@ export function request(method, url, body, abortable = false, withCredentials =
xhr.withCredentials = withCredentials;
let resolve;
let reject;
- let p = new Promise((res, rej) => {
+ const p = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
@@ -50,12 +59,12 @@ export function request(method, url, body, abortable = false, withCredentials =
notice: 'Network Error',
});
};
- xhr.onload = function() {
+ xhr.onload = function () {
if (this.status === 200) {
let json = {};
try {
json = JSON.parse(this.responseText);
- } catch(ex) {
+ } catch (ex) {
p.reject(this.responseText);
return;
}
@@ -76,11 +85,11 @@ export function request(method, url, body, abortable = false, withCredentials =
let json = {};
try {
json = JSON.parse(this.responseText);
- } catch(ex) {
- p.reject(this.responseText)
+ } catch (ex) {
+ p.reject(this.responseText);
return;
}
- let message = json.message || json.error || json.notice || 'Request Error';
+ const message = json.message || json.error || json.notice || 'Request Error';
p.reject({
success: false,
message: message,
@@ -102,13 +111,10 @@ export function request(method, url, body, abortable = false, withCredentials =
if (body instanceof FormData) {
xhr.send(body);
} else {
- xhr.setRequestHeader(
- 'Content-Type',
- 'application/x-www-form-urlencoded; charset=UTF-8'
- );
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
// Encode it as a url parameter string
- let formData = [];
- for (let k in body) {
+ const formData = [];
+ for (const k in body) {
formData.push(encodeFormData(k, body[k]));
}
xhr.send(formData.join('&'));
@@ -119,8 +125,8 @@ export function request(method, url, body, abortable = false, withCredentials =
if (abortable) {
return {
xhr,
- promise: p
- }
+ promise: p,
+ };
}
return p;
}
diff --git a/src/lib/AccountManager.js b/src/lib/AccountManager.js
index 04695f7556..3eda1983a8 100644
--- a/src/lib/AccountManager.js
+++ b/src/lib/AccountManager.js
@@ -6,14 +6,14 @@
* the root directory of this source tree.
*/
import { abortableGet, put, post, del } from 'lib/AJAX';
-import { unescape } from 'lib/StringEscaping';
+import { unescape } from 'lib/StringEscaping';
let currentUser = null;
-let xhrMap = {};
+const xhrMap = {};
-let AccountManager = {
+const AccountManager = {
init() {
- let accountData = document.getElementById('accountData');
+ const accountData = document.getElementById('accountData');
if (!accountData) {
return;
}
@@ -28,20 +28,23 @@ let AccountManager = {
},
resetPasswordAndEmailAndName(currentPassword, newPassword, newEmail, newName) {
- let path = '/account';
+ const path = '/account';
return put(path, {
confirm_password: currentPassword,
- 'user[password]':newPassword,
+ 'user[password]': newPassword,
'user[email]': newEmail,
'user[name]': newName,
});
},
createAccountKey(keyName) {
- let path = '/account/keys';
- let promise = post(path, {name: keyName});
+ const path = '/account/keys';
+ const promise = post(path, { name: keyName });
promise.then(newKey => {
- let hiddenKey = {...newKey, token: '\u2022\u2022' + newKey.token.substr(newKey.token.length - 4)};
+ const hiddenKey = {
+ ...newKey,
+ token: '\u2022\u2022' + newKey.token.substr(newKey.token.length - 4),
+ };
//TODO: save the account key better. This currently only works because everywhere that uses
// the account keys happens to rerender after the account keys change anyway.
currentUser.account_keys.unshift(hiddenKey);
@@ -50,10 +53,9 @@ let AccountManager = {
},
deleteAccountKeyById(id) {
- let path = '/account/keys/' + id.toString();
- let promise = del(path);
+ const path = '/account/keys/' + id.toString();
+ const promise = del(path);
promise.then(() => {
-
//TODO: delete the account key better. This currently only works because everywhere that uses
// the account keys happens to rerender after the account keys change anyway.
currentUser.account_keys = currentUser.account_keys.filter(key => key.id != id);
@@ -62,10 +64,10 @@ let AccountManager = {
},
fetchLinkedAccounts(xhrKey) {
- let path = '/account/linked_accounts';
- let {xhr, promise} = abortableGet(path);
+ const path = '/account/linked_accounts';
+ const { xhr, promise } = abortableGet(path);
xhrMap[xhrKey] = xhr;
- promise.then((result) => {
+ promise.then(result => {
this.linkedAccounts = result;
});
return promise;
diff --git a/src/lib/AppsManager.js b/src/lib/AppsManager.js
index e3d58c589f..04f75aceff 100644
--- a/src/lib/AppsManager.js
+++ b/src/lib/AppsManager.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import ParseApp from 'lib/ParseApp';
+import ParseApp from 'lib/ParseApp';
import { post, del } from 'lib/AJAX';
-let appsStore = [];
+const appsStore = [];
const AppsManager = {
addApp(raw) {
@@ -16,14 +16,14 @@ const AppsManager = {
},
apps() {
- appsStore.sort(function(app1, app2) {
+ appsStore.sort(function (app1, app2) {
return app1.name.localeCompare(app2.name);
});
return appsStore;
},
findAppBySlugOrName(slugOrName) {
- let apps = this.apps();
+ const apps = this.apps();
for (let i = apps.length; i--;) {
if (apps[i].slug === slugOrName || apps[i].name === slugOrName) {
return apps[i];
@@ -33,14 +33,14 @@ const AppsManager = {
},
create(name, connectionURL) {
- let payload = {
- parse_app: { name }
+ const payload = {
+ parse_app: { name },
};
if (connectionURL) {
payload.parse_app.connectionString = connectionURL;
}
- return post('/apps', payload).then((response) => {
- let newApp = new ParseApp(response.app);
+ return post('/apps', payload).then(response => {
+ const newApp = new ParseApp(response.app);
appsStore.push(newApp);
return newApp;
});
@@ -59,17 +59,17 @@ const AppsManager = {
// Fetch the latest usage and request info for the apps index
getAllAppsIndexStats() {
- return Promise.all(this.apps().map(app => {
- if (app.serverInfo.error) {
- return;
- }
- return Promise.all(
- [
- app.getClassCount('_Installation').then(count => app.installations = count),
- app.getClassCount('_User').then(count => app.users = count)
- ]
- );
- }));
+ return Promise.all(
+ this.apps().map(app => {
+ if (app.serverInfo.error) {
+ return;
+ }
+ return Promise.all([
+ app.getClassCount('_Installation').then(count => (app.installations = count)),
+ app.getClassCount('_User').then(count => (app.users = count)),
+ ]);
+ })
+ );
},
// Options should be a list containing a subset of
@@ -77,7 +77,7 @@ const AppsManager = {
// indicating which parts of the app to clone.
cloneApp(slug, name, options) {
//Clone nothing by default
- let optionsForRuby = {
+ const optionsForRuby = {
cloud_code: false,
background_jobs: false,
config: false,
@@ -85,13 +85,14 @@ const AppsManager = {
app_settings: false,
data: false,
};
- options.forEach((option) => {
- if (option !== 'data') { //Data cloning not supported yet, but api_server still requires the key to be present
+ options.forEach(option => {
+ if (option !== 'data') {
+ //Data cloning not supported yet, but api_server still requires the key to be present
optionsForRuby[option] = true;
}
});
- let path = '/apps/' + slug + '/clone_app';
- let request = post(path, {
+ const path = '/apps/' + slug + '/clone_app';
+ const request = post(path, {
app_name: name,
options: optionsForRuby,
});
@@ -105,21 +106,21 @@ const AppsManager = {
},
transferApp(slug, newOwner, password) {
- let payload = {
+ const payload = {
new_owner_email: newOwner,
- }
+ };
if (password) {
// Users who log in with oauth don't have a password,
// and don't require one to transfer their app.
payload.password_confirm_transfer = password;
}
- let promise = post('/apps/' + slug + '/transfer', payload);
+ const promise = post('/apps/' + slug + '/transfer', payload);
promise.then(() => {
//TODO modify appsStore to reflect transfer
});
return promise;
- }
-}
+ },
+};
export default AppsManager;
diff --git a/src/lib/CSRFManager.js b/src/lib/CSRFManager.js
index fcb0c3813d..bcbf22a732 100644
--- a/src/lib/CSRFManager.js
+++ b/src/lib/CSRFManager.js
@@ -11,7 +11,7 @@ let currentToken = null;
export function getToken() {
if (!currentToken) {
- let tokenScript = document.getElementById('csrf');
+ const tokenScript = document.getElementById('csrf');
if (tokenScript) {
currentToken = JSON.parse(unescape(tokenScript.innerHTML));
}
diff --git a/src/lib/Charting.js b/src/lib/Charting.js
index 18b8773361..d2fb90ac4b 100644
--- a/src/lib/Charting.js
+++ b/src/lib/Charting.js
@@ -10,12 +10,14 @@ const DAY = HOUR * 24;
// Determines the points marked on the x-axis of a chart
export function timeAxisBuckets(minRaw, maxRaw) {
- let min = new Date(minRaw);
- let max = new Date(maxRaw);
+ const min = new Date(minRaw);
+ const max = new Date(maxRaw);
if (max - min <= DAY * 2) {
- let buckets = [];
- let bucket = new Date(Date.UTC(min.getUTCFullYear(), min.getUTCMonth(), min.getUTCDate(), min.getUTCHours()));
+ const buckets = [];
+ let bucket = new Date(
+ Date.UTC(min.getUTCFullYear(), min.getUTCMonth(), min.getUTCDate(), min.getUTCHours())
+ );
while (bucket < max) {
buckets.push(bucket);
bucket = new Date(bucket.getTime() + HOUR);
@@ -25,7 +27,7 @@ export function timeAxisBuckets(minRaw, maxRaw) {
}
if (max - min <= DAY * 60) {
- let buckets = [];
+ const buckets = [];
let bucket = new Date(Date.UTC(min.getUTCFullYear(), min.getUTCMonth(), min.getUTCDate()));
while (bucket < max) {
buckets.push(bucket);
@@ -37,7 +39,7 @@ export function timeAxisBuckets(minRaw, maxRaw) {
return buckets;
}
- let buckets = [];
+ const buckets = [];
let bucket = new Date(Date.UTC(min.getUTCFullYear(), min.getUTCMonth()));
while (bucket < max) {
buckets.push(bucket);
@@ -50,7 +52,8 @@ export function timeAxisBuckets(minRaw, maxRaw) {
// Determines the points marked on the y-axis of a chart
export function valueAxisBuckets(max) {
- if (max === 0) { // prevent horrible crash when max is zero value
+ if (max === 0) {
+ // prevent horrible crash when max is zero value
console.warn('max param should be a non zero value');
return [];
}
@@ -59,8 +62,8 @@ export function valueAxisBuckets(max) {
if (max / Math.pow(10, magnitude) < 1.5) {
magnitude--;
}
- let skip = Math.pow(10, magnitude);
- let buckets = [];
+ const skip = Math.pow(10, magnitude);
+ const buckets = [];
let bucket = 0;
while (bucket <= max) {
buckets.push(bucket);
@@ -72,9 +75,12 @@ export function valueAxisBuckets(max) {
// Determines the x,y points on the chart for each data point
export function getDataPoints(chartWidth, chartHeight, timeBuckets, valueBuckets, dataPoints) {
- let xLength = timeBuckets[timeBuckets.length - 1] - timeBuckets[0];
- let yLength = valueBuckets[valueBuckets.length - 1] - valueBuckets[0];
+ const xLength = timeBuckets[timeBuckets.length - 1] - timeBuckets[0];
+ const yLength = valueBuckets[valueBuckets.length - 1] - valueBuckets[0];
return dataPoints.map(([x, y]) => {
- return [chartWidth * (x - timeBuckets[0]) / xLength, chartHeight - (chartHeight * y / yLength)];
+ return [
+ (chartWidth * (x - timeBuckets[0])) / xLength,
+ chartHeight - (chartHeight * y) / yLength,
+ ];
});
}
diff --git a/src/lib/ClassPreferences.js b/src/lib/ClassPreferences.js
index b36b1a5a7b..83e14f8327 100644
--- a/src/lib/ClassPreferences.js
+++ b/src/lib/ClassPreferences.js
@@ -1,5 +1,5 @@
const VERSION = 1; // In case we ever need to invalidate these
-let cache = {};
+const cache = {};
export function updatePreferences(prefs, appId, className) {
try {
localStorage.setItem(path(appId, className), JSON.stringify(prefs));
@@ -16,9 +16,11 @@ export function getPreferences(appId, className) {
}
let entry;
try {
- entry = localStorage.getItem(path(appId, className)) || JSON.stringify({
- filters: [],
- });
+ entry =
+ localStorage.getItem(path(appId, className)) ||
+ JSON.stringify({
+ filters: [],
+ });
} catch (e) {
// Fails in Safari private browsing
entry = null;
@@ -27,7 +29,7 @@ export function getPreferences(appId, className) {
return null;
}
try {
- let prefs = JSON.parse(entry);
+ const prefs = JSON.parse(entry);
cache[appId] = cache[appId] || {};
cache[appId][className] = prefs;
return prefs;
@@ -43,7 +45,7 @@ export function getAllPreferences(appId) {
const storageKeys = Object.keys(localStorage);
const result = {};
for (const key of storageKeys) {
- const split = key.split(':')
+ const split = key.split(':');
if (split.length <= 1 || split[2] !== appId) {
continue;
}
diff --git a/src/lib/ColumnPreferences.js b/src/lib/ColumnPreferences.js
index 78619dbe18..77c9d82648 100644
--- a/src/lib/ColumnPreferences.js
+++ b/src/lib/ColumnPreferences.js
@@ -9,7 +9,7 @@ const VERSION = 'v1'; // In case we ever need to invalidate these
const DEFAULT_WIDTH = 150;
const COLUMN_SORT = '__columnClassesSort'; // Used for storing classes sort field
const DEFAULT_COLUMN_SORT = '-createdAt'; // Default column sorting
-let cache = {};
+const cache = {};
export function updatePreferences(prefs, appId, className) {
try {
@@ -36,7 +36,7 @@ export function getPreferences(appId, className) {
return null;
}
try {
- let prefs = JSON.parse(entry);
+ const prefs = JSON.parse(entry);
cache[appId] = cache[appId] || {};
cache[appId][className] = prefs;
return prefs;
@@ -49,7 +49,7 @@ export function getAllPreferences(appId) {
const storageKeys = Object.keys(localStorage);
const result = {};
for (const key of storageKeys) {
- const split = key.split(':')
+ const split = key.split(':');
if (split.length <= 1) {
continue;
}
@@ -63,8 +63,10 @@ export function getAllPreferences(appId) {
}
export function getColumnSort(sortBy, appId, className) {
- let cachedSort = getPreferences(appId, COLUMN_SORT) || [ { name: className, value: DEFAULT_COLUMN_SORT } ];
- let ordering = [].concat(cachedSort);
+ const cachedSort = getPreferences(appId, COLUMN_SORT) || [
+ { name: className, value: DEFAULT_COLUMN_SORT },
+ ];
+ const ordering = [].concat(cachedSort);
let updated = false;
let missing = true;
let currentSort = sortBy ? sortBy : DEFAULT_COLUMN_SORT;
@@ -79,7 +81,7 @@ export function getColumnSort(sortBy, appId, className) {
}
}
}
- if(missing) {
+ if (missing) {
ordering.push({ name: className, value: currentSort });
}
if ((updated && sortBy) || missing) {
@@ -89,11 +91,11 @@ export function getColumnSort(sortBy, appId, className) {
}
export function getOrder(cols, appId, className, defaultPrefs) {
-
- let prefs = getPreferences(appId, className) || [ { name: 'objectId', width: DEFAULT_WIDTH, visible: true, cached: true } ];
+ let prefs = getPreferences(appId, className) || [
+ { name: 'objectId', width: DEFAULT_WIDTH, visible: true, cached: true },
+ ];
if (defaultPrefs) {
-
// Check that every default pref is in the prefs array.
defaultPrefs.forEach(defaultPrefsItem => {
// If the default pref is not in the prefs: Add it.
@@ -103,34 +105,41 @@ export function getOrder(cols, appId, className, defaultPrefs) {
});
// Iterate over the current prefs
- prefs = prefs.map((prefsItem) => {
+ prefs = prefs.map(prefsItem => {
// Get the default prefs item.
- const defaultPrefsItem = defaultPrefs.find(defaultPrefsItem => defaultPrefsItem.name === prefsItem.name) || {};
+ const defaultPrefsItem =
+ defaultPrefs.find(defaultPrefsItem => defaultPrefsItem.name === prefsItem.name) || {};
// The values from the prefsItem object will overwrite those from the defaultPrefsItem object.
return {
// Set default width if not given.
width: DEFAULT_WIDTH,
...defaultPrefsItem,
...prefsItem,
- }
+ };
});
}
- let order = [].concat(prefs);
- let seen = {};
+ const order = [].concat(prefs);
+ const seen = {};
for (let i = 0; i < order.length; i++) {
seen[order[i].name] = true;
}
- let requested = {};
+ const requested = {};
let updated = false;
- for (let name in cols) {
+ for (const name in cols) {
requested[name] = true;
if (!seen[name]) {
- order.push({ name: name, width: DEFAULT_WIDTH, visible: !defaultPrefs, required: cols[name]['required'], cached: !defaultPrefs });
+ order.push({
+ name: name,
+ width: DEFAULT_WIDTH,
+ visible: !defaultPrefs,
+ required: cols[name]['required'],
+ cached: !defaultPrefs,
+ });
seen[name] = true;
updated = true;
}
}
- let filtered = [];
+ const filtered = [];
for (let i = 0; i < order.length; i++) {
const { name, visible, required, cached } = order[i];
@@ -166,26 +175,26 @@ export function getOrder(cols, appId, className, defaultPrefs) {
}
export function updateCachedColumns(appId, className) {
- let prefs = getPreferences(appId, className);
- let order = [].concat(prefs);
+ const prefs = getPreferences(appId, className);
+ const order = [].concat(prefs);
- for (let col of order) {
- let { visible } = col;
+ for (const col of order) {
+ const { visible } = col;
col.cached = visible;
}
updatePreferences(order, appId, className);
return order;
}
-export function setPointerDefaultKey( appId, className, name ) {
+export function setPointerDefaultKey(appId, className, name) {
localStorage.setItem(pointerKeyPath(appId, className), name);
// remove old pointer key.
localStorage.removeItem(className);
}
-export function getPointerDefaultKey( appId, className ) {
+export function getPointerDefaultKey(appId, className) {
let pointerKey = localStorage.getItem(pointerKeyPath(appId, className));
- if ( !pointerKey ) {
+ if (!pointerKey) {
// old pointer key.
pointerKey = localStorage.getItem(className) || 'objectId';
}
@@ -196,6 +205,6 @@ function path(appId, className) {
return `ParseDashboard:${VERSION}:${appId}:${className}`;
}
-function pointerKeyPath( appId, className ) {
+function pointerKeyPath(appId, className) {
return `ParseDashboard:${VERSION}:${appId}:${className}::defaultPointerKey`;
}
diff --git a/src/lib/Constants.js b/src/lib/Constants.js
index 60615d957a..ff9e1bc53d 100644
--- a/src/lib/Constants.js
+++ b/src/lib/Constants.js
@@ -9,7 +9,7 @@ export const Anchors = {
TOP: 'TOP',
RIGHT: 'RIGHT',
BOTTOM: 'BOTTOM',
- LEFT: 'LEFT'
+ LEFT: 'LEFT',
};
export const AsyncStatus = {
@@ -17,7 +17,7 @@ export const AsyncStatus = {
PROGRESS: 'PROGRESS',
SUCCESS: 'SUCCESS',
FAILED: 'FAILED',
-}
+};
export const ChartColorSchemes = [
'#169cee',
@@ -31,7 +31,7 @@ export const ChartColorSchemes = [
'#15D0ED',
'#5858ED',
'#15EDC8',
- '#B515ED'
+ '#B515ED',
];
export const Directions = {
@@ -42,7 +42,7 @@ export const Directions = {
BOTTOM_RIGHT: 'BOTTOM_RIGHT',
DOWN: 'DOWN',
BOTTOM_LEFT: 'BOTTOM_LEFT',
- LEFT: 'LEFT'
+ LEFT: 'LEFT',
};
export const SpecialClasses = [
@@ -55,14 +55,36 @@ export const SpecialClasses = [
];
export const DefaultColumns = {
- All: [ 'objectId', 'ACL', 'createdAt', 'updatedAt' ],
+ All: ['objectId', 'ACL', 'createdAt', 'updatedAt'],
- _User: [ 'username', 'password', 'email', 'emailVerified', 'authData' ],
- _Installation: [ 'installationId', 'deviceToken', 'channels', 'deviceType', 'pushType', 'GCMSenderId', 'timeZone', 'localeIdentifier', 'badge' ],
- _Role: [ 'name', 'users', 'roles' ],
- _Product: [ 'order', 'productIdentifier', 'icon', 'title', 'subtitle', 'download', 'downloadName' ],
- _Session: [ 'restricted', 'user', 'installationId', 'sessionToken', 'expiresAt', 'createdWith' ],
- _PushStatus: [ 'pushTime', 'query', 'pushHash', 'payload', 'status', 'sentPerType', 'failedPerType', 'numSent', 'numFailed', 'errorMessage', 'source' ],
+ _User: ['username', 'password', 'email', 'emailVerified', 'authData'],
+ _Installation: [
+ 'installationId',
+ 'deviceToken',
+ 'channels',
+ 'deviceType',
+ 'pushType',
+ 'GCMSenderId',
+ 'timeZone',
+ 'localeIdentifier',
+ 'badge',
+ ],
+ _Role: ['name', 'users', 'roles'],
+ _Product: ['order', 'productIdentifier', 'icon', 'title', 'subtitle', 'download', 'downloadName'],
+ _Session: ['restricted', 'user', 'installationId', 'sessionToken', 'expiresAt', 'createdWith'],
+ _PushStatus: [
+ 'pushTime',
+ 'query',
+ 'pushHash',
+ 'payload',
+ 'status',
+ 'sentPerType',
+ 'failedPerType',
+ 'numSent',
+ 'numFailed',
+ 'errorMessage',
+ 'source',
+ ],
};
export const DataTypes = [
diff --git a/src/lib/DateUtils.js b/src/lib/DateUtils.js
index 0e405551ff..57163eafe0 100644
--- a/src/lib/DateUtils.js
+++ b/src/lib/DateUtils.js
@@ -17,7 +17,7 @@ export const MONTHS = [
'September',
'October',
'November',
- 'December'
+ 'December',
];
export const WEEKDAYS = [
@@ -27,13 +27,13 @@ export const WEEKDAYS = [
'Wednesday',
'Thursday',
'Friday',
- 'Saturday'
+ 'Saturday',
];
const toString = Object.prototype.toString;
export function isDate(obj) {
- return typeof(obj) === 'object' && toString.call(obj).indexOf('Date') > -1;
+ return typeof obj === 'object' && toString.call(obj).indexOf('Date') > -1;
}
export function getWeekday(n) {
@@ -59,24 +59,16 @@ export function shortMonth(month) {
}
export function nextMonth(date) {
- return new Date(
- date.getFullYear(),
- date.getMonth() + 1,
- 1
- );
+ return new Date(date.getFullYear(), date.getMonth() + 1, 1);
}
export function prevMonth(date) {
- return new Date(
- date.getFullYear(),
- date.getMonth() - 1,
- 1
- );
+ return new Date(date.getFullYear(), date.getMonth() - 1, 1);
}
export function daysInMonth(date) {
- let next = nextMonth(date);
- let lastDay = new Date(next.getFullYear(), next.getMonth(), next.getDate() - 1);
+ const next = nextMonth(date);
+ const lastDay = new Date(next.getFullYear(), next.getMonth(), next.getDate() - 1);
return lastDay.getDate();
}
@@ -111,15 +103,19 @@ export function monthsFrom(date, delta) {
}
export function dateStringUTC(date) {
- let full = String(date.getUTCDate()) + ' ' +
- shortMonth(date.getUTCMonth()) + ' ' +
- String(date.getUTCFullYear()) + ' at ';
- let time = {
+ let full =
+ String(date.getUTCDate()) +
+ ' ' +
+ shortMonth(date.getUTCMonth()) +
+ ' ' +
+ String(date.getUTCFullYear()) +
+ ' at ';
+ const time = {
hours: String(date.getUTCHours()),
minutes: String(date.getUTCMinutes()),
- seconds: String(date.getUTCSeconds())
+ seconds: String(date.getUTCSeconds()),
};
- for (let k in time) {
+ for (const k in time) {
if (time[k].length < 2) {
time[k] = '0' + time[k];
}
@@ -138,11 +134,22 @@ export function monthDayStringUTC(date) {
* @return {String}
*/
export function yearMonthDayFormatter(date) {
- return date.toLocaleDateString('en-US', {year: 'numeric', month: 'short', day: 'numeric'});
+ return date.toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
}
export function yearMonthDayTimeFormatter(date, timeZone) {
- let options = {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: false};
+ const options = {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ hour12: false,
+ };
if (timeZone) {
options.timeZoneName = 'short';
}
@@ -151,7 +158,7 @@ export function yearMonthDayTimeFormatter(date, timeZone) {
export function getDateMethod(local, methodName) {
if (!local) {
- return methodName.replace('get','getUTC');
+ return methodName.replace('get', 'getUTC');
} else {
return methodName;
}
diff --git a/src/lib/Email.js b/src/lib/Email.js
index c3b7cd1fbc..5fa5e58b82 100644
--- a/src/lib/Email.js
+++ b/src/lib/Email.js
@@ -48,10 +48,10 @@ const domains = [
const domainMap = {};
for (let i = domains.length; i--;) {
- let levels = domains[i].split('.');
- let secondLevel = levels.shift();
- let tld = levels.join('.');
- let pairs = domainMap[secondLevel];
+ const levels = domains[i].split('.');
+ const secondLevel = levels.shift();
+ const tld = levels.join('.');
+ const pairs = domainMap[secondLevel];
if (!pairs) {
domainMap[secondLevel] = [tld];
} else {
@@ -70,9 +70,9 @@ export function dist(a, b) {
return a.length;
}
- let vectors = [];
+ const vectors = [];
for (let i = 0; i <= a.length; i++) {
- let row = [];
+ const row = [];
for (let j = 0; j <= b.length; j++) {
row[j] = 0;
}
@@ -107,16 +107,16 @@ export const emailRegex = /^([^@<>\s]+)@([^@<>\s]{2,}\.[^@<>\s]{2,})$/;
// If we think there's a typo, return a string suggesting a correction
// Return null if we have no suggestion
export function suggestion(email, checkTLD) {
- let match = email.match(emailRegex);
+ const match = email.match(emailRegex);
if (!match) {
return null;
}
- let emailDomain = match[2].toLowerCase();
- let emailSecondLevel = emailDomain.split('.').shift();
+ const emailDomain = match[2].toLowerCase();
+ const emailSecondLevel = emailDomain.split('.').shift();
let closestDistance = Infinity;
let closestDomain = '';
- let secondLevelDomains = Object.keys(domainMap);
+ const secondLevelDomains = Object.keys(domainMap);
let i;
let d;
for (i = secondLevelDomains.length; i--;) {
@@ -132,12 +132,12 @@ export function suggestion(email, checkTLD) {
if (closestDistance > 3 || (!checkTLD && closestDistance === 0)) {
return null;
}
- let selectedDomain = closestDomain;
+ const selectedDomain = closestDomain;
closestDistance = Infinity;
- let tlds = domainMap[selectedDomain];
+ const tlds = domainMap[selectedDomain];
for (i = tlds.length; i--;) {
- let joined = selectedDomain + '.' + tlds[i];
+ const joined = selectedDomain + '.' + tlds[i];
d = dist(emailDomain, joined);
if (d < closestDistance) {
closestDistance = d;
diff --git a/src/lib/Filters.js b/src/lib/Filters.js
index c53e41a008..60533a9c68 100644
--- a/src/lib/Filters.js
+++ b/src/lib/Filters.js
@@ -11,167 +11,167 @@ export const Constraints = {
exists: {
name: 'exists',
field: null,
- comparable: false
+ comparable: false,
},
dne: {
name: 'does not exist',
field: null,
- comparable: false
+ comparable: false,
},
eq: {
name: 'equals',
- comparable: true
+ comparable: true,
},
neq: {
name: 'does not equal',
- comparable: true
+ comparable: true,
},
lt: {
name: 'less than',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
lte: {
name: 'less than or equal',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
gt: {
name: 'greater than',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
gte: {
name: 'greater than or equal',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
starts: {
name: 'starts with',
- comparable: true
+ comparable: true,
},
ends: {
name: 'ends with',
- comparable: true
+ comparable: true,
},
stringContainsString: {
name: 'string contains string',
field: 'String',
composable: true,
- comparable: true
+ comparable: true,
},
before: {
name: 'is before',
field: 'Date',
composable: true,
- comparable: true
+ comparable: true,
},
after: {
name: 'is after',
field: 'Date',
composable: true,
- comparable: true
+ comparable: true,
},
containsString: {
name: 'contains string',
field: 'String',
composable: true,
- comparable: true
+ comparable: true,
},
doesNotContainString: {
name: 'without string',
field: 'String',
composable: true,
- comparable: true
+ comparable: true,
},
containsNumber: {
name: 'contains number',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
doesNotContainNumber: {
name: 'without number',
field: 'Number',
composable: true,
- comparable: true
+ comparable: true,
},
containsAny: {
name: 'contains',
field: 'Array',
- comparable: true
+ comparable: true,
},
doesNotContainAny: {
name: 'does not contain',
field: 'Array',
- comparable: true
+ comparable: true,
},
keyExists: {
name: 'key exists',
field: 'Object',
composable: true,
- comparable: false
+ comparable: false,
},
keyDne: {
name: 'key does not exist',
field: 'Object',
composable: true,
- comparable: false
+ comparable: false,
},
keyEq: {
name: 'key equals',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
keyNeq: {
name: 'key does not equal',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
keyGt: {
name: 'key greater than',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
keyGte: {
name: 'key greater than/equal',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
keyLt: {
name: 'key less than',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
keyLte: {
name: 'key less than/equal',
field: 'Object',
composable: true,
- comparable: true
+ comparable: true,
},
unique: {
name: 'unique',
field: null,
- comparable: false
+ comparable: false,
},
};
export const FieldConstraints = {
- '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': [
+ 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',
'keyExists',
@@ -184,7 +184,7 @@ export const FieldConstraints = {
'keyLte',
'unique',
],
- 'Array': [
+ Array: [
'exists',
'dne',
'containsString',
@@ -193,16 +193,16 @@ export const FieldConstraints = {
'doesNotContainNumber',
'containsAny',
'doesNotContainAny',
- ]
+ ],
};
export const DefaultComparisons = {
- 'Pointer': '',
- 'Boolean': false,
- 'Number': '',
- 'String': '',
- 'Object': '',
- 'Date': Parse._encode(new Date()),
+ Pointer: '',
+ Boolean: false,
+ Number: '',
+ String: '',
+ Object: '',
+ Date: Parse._encode(new Date()),
};
// Given a class schema and an array of current filters, this returns the remaining available filters
@@ -211,26 +211,26 @@ export const DefaultComparisons = {
// blacklist is an optional array of constraints to ignore
export function availableFilters(schema, currentFilters, blacklist) {
blacklist = blacklist || [];
- let disabled = {};
+ const disabled = {};
if (currentFilters) {
- currentFilters.forEach((filter) => {
+ currentFilters.forEach(filter => {
if (!Constraints[filter.get('constraint')].composable) {
disabled[filter.get('field')] = true;
}
});
}
- let available = {};
- for (let col in schema) {
+ const available = {};
+ for (const col in schema) {
if (disabled[col]) {
continue;
}
- let type = schema[col].type;
+ const type = schema[col].type;
if (!FieldConstraints[type]) {
continue;
}
- available[col] = FieldConstraints[type].filter((c) => blacklist.indexOf(c) < 0);
+ available[col] = FieldConstraints[type].filter(c => blacklist.indexOf(c) < 0);
}
return available;
}
-export const BLACKLISTED_FILTERS = [ 'containsAny', 'doesNotContainAny' ];
+export const BLACKLISTED_FILTERS = ['containsAny', 'doesNotContainAny'];
diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js
index b71d14dc48..fdbdc196aa 100644
--- a/src/lib/ParseApp.js
+++ b/src/lib/ParseApp.js
@@ -5,17 +5,17 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import * as AJAX from 'lib/AJAX';
+import * as AJAX from 'lib/AJAX';
import encodeFormData from 'lib/encodeFormData';
-import Parse from 'parse';
+import Parse from 'parse';
import { updatePreferences, getPreferences } from 'lib/ClassPreferences';
function setEnablePushSource(setting, enable) {
- let path = `/apps/${this.slug}/update_push_notifications`;
- let attr = `parse_app[${setting}]`;
- let body = {};
+ const path = `/apps/${this.slug}/update_push_notifications`;
+ const attr = `parse_app[${setting}]`;
+ const body = {};
body[attr] = enable ? 'true' : 'false';
- let promise = AJAX.put(path, body);
+ const promise = AJAX.put(path, body);
promise.then(() => {
this.settings.fields.fields[setting] = enable;
});
@@ -46,14 +46,16 @@ export default class ParseApp {
preventSchemaEdits,
graphQLServerURL,
columnPreference,
- classPreference
+ scripts,
+ classPreference,
+ enableSecurityChecks
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
this.applicationId = appId;
this.slug = appNameForURL || appName;
if (!this.slug && dashboardURL) {
- let pieces = dashboardURL.split('/');
+ const pieces = dashboardURL.split('/');
this.slug = pieces[pieces.length - 1];
}
this.clientKey = clientKey;
@@ -62,41 +64,47 @@ export default class ParseApp {
this.restKey = restKey;
this.windowsKey = windowsKey;
this.webhookKey = webhookKey;
- this.fileKey = apiKey;
+ this.fileKey = apiKey;
this.production = production;
this.serverURL = serverURL;
this.serverInfo = serverInfo;
this.icon = iconName;
- this.primaryBackgroundColor=primaryBackgroundColor;
- this.secondaryBackgroundColor=secondaryBackgroundColor;
+ this.primaryBackgroundColor = primaryBackgroundColor;
+ this.secondaryBackgroundColor = secondaryBackgroundColor;
this.supportedPushLocales = supportedPushLocales ? supportedPushLocales : [];
this.preventSchemaEdits = preventSchemaEdits || false;
this.graphQLServerURL = graphQLServerURL;
this.columnPreference = columnPreference;
-
- if(!supportedPushLocales) {
- console.warn('Missing push locales for \'' + appName + '\', see this link for details on setting localizations up. https://github.com/parse-community/parse-dashboard#configuring-localized-push-notifications');
+ this.scripts = scripts;
+ this.enableSecurityChecks = !!enableSecurityChecks;
+
+ if (!supportedPushLocales) {
+ console.warn(
+ 'Missing push locales for \'' +
+ appName +
+ '\', see this link for details on setting localizations up. https://github.com/parse-community/parse-dashboard#configuring-localized-push-notifications'
+ );
}
this.settings = {
fields: {},
- lastFetched: new Date(0)
+ lastFetched: new Date(0),
};
this.latestRelease = {
release: null,
- lastFetched: new Date(0)
+ lastFetched: new Date(0),
};
this.jobStatus = {
status: null,
- lastFetched: new Date(0)
+ lastFetched: new Date(0),
};
this.classCounts = {
counts: {},
lastFetched: {},
- }
+ };
this.hasCheckedForMigraton = false;
@@ -134,7 +142,11 @@ export default class ParseApp {
* since - only fetch lines since this Date
*/
getLogs(level, since) {
- let path = 'scriptlog?level=' + encodeURIComponent(level.toLowerCase()) + '&n=100' + (since?'&startDate=' + encodeURIComponent(since.getTime()):'');
+ const path =
+ 'scriptlog?level=' +
+ encodeURIComponent(level.toLowerCase()) +
+ '&n=100' +
+ (since ? '&startDate=' + encodeURIComponent(since.getTime()) : '');
return this.apiRequest('GET', path, {}, { useMasterKey: true });
}
@@ -143,29 +155,33 @@ export default class ParseApp {
* fileName - the name of the file to be fetched
*/
getSource(fileName) {
- return this.getLatestRelease().then((release) => {
- if (release.files === null) {
- // No release yet
- return Promise.resolve(null);
- }
+ return this.getLatestRelease()
+ .then(release => {
+ if (release.files === null) {
+ // No release yet
+ return Promise.resolve(null);
+ }
- let fileMetaData = release.files[fileName];
- if (fileMetaData && fileMetaData.source) {
- return Promise.resolve(fileMetaData.source);
- }
+ const fileMetaData = release.files[fileName];
+ if (fileMetaData && fileMetaData.source) {
+ return Promise.resolve(fileMetaData.source);
+ }
- let params = {
- version: fileMetaData.version,
- checksum: fileMetaData.checksum
- }
- return this.apiRequest('GET', `scripts/${fileName}`, params, { useMasterKey: true });
- }).then((source) => {
- if (this.latestRelease.files) {
- this.latestRelease.files[fileName].source = source;
- }
+ const params = {
+ version: fileMetaData.version,
+ checksum: fileMetaData.checksum,
+ };
+ return this.apiRequest('GET', `scripts/${fileName}`, params, {
+ useMasterKey: true,
+ });
+ })
+ .then(source => {
+ if (this.latestRelease.files) {
+ this.latestRelease.files[fileName].source = source;
+ }
- return Promise.resolve(source);
- });
+ return Promise.resolve(source);
+ });
}
getLatestRelease() {
@@ -173,24 +189,19 @@ export default class ParseApp {
if (new Date() - this.latestRelease.lastFetched < 60000) {
return Promise.resolve(this.latestRelease);
}
- return this.apiRequest(
- 'GET',
- 'releases/latest',
- {},
- { useMasterKey: true }
- ).then((release) => {
+ return this.apiRequest('GET', 'releases/latest', {}, { useMasterKey: true }).then(release => {
this.latestRelease.lastFetched = new Date();
this.latestRelease.files = null;
if (release.length === 0) {
this.latestRelease.release = null;
} else {
- let latestRelease = release[0];
+ const latestRelease = release[0];
this.latestRelease.release = {
version: latestRelease.version,
parseVersion: latestRelease.parseVersion,
- deployedAt: new Date(latestRelease.timestamp)
+ deployedAt: new Date(latestRelease.timestamp),
};
let checksums = JSON.parse(latestRelease.checksums);
@@ -204,11 +215,11 @@ export default class ParseApp {
if (versions.cloud) {
versions = versions.cloud;
}
- for (let c in checksums) {
+ for (const c in checksums) {
this.latestRelease.files[c] = {
checksum: checksums[c],
version: versions[c],
- source: null
+ source: null,
};
}
}
@@ -225,17 +236,17 @@ export default class ParseApp {
return Promise.resolve(this.classCounts.counts[className]);
}
}
- let p = new Parse.Query(className).count({ useMasterKey: true });
+ const p = new Parse.Query(className).count({ useMasterKey: true });
p.then(count => {
this.classCounts.counts[className] = count;
this.classCounts.lastFetched[className] = new Date();
- })
+ });
return p;
}
getRelationCount(relation) {
this.setParseKeys();
- let p = relation.query().count({ useMasterKey: true });
+ const p = relation.query().count({ useMasterKey: true });
return p;
}
@@ -246,7 +257,7 @@ export default class ParseApp {
getAnalyticsOverview(time) {
time = Math.round(time.getTime() / 1000);
- let audiencePromises = [
+ const audiencePromises = [
'daily_users',
'weekly_users',
'monthly_users',
@@ -254,101 +265,116 @@ export default class ParseApp {
'daily_installations',
'weekly_installations',
'monthly_installations',
- 'total_installations'
- ].map((activity) => {
- let { xhr, promise } = AJAX.abortableGet('/apps/' + this.slug + '/analytics_content_audience?at=' + time + '&audienceType=' + activity);
- promise = promise.then((result) => (
+ 'total_installations',
+ ].map(activity => {
+ const res = AJAX.abortableGet(
+ '/apps/' +
+ this.slug +
+ '/analytics_content_audience?at=' +
+ time +
+ '&audienceType=' +
+ activity
+ );
+ let promise = res.promise;
+ const xhr = res.xhr;
+ promise = promise.then(result =>
result.total === undefined ? result.content : result.total
- ));
- return { xhr, promise }
+ );
+ return { xhr, promise };
});
- let billingPromises = [
+ const billingPromises = [
'billing_file_storage',
'billing_database_storage',
- 'billing_data_transfer'
- ].map((billing) => (
- AJAX.abortableGet('/apps/' + this.slug + '/' + billing)
- ));
+ 'billing_data_transfer',
+ ].map(billing => AJAX.abortableGet('/apps/' + this.slug + '/' + billing));
- let allPromises = audiencePromises.concat(billingPromises);
+ const allPromises = audiencePromises.concat(billingPromises);
return {
- 'dailyActiveUsers': allPromises[0],
- 'weeklyActiveUsers': allPromises[1],
- 'monthlyActiveUsers': allPromises[2],
- 'totalUsers': allPromises[3],
- 'dailyActiveInstallations': allPromises[4],
- 'weeklyActiveInstallations': allPromises[5],
- 'monthlyActiveInstallations': allPromises[6],
- 'totalInstallations': allPromises[7],
- 'billingFileStorage': allPromises[8],
- 'billingDatabasetorage': allPromises[9],
- 'billingDataTransfer': allPromises[10]
+ dailyActiveUsers: allPromises[0],
+ weeklyActiveUsers: allPromises[1],
+ monthlyActiveUsers: allPromises[2],
+ totalUsers: allPromises[3],
+ dailyActiveInstallations: allPromises[4],
+ weeklyActiveInstallations: allPromises[5],
+ monthlyActiveInstallations: allPromises[6],
+ totalInstallations: allPromises[7],
+ billingFileStorage: allPromises[8],
+ billingDatabasetorage: allPromises[9],
+ billingDataTransfer: allPromises[10],
};
}
getAnalyticsTimeSeries(query) {
- let path = '/apps/' + this.slug + '/analytics?' + encodeFormData(null, query);
- let { promise, xhr } = AJAX.abortableGet(path);
+ const path = '/apps/' + this.slug + '/analytics?' + encodeFormData(null, query);
+ const res = AJAX.abortableGet(path);
+ let promise = res.promise;
+ const xhr = res.xhr;
promise = promise.then(({ requested_data }) => requested_data);
return { promise, xhr };
}
getAnalyticsSlowQueries(className, os, version, from, to) {
- let path = '/apps/' + this.slug + '/slow_queries?' + encodeFormData(null, {
- className: className || '',
- os: os || '',
- version: version || '',
- from: from.getTime() / 1000,
- to: to.getTime() / 1000
- });
- let { promise, xhr } = AJAX.abortableGet(path);
+ const path =
+ '/apps/' +
+ this.slug +
+ '/slow_queries?' +
+ encodeFormData(null, {
+ className: className || '',
+ os: os || '',
+ version: version || '',
+ from: from.getTime() / 1000,
+ to: to.getTime() / 1000,
+ });
+ const res = AJAX.abortableGet(path);
+ let promise = res.promise;
+ const xhr = res.xhr;
promise = promise.then(({ result }) => result);
return { promise, xhr };
}
getAppleCerts() {
- let path = '/apps/' + this.slug + '/apple_certificates';
+ const path = '/apps/' + this.slug + '/apple_certificates';
return AJAX.get(path).then(({ certs }) => certs);
}
uploadAppleCert(file) {
- let path = '/apps/' + this.slug + '/dashboard_ajax/push_certificate';
- let data = new FormData();
+ const path = '/apps/' + this.slug + '/dashboard_ajax/push_certificate';
+ const data = new FormData();
data.append('new_apple_certificate', file);
return AJAX.post(path, data).then(({ cert }) => cert);
}
deleteAppleCert(id) {
- let path = '/apps/' + this.slug + '/apple_certificates/' + id;
+ const path = '/apps/' + this.slug + '/apple_certificates/' + id;
return AJAX.del(path);
}
uploadSSLPublicCertificate(file) {
- let path = '/apps/' + this.slug + '/update_hosting_certificates';
- let data= new FormData();
+ const path = '/apps/' + this.slug + '/update_hosting_certificates';
+ const data = new FormData();
data.append('new_hosting_certificate[certificate_data]', file);
return AJAX.put(path, data);
}
uploadSSLPrivateKey(file) {
- let path = '/apps/' + this.slug + '/update_hosting_certificates';
- let data= new FormData();
+ const path = '/apps/' + this.slug + '/update_hosting_certificates';
+ const data = new FormData();
data.append('new_hosting_certificate[key_data]', file);
return AJAX.put(path, data);
}
saveSettingsFields(fields) {
- let path = '/apps/' + this.slug;
- let appFields = {};
- for (let f in fields) {
+ const path = '/apps/' + this.slug;
+ const appFields = {};
+ for (const f in fields) {
appFields['parse_app[' + f + ']'] = fields[f];
}
- let promise = AJAX.put(path, appFields);
+ const promise = AJAX.put(path, appFields);
promise.then(({ successes }) => {
- for (let f in fields) {
+ for (const f in fields) {
this.settings.fields[f] = successes[f];
}
});
@@ -360,9 +386,9 @@ export default class ParseApp {
if (new Date() - this.settings.lastFetched < 60000) {
return Promise.resolve(this.settings.fields);
}
- let path = '/apps/' + this.slug + '/dashboard_ajax/settings';
- return AJAX.get(path).then((fields) => {
- for (let f in fields) {
+ const path = '/apps/' + this.slug + '/dashboard_ajax/settings';
+ return AJAX.get(path).then(fields => {
+ for (const f in fields) {
this.settings.fields[f] = fields[f];
this.settings.lastFetched = new Date();
}
@@ -371,21 +397,20 @@ export default class ParseApp {
}
cleanUpFiles() {
- let path = '/apps/' + this.slug + '/cleanup_files';
+ const path = '/apps/' + this.slug + '/cleanup_files';
return AJAX.put(path);
}
exportData() {
- let path = '/apps/' + this.slug + '/export_data';
+ const path = '/apps/' + this.slug + '/export_data';
return AJAX.put(path);
}
resetMasterKey(password) {
- let path = '/apps/' + this.slug + '/reset_master_key';
- return AJAX.post(
- path,
- { password_confirm_reset_master_key: password }
- ).then(({ new_key }) => {
+ const path = '/apps/' + this.slug + '/reset_master_key';
+ return AJAX.post(path, {
+ password_confirm_reset_master_key: password,
+ }).then(({ new_key }) => {
this.masterKey = new_key;
return Promise.resolve();
});
@@ -393,16 +418,17 @@ export default class ParseApp {
clearCollection(className) {
if (this.serverInfo.parseServerVersion == 'Parse.com') {
- let path = `/apps/${this.slug}/collections/${className}/clear`;
+ const path = `/apps/${this.slug}/collections/${className}/clear`;
return AJAX.del(path);
} else {
- let path = `purge/${className}`;
+ const path = `purge/${className}`;
return this.apiRequest('DELETE', path, {}, { useMasterKey: true });
}
}
validateCollaborator(email) {
- let path = '/apps/' + this.slug + '/collaborations/validate?email=' + encodeURIComponent(email);
+ const path =
+ '/apps/' + this.slug + '/collaborations/validate?email=' + encodeURIComponent(email);
return AJAX.get(path);
}
@@ -412,35 +438,44 @@ export default class ParseApp {
query = {};
}
if (!query) {
- promise = new Parse.Query('_Audience').get(audienceId, { useMasterKey: true }).then(function(audience) {
- return Parse.Query.fromJSON('_Installation', { where: audience.get('query') }).count({ useMasterKey: true })
- });
+ promise = new Parse.Query('_Audience')
+ .get(audienceId, { useMasterKey: true })
+ .then(function (audience) {
+ return Parse.Query.fromJSON('_Installation', {
+ where: audience.get('query'),
+ }).count({ useMasterKey: true });
+ });
} else {
- promise = Parse.Query.fromJSON('_Installation', { where: query }).count({ useMasterKey: true })
+ promise = Parse.Query.fromJSON('_Installation', { where: query }).count({
+ useMasterKey: true,
+ });
}
- return { xhr: undefined, promise: promise.then(function (count) {
- return { count: count };
- }) };
+ return {
+ xhr: undefined,
+ promise: promise.then(function (count) {
+ return { count: count };
+ }),
+ };
}
fetchPushNotifications(type, page, limit) {
- let query = new Parse.Query('_PushStatus');
+ const query = new Parse.Query('_PushStatus');
if (type != 'all') {
query.equalTo('source', type || 'rest');
}
- query.skip(page*limit);
+ query.skip(page * limit);
query.limit(limit);
query.descending('createdAt');
return query.find({ useMasterKey: true });
}
fetchPushAudienceSizeSuggestion() {
- let path = '/apps/' + this.slug + '/push_notifications/audience_size_suggestion';
+ const path = '/apps/' + this.slug + '/push_notifications/audience_size_suggestion';
return AJAX.get(path);
}
fetchPushDetails(objectId) {
- let query = new Parse.Query('_PushStatus');
+ const query = new Parse.Query('_PushStatus');
query.equalTo('objectId', objectId);
return query.first({ useMasterKey: true });
}
@@ -457,31 +492,33 @@ export default class ParseApp {
let path = '/apps/' + this.slug + '/push_subscriber_translation_count';
let urlsSeparator = '?';
path += `?where=${encodeURI(JSON.stringify(where || {}))}`;
- path += `&locales=${encodeURI(JSON.stringify(locales))}`
+ path += `&locales=${encodeURI(JSON.stringify(locales))}`;
urlsSeparator = '&';
return AJAX.abortableGet(audienceId ? `${path}${urlsSeparator}audienceId=${audienceId}` : path);
}
fetchAvailableDevices() {
- let path = '/apps/' + this.slug + '/dashboard_ajax/available_devices';
+ const path = '/apps/' + this.slug + '/dashboard_ajax/available_devices';
return AJAX.get(path);
}
removeCollaboratorById(id) {
- let path = '/apps/' + this.slug + '/collaborations/' + id.toString();
- let promise = AJAX.del(path)
+ const path = '/apps/' + this.slug + '/collaborations/' + id.toString();
+ const promise = AJAX.del(path);
promise.then(() => {
//TODO: this currently works because everything that uses collaborators
// happens to re-render after this call anyway, but really the collaborators
// should be updated properly in a store or AppsManager or something
- this.settings.fields.fields.collaborators = this.settings.fields.fields.collaborators.filter(c => c.id != id);
+ this.settings.fields.fields.collaborators = this.settings.fields.fields.collaborators.filter(
+ c => c.id != id
+ );
});
return promise;
}
addCollaborator(email) {
- let path = '/apps/' + this.slug + '/collaborations';
- let promise = AJAX.post(path, {'collaboration[email]': email});
+ const path = '/apps/' + this.slug + '/collaborations';
+ const promise = AJAX.post(path, { 'collaboration[email]': email });
promise.then(({ data }) => {
//TODO: this currently works because everything that uses collaborators
// happens to re-render after this call anyway, but really the collaborators
@@ -492,8 +529,8 @@ export default class ParseApp {
}
setRequestLimit(limit) {
- let path = '/plans/' + this.slug + '?new_limit=' + limit.toString();
- let promise = AJAX.put(path);
+ const path = '/plans/' + this.slug + '?new_limit=' + limit.toString();
+ const promise = AJAX.put(path);
promise.then(() => {
this.settings.fields.fields.pricing_plan.request_limit = limit;
});
@@ -501,8 +538,8 @@ export default class ParseApp {
}
setAppName(name) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[name]': name});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, { 'parse_app[name]': name });
promise.then(() => {
this.name = name;
});
@@ -510,17 +547,21 @@ export default class ParseApp {
}
setAppStoreURL(type, url) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {['parse_app[parse_app_metadata][url][' + type + ']']: url});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ ['parse_app[parse_app_metadata][url][' + type + ']']: url,
+ });
promise.then(() => {
- this.settings.fields.fields.urls.unshift({platform: type, url: url});
+ this.settings.fields.fields.urls.unshift({ platform: type, url: url });
});
return promise;
}
setInProduction(inProduction) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[parse_app_metadata][production]': inProduction ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[parse_app_metadata][production]': inProduction ? 'true' : 'false',
+ });
promise.then(() => {
this.production = inProduction;
});
@@ -528,7 +569,7 @@ export default class ParseApp {
}
launchExperiment(objectId, formData) {
- let path = `/apps/${this.slug}/push_notifications/${objectId}/launch_experiment`;
+ const path = `/apps/${this.slug}/push_notifications/${objectId}/launch_experiment`;
return AJAX.post(path, formData);
}
@@ -536,30 +577,30 @@ export default class ParseApp {
if (!where) {
where = {};
}
- let path = '/apps/' + this.slug + '/export_data';
+ const path = '/apps/' + this.slug + '/export_data';
return AJAX.put(path, { name: className, where: where });
}
getExportProgress() {
- let path = '/apps/' + this.slug + '/export_progress';
+ const path = '/apps/' + this.slug + '/export_progress';
return AJAX.get(path);
}
getAvailableJobs() {
- let path = 'cloud_code/jobs/data';
+ const path = 'cloud_code/jobs/data';
return this.apiRequest('GET', path, {}, { useMasterKey: true });
}
getJobStatus() {
- let query = new Parse.Query('_JobStatus');
+ const query = new Parse.Query('_JobStatus');
query.descending('createdAt');
- return query.find({ useMasterKey: true }).then((status) => {
- status = status.map((jobStatus) => {
+ return query.find({ useMasterKey: true }).then(status => {
+ status = status.map(jobStatus => {
return jobStatus.toJSON();
});
this.jobStatus = {
status: status || null,
- lastFetched: new Date()
+ lastFetched: new Date(),
};
return status;
});
@@ -573,31 +614,33 @@ export default class ParseApp {
description: 'Executing from job schedule web console.',
input: JSON.parse(job.params || '{}'),
jobName: job.jobName,
- when: 0
+ when: 0,
},
{ useMasterKey: true }
);
}
getMigrations() {
- let path = '/apps/' + this.slug + '/migrations';
- let obj = AJAX.abortableGet(path);
- this.hasCheckedForMigraton = true
- obj.promise.then(({ migration }) => {
- this.migration = migration;
- }).catch(() => {}); // swallow errors
+ const path = '/apps/' + this.slug + '/migrations';
+ const obj = AJAX.abortableGet(path);
+ this.hasCheckedForMigraton = true;
+ obj.promise
+ .then(({ migration }) => {
+ this.migration = migration;
+ })
+ .catch(() => {}); // swallow errors
return obj;
}
beginMigration(connectionString) {
this.hasCheckedForMigraton = false;
- let path = '/apps/' + this.slug + '/migrations';
- return AJAX.post(path, {connection_string: connectionString});
+ const path = '/apps/' + this.slug + '/migrations';
+ return AJAX.post(path, { connection_string: connectionString });
}
changeConnectionString(newConnectionString) {
- let path = '/apps/' + this.slug + '/change_connection_string';
- let promise = AJAX.post(path, {connection_string: newConnectionString});
+ const path = '/apps/' + this.slug + '/change_connection_string';
+ const promise = AJAX.post(path, { connection_string: newConnectionString });
promise.then(() => {
this.settings.fields.fields.opendb_connection_string = newConnectionString;
});
@@ -606,20 +649,22 @@ export default class ParseApp {
stopMigration() {
//We will need to pass the real ID here if we decide to have migrations deletable by id. For now, from the users point of view, there is only one migration per app.
- let path = '/apps/' + this.slug + '/migrations/0';
+ const path = '/apps/' + this.slug + '/migrations/0';
return AJAX.del(path);
}
commitMigration() {
//Migration IDs are not to be exposed, so pass 0 as ID and let rails fetch the correct ID
- let path = '/apps/' + this.slug + '/migrations/0/commit';
+ const path = '/apps/' + this.slug + '/migrations/0/commit';
//No need to update anything, UI will autorefresh once request goes through and mowgli enters FINISH/DONE state
return AJAX.post(path);
}
setRequireRevocableSessions(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[require_revocable_session]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[require_revocable_session]': require ? 'true' : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -630,8 +675,10 @@ export default class ParseApp {
}
setExpireInactiveSessions(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[expire_revocable_session]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[expire_revocable_session]': require ? 'true' : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -642,8 +689,10 @@ export default class ParseApp {
}
setRevokeSessionOnPasswordChange(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[revoke_on_password_reset]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[revoke_on_password_reset]': require ? 'true' : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -654,8 +703,10 @@ export default class ParseApp {
}
setEnableNewMethodsByDefault(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[auth_options_attributes][_enable_by_default_as_bool]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][_enable_by_default_as_bool]': require ? 'true' : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -666,8 +717,12 @@ export default class ParseApp {
}
setAllowUsernameAndPassword(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[auth_options_attributes][username_attributes][enabled_as_bool]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][username_attributes][enabled_as_bool]': require
+ ? 'true'
+ : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -678,8 +733,12 @@ export default class ParseApp {
}
setAllowAnonymousUsers(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[auth_options_attributes][anonymous_attributes][enabled_as_bool]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][anonymous_attributes][enabled_as_bool]': require
+ ? 'true'
+ : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -690,8 +749,12 @@ export default class ParseApp {
}
setAllowCustomAuthentication(require) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {'parse_app[auth_options_attributes][custom_attributes][enabled_as_bool]': require ? 'true' : 'false'});
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][custom_attributes][enabled_as_bool]': require
+ ? 'true'
+ : 'false',
+ });
promise.then(() => {
//TODO: this currently works because everything that uses this
// happens to re-render after this call anyway, but really this
@@ -702,10 +765,11 @@ export default class ParseApp {
}
setConnectedFacebookApps(idList, secretList) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
'parse_app[auth_options_attributes][facebook_attributes][app_ids_as_list]': idList.join(','),
- 'parse_app[auth_options_attributes][facebook_attributes][app_secrets_as_list]': secretList.join(','),
+ 'parse_app[auth_options_attributes][facebook_attributes][app_secrets_as_list]':
+ secretList.join(','),
});
promise.then(() => {
this.settings.fields.fields.auth_options_attributes.facebook.app_ids = idList;
@@ -715,15 +779,21 @@ export default class ParseApp {
}
addConnectedFacebookApp(newId, newSecret) {
- let allIds = (this.settings.fields.fields.auth_options_attributes.facebook.app_ids || []).concat(newId);
- let allSecrets = (this.settings.fields.fields.auth_options_attributes.facebook.app_secrets || []).concat(newSecret);
+ const allIds = (
+ this.settings.fields.fields.auth_options_attributes.facebook.app_ids || []
+ ).concat(newId);
+ const allSecrets = (
+ this.settings.fields.fields.auth_options_attributes.facebook.app_secrets || []
+ ).concat(newSecret);
return this.setConnectedFacebookApps(allIds, allSecrets);
}
setAllowFacebookAuth(enable) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {
- 'parse_app[auth_options_attributes][facebook_attributes][enabled_as_bool]': enable ? 'true' : 'false',
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][facebook_attributes][enabled_as_bool]': enable
+ ? 'true'
+ : 'false',
});
promise.then(() => {
this.settings.fields.fields.auth_options_attributes.facebook.enabled = !!enable;
@@ -732,9 +802,10 @@ export default class ParseApp {
}
setConnectedTwitterApps(consumerKeyList) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {
- 'parse_app[auth_options_attributes][twitter_attributes][consumer_keys_as_list]': consumerKeyList.join(','),
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][twitter_attributes][consumer_keys_as_list]':
+ consumerKeyList.join(','),
});
promise.then(() => {
this.settings.fields.fields.auth_options_attributes.twitter.consumer_keys = consumerKeyList;
@@ -743,14 +814,18 @@ export default class ParseApp {
}
addConnectedTwitterApp(newConsumerKey) {
- let allKeys = (this.settings.fields.fields.auth_options_attributes.twitter.consumer_keys || []).concat(newConsumerKey);
+ const allKeys = (
+ this.settings.fields.fields.auth_options_attributes.twitter.consumer_keys || []
+ ).concat(newConsumerKey);
return this.setConnectedTwitterApps(allKeys);
}
setAllowTwitterAuth(allow) {
- let path = '/apps/' + this.slug;
- let promise = AJAX.put(path, {
- 'parse_app[auth_options_attributes][twitter_attributes][enabled_as_bool]': allow ? 'true' : 'false',
+ const path = '/apps/' + this.slug;
+ const promise = AJAX.put(path, {
+ 'parse_app[auth_options_attributes][twitter_attributes][enabled_as_bool]': allow
+ ? 'true'
+ : 'false',
});
promise.then(() => {
this.settings.fields.fields.auth_options_attributes.twitter.enabled = !!allow;
@@ -767,10 +842,10 @@ export default class ParseApp {
}
addGCMCredentials(sender_id, api_key) {
- let path = '/apps/' + this.slug + '/update_push_notifications'
- let promise = AJAX.post(path, {
+ const path = '/apps/' + this.slug + '/update_push_notifications';
+ const promise = AJAX.post(path, {
gcm_sender_id: sender_id,
- gcm_api_key: api_key
+ gcm_api_key: api_key,
});
promise.then(() => {
this.settings.fields.fields.gcm_credentials.push({ sender_id, api_key });
@@ -779,12 +854,11 @@ export default class ParseApp {
}
deleteGCMPushCredentials(GCMSenderID) {
- let path = '/apps/' + this.slug + '/delete_gcm_push_credential?gcm_sender_id='+GCMSenderID;
- let promise = AJAX.get(path);
+ const path = '/apps/' + this.slug + '/delete_gcm_push_credential?gcm_sender_id=' + GCMSenderID;
+ const promise = AJAX.get(path);
promise.then(() => {
- this.settings.fields.fields.gcm_credentials = this.settings.fields.fields.gcm_credentials.filter(cred =>
- cred.sender_id != GCMSenderID
- );
+ this.settings.fields.fields.gcm_credentials =
+ this.settings.fields.fields.gcm_credentials.filter(cred => cred.sender_id != GCMSenderID);
});
return promise;
}
diff --git a/src/lib/Position.js b/src/lib/Position.js
index 491ee773bb..a9b41dcc2c 100644
--- a/src/lib/Position.js
+++ b/src/lib/Position.js
@@ -16,14 +16,14 @@ export default class Position {
}
static inDocument(node) {
- let pos = Position.inWindow(node);
+ const pos = Position.inWindow(node);
pos.x += window.pageXOffset;
pos.y += window.pageYOffset;
return pos;
}
static inWindow(node) {
- let rect = node.getBoundingClientRect();
+ const rect = node.getBoundingClientRect();
return new Position(rect.left, rect.top);
}
}
diff --git a/src/lib/PropTypes.js b/src/lib/PropTypes.js
index 692913d8a0..093f541e8d 100644
--- a/src/lib/PropTypes.js
+++ b/src/lib/PropTypes.js
@@ -22,10 +22,10 @@ function wrapType(type, id) {
type.isRequired._classes = type._classes;
type.isRequired._values = type._values;
}
- type.describe = function(description) {
- let wrapped = function(...args) {
+ type.describe = function (description) {
+ const wrapped = function (...args) {
return type.apply(type, args);
- }
+ };
wrapped._id = type._id;
wrapped._required = type._required;
wrapped._description = description;
@@ -51,7 +51,7 @@ Types = {
any: wrapType(PropTypes.any, 'Any'),
- instanceOf: function(klass) {
+ instanceOf: function (klass) {
let name = klass.constructor.name;
if (klass === Date) {
name = 'Date';
@@ -59,29 +59,29 @@ Types = {
return wrapType(PropTypes.instanceOf(klass), name);
},
- oneOf: function(values) {
+ oneOf: function (values) {
let type = PropTypes.oneOf(values);
type._values = values;
type = wrapType(type, 'Enum');
return type;
},
- oneOfType: function(classes) {
+ oneOfType: function (classes) {
let type = PropTypes.oneOfType(classes);
type._classes = classes;
type = wrapType(type, 'Union');
return type;
},
- arrayOf: function(valueType) {
+ arrayOf: function (valueType) {
return wrapType(PropTypes.arrayOf(valueType), `Array<${valueType._id}>`);
},
- objectOf: function(valueType) {
+ objectOf: function (valueType) {
return wrapType(PropTypes.objectOf(valueType), `Object`);
},
- shape: function(shape) {
+ shape: function (shape) {
return wrapType(PropTypes.shape(shape), 'Object');
},
};
diff --git a/src/lib/PushUtils.js b/src/lib/PushUtils.js
index 6a9e7b16b8..fee57eb5b7 100644
--- a/src/lib/PushUtils.js
+++ b/src/lib/PushUtils.js
@@ -11,15 +11,15 @@
*/
import * as PushConstants from 'dashboard/Push/PushConstants';
-import LoaderDots from 'components/LoaderDots/LoaderDots.react';
-import prettyNumber from 'lib/prettyNumber';
-import React from 'react';
-import stringList from 'lib/stringList';
+import LoaderDots from 'components/LoaderDots/LoaderDots.react';
+import prettyNumber from 'lib/prettyNumber';
+import React from 'react';
+import stringList from 'lib/stringList';
// formats pointers into human readable form
-let pointerToReadbleValue = (value) => {
- return value.className + ':' + value.objectId
-}
+const pointerToReadbleValue = value => {
+ return value.className + ':' + value.objectId;
+};
/**
* Formats Pointer, GeoPoint, and Date objects into a human readable form
@@ -27,14 +27,14 @@ let pointerToReadbleValue = (value) => {
* @param {Object}
* @return {String}
*/
-let objectToReadable = (value) => {
+const objectToReadable = value => {
if (value === undefined || value === null) {
return '';
}
- let typeValue = value['__type'];
+ const typeValue = value['__type'];
let res = '';
- switch(typeValue) {
+ switch (typeValue) {
case 'Pointer':
res = `${pointerToReadbleValue(value)}`;
break;
@@ -43,7 +43,7 @@ let objectToReadable = (value) => {
break;
case 'Date':
if (value['iso']) {
- res = (new Date(value['iso'])).toLocaleString();
+ res = new Date(value['iso']).toLocaleString();
} else {
res = value;
}
@@ -53,7 +53,7 @@ let objectToReadable = (value) => {
break;
}
return res;
-}
+};
/**
* Creates humanized version of lists that are either inclusive ('or')
@@ -69,11 +69,11 @@ let objectToReadable = (value) => {
* @param {Boolean}
* @return {String}
*/
-let humanizedList = (value, inclusive, all) => {
- if(!value.constructor === Array){
+const humanizedList = (value, inclusive, all) => {
+ if (!value.constructor === Array) {
return '';
}
- let join = all ? 'and' : 'or';
+ const join = all ? 'and' : 'or';
let prefix = '';
let res = '';
switch (value.length) {
@@ -82,18 +82,18 @@ let humanizedList = (value, inclusive, all) => {
break;
case 2:
prefix = inclusive ? 'either ' : '';
- if(all) {
+ if (all) {
prefix = 'both ';
}
- res = `${prefix}${objectToReadable(value[0])} ${join} ${objectToReadable(value[1])}`;
+ res = `${prefix}${objectToReadable(value[0])} ${join} ${objectToReadable(value[1])}`;
break;
default:
prefix = all ? 'all of' : 'any of';
- res = `${join} ${objectToReadable(value[value.length-1])}`;
+ res = `${join} ${objectToReadable(value[value.length - 1])}`;
break;
}
return res;
-}
+};
/**
* Result is 3 components for the constraint, like this: ["key", "descriptor", "value"]
@@ -106,7 +106,7 @@ let humanizedList = (value, inclusive, all) => {
* @param {Object}
* @return {Array}
*/
-let formatConstraintComponent = (key, operation, value, schema) => {
+const formatConstraintComponent = (key, operation, value, schema) => {
let res = null;
switch (operation) {
case '$lt':
@@ -130,16 +130,18 @@ let formatConstraintComponent = (key, operation, value, schema) => {
case '$in':
case '$nin':
case '$all':
- let isInclusive = operation === '$in';
- if(!value.constructor === Array) {
+ const isInclusive = operation === '$in';
+ if (!value.constructor === Array) {
res = [key, `constraint is malformed (${operation} operator requires an array)`, ''];
} else if (!schema[key]) {
res = ['', `Cannot perform operation on non-existent column ${key}`, ''];
- } else if (schema[key]['type'] === 'Array'){
- let isAll = operation === '$all';
- res = [key,
- isInclusive || isAll ? 'contains' : 'does not contain',
- humanizedList(value, isInclusive, isAll)];
+ } else if (schema[key]['type'] === 'Array') {
+ const isAll = operation === '$all';
+ res = [
+ key,
+ isInclusive || isAll ? 'contains' : 'does not contain',
+ humanizedList(value, isInclusive, isAll),
+ ];
}
break;
default:
@@ -147,7 +149,7 @@ let formatConstraintComponent = (key, operation, value, schema) => {
break;
}
return res;
-}
+};
/**
* Handles formatting of Arrays and Maps
@@ -156,15 +158,15 @@ let formatConstraintComponent = (key, operation, value, schema) => {
* @param {Object}
* @return {Array}
*/
-let formatStructure = (key, constraints, schema) => {
- let rows = [];
- for(let prop in constraints){
- if(Object.prototype.hasOwnProperty.call(constraints, prop)){
+const formatStructure = (key, constraints, schema) => {
+ const rows = [];
+ for (const prop in constraints) {
+ if (Object.prototype.hasOwnProperty.call(constraints, prop)) {
rows.push(formatConstraintComponent(key, prop, constraints[prop], schema));
}
}
return rows;
-}
+};
/**
* Result is 3 components for each constraint, like this: [["key", "descriptor", "value"]]
@@ -178,19 +180,19 @@ let formatStructure = (key, constraints, schema) => {
*/
export function formatConstraint(key, constraints, schema) {
let rows = [];
- if(constraints.constructor === Object){
+ if (constraints.constructor === Object) {
rows.push(formatStructure(key, constraints, schema));
- } else if(constraints.constructor === Array) {
+ } else if (constraints.constructor === Array) {
// legacy comment: Not sure how we want to display grouped subclauses
- for(let i = 0; i {
- return devices.map((device) => {
+const devicesToReadableList = devices => {
+ return devices.map(device => {
return PushConstants.DEVICE_MAP[device];
});
-}
+};
/**
* build short for query information
@@ -216,23 +218,23 @@ let devicesToReadableList = (devices) => {
* @return {String}
*/
export function shortInfoBuilder(query, schema) {
- if(!query){
+ if (!query) {
return '';
}
- let platformString = query.deviceType && query.deviceType['$in'] ? devicesToReadableList(query.deviceType['$in']).join(', ') : '';
- let otherConstraints = [];
- for(let entry in query){
- if(entry !== 'deviceType'){ //filter out deviceType entry
- formatConstraint(entry, query[entry], schema).forEach(
- (constraint) => {
- constraint.forEach(
- ([key, description, value]) => {
- otherConstraints.push([key, description, value].join(' '));
- }
- )
- }
- );
+ const platformString =
+ query.deviceType && query.deviceType['$in']
+ ? devicesToReadableList(query.deviceType['$in']).join(', ')
+ : '';
+ const otherConstraints = [];
+ for (const entry in query) {
+ if (entry !== 'deviceType') {
+ //filter out deviceType entry
+ formatConstraint(entry, query[entry], schema).forEach(constraint => {
+ constraint.forEach(([key, description, value]) => {
+ otherConstraints.push([key, description, value].join(' '));
+ });
+ });
}
}
return [platformString, otherConstraints.join(', ')].join(', ');
@@ -245,36 +247,32 @@ export function shortInfoBuilder(query, schema) {
* @return {Object} React Element
*/
export function largeInfoBuilder(query, schema, styles = {}) {
- if(!query) {
+ if (!query) {
return;
}
- let platforms = query.deviceType && query.deviceType['$in'] ? devicesToReadableList(query.deviceType['$in']) : [];
- let platformRows = [];
+ const platforms =
+ query.deviceType && query.deviceType['$in']
+ ? devicesToReadableList(query.deviceType['$in'])
+ : [];
+ const platformRows = [];
- for (let platform in platforms) {
- platformRows.push(
-
- {platforms[platform]}
-
- );
+ for (const platform in platforms) {
+ platformRows.push({platforms[platform]} );
}
- let conditionRows = [];
- for(let entry in query){
- if(entry !== 'deviceType'){ //filter out deviceType entry
- formatConstraint(entry, query[entry], schema).forEach(
- (constraint) => {
- constraint.forEach(
- ([key, description, value]) => {
- conditionRows.push(
-
- {key} {description} {value}
-
- );
- }
- )
- }
- );
+ const conditionRows = [];
+ for (const entry in query) {
+ if (entry !== 'deviceType') {
+ //filter out deviceType entry
+ formatConstraint(entry, query[entry], schema).forEach(constraint => {
+ constraint.forEach(([key, description, value]) => {
+ conditionRows.push(
+
+ {key} {description} {value}
+
+ );
+ });
+ });
}
}
return (
@@ -283,48 +281,54 @@ export function largeInfoBuilder(query, schema, styles = {}) {
PLATFORMS
{platformRows}
- { conditionRows.length > 0 ?
+ {conditionRows.length > 0 ? (
INSTALLATION CONDITIONS
{conditionRows}
- :
- null
- }
+
+ ) : null}
- )
+ );
}
-let tableInfoBuilderHelper = (styles, key, description, value) => {
+const tableInfoBuilderHelper = (styles, key, description, value) => {
return (
{key}
{description}
- {value}
+
+ {value}
+
);
-}
+};
export function tableInfoBuilder(query, schema, styles = {}) {
try {
query = JSON.parse(query);
- } catch(e) {/**/}
+ } catch (e) {
+ /**/
+ }
- if(!query) {
+ if (!query) {
return;
}
- let platforms = query.deviceType && query.deviceType['$in'] ? devicesToReadableList(query.deviceType['$in']) : [];
+ const platforms =
+ query.deviceType && query.deviceType['$in']
+ ? devicesToReadableList(query.deviceType['$in'])
+ : [];
// special case: ex: {deviceType: "ios"}
if (query.deviceType && query.deviceType.constructor === String) {
- platforms.push(query.deviceType)
+ platforms.push(query.deviceType);
}
- let platformStr = stringList(platforms, 'or');
- let tableInfoRows = [];
+ const platformStr = stringList(platforms, 'or');
+ const tableInfoRows = [];
if (platformStr) {
tableInfoRows.push(
-
+
deviceType
is
{platformStr}
@@ -332,32 +336,29 @@ export function tableInfoBuilder(query, schema, styles = {}) {
);
}
- for(let entry in query){
- if(entry !== 'deviceType'){ //filter out deviceType entry
- formatConstraint(entry, query[entry], schema).forEach(
- (constraint) => {
- if (constraint && Array.isArray(constraint[0])) {
- // case 1: contraint = [[key, description, value]]
- constraint.forEach(
- ([key, description, value]) => {
- tableInfoRows.push(tableInfoBuilderHelper(styles, key, description, value));
- }
- );
- } else {
- // case 2: contraint = [key, description, value]
- let [key, description, value] = constraint;
+ for (const entry in query) {
+ if (entry !== 'deviceType') {
+ //filter out deviceType entry
+ formatConstraint(entry, query[entry], schema).forEach(constraint => {
+ if (constraint && Array.isArray(constraint[0])) {
+ // case 1: contraint = [[key, description, value]]
+ constraint.forEach(([key, description, value]) => {
tableInfoRows.push(tableInfoBuilderHelper(styles, key, description, value));
- }
+ });
+ } else {
+ // case 2: contraint = [key, description, value]
+ const [key, description, value] = constraint;
+ tableInfoRows.push(tableInfoBuilderHelper(styles, key, description, value));
}
- );
+ });
}
}
return tableInfoRows;
}
export function formatCountDetails(count, approximate) {
- if(count === undefined) {
- return ( );
+ if (count === undefined) {
+ return ;
} else if (count === 0 && approximate) {
return 'very small';
} else {
@@ -366,10 +367,10 @@ export function formatCountDetails(count, approximate) {
}
export function formatAudienceSchema(classes) {
- let schema = {};
- if(classes){
- let installations = classes.get('_Installation');
- if(typeof(installations) !== 'undefined'){
+ const schema = {};
+ if (classes) {
+ const installations = classes.get('_Installation');
+ if (typeof installations !== 'undefined') {
installations.forEach((type, col) => {
schema[col] = type;
});
diff --git a/src/lib/StringEscaping.js b/src/lib/StringEscaping.js
index 0e5966d086..5dbff874a9 100644
--- a/src/lib/StringEscaping.js
+++ b/src/lib/StringEscaping.js
@@ -11,24 +11,24 @@ const UNESCAPE_MAP = {
'>': '>',
'/': '/',
''': '\'',
- '"': '"'
+ '"': '"',
};
const ESCAPE_MAP = {};
-for (let k in UNESCAPE_MAP) {
+for (const k in UNESCAPE_MAP) {
ESCAPE_MAP[UNESCAPE_MAP[k]] = k;
}
-let escapeMatcher = RegExp('(?:' + (Object.keys(ESCAPE_MAP).join('|')) + ')', 'g');
-let unescapeMatcher = RegExp('(?:' + (Object.keys(UNESCAPE_MAP).join('|')) + ')', 'g');
+const escapeMatcher = RegExp('(?:' + Object.keys(ESCAPE_MAP).join('|') + ')', 'g');
+const unescapeMatcher = RegExp('(?:' + Object.keys(UNESCAPE_MAP).join('|') + ')', 'g');
export function escape(str) {
- return str.replace(escapeMatcher, function(ch) {
+ return str.replace(escapeMatcher, function (ch) {
return ESCAPE_MAP[ch];
});
}
export function unescape(str) {
- return str.replace(unescapeMatcher, function(ch) {
+ return str.replace(unescapeMatcher, function (ch) {
return UNESCAPE_MAP[ch];
});
}
diff --git a/src/lib/encodeFormData.js b/src/lib/encodeFormData.js
index e873e5883f..5743bb0f0b 100644
--- a/src/lib/encodeFormData.js
+++ b/src/lib/encodeFormData.js
@@ -19,14 +19,14 @@ export default function encodeFormData(key, value, prevKey) {
if (value && typeof value.toJSON === 'function') {
return encodeURI(field) + '=' + encodeURI(value.toJSON());
}
- let pieces = [];
+ const pieces = [];
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
pieces.push(encodeFormData('', value[i], field));
}
return pieces.join('&');
}
- for (let k in value) {
+ for (const k in value) {
pieces.push(encodeFormData(k, value[k], field));
}
return pieces.join('&');
diff --git a/src/lib/englishOrdinalIndicator.js b/src/lib/englishOrdinalIndicator.js
index 83f88acdb2..53552dd3a1 100644
--- a/src/lib/englishOrdinalIndicator.js
+++ b/src/lib/englishOrdinalIndicator.js
@@ -5,7 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-let numberSuffix = (number) => {
+const numberSuffix = number => {
if (number > 3 && number < 21) {
return 'th';
}
diff --git a/src/lib/extractTime.js b/src/lib/extractTime.js
index 78b0f867a2..d7d4042308 100644
--- a/src/lib/extractTime.js
+++ b/src/lib/extractTime.js
@@ -1,10 +1,5 @@
export function extractPushTime(changes) {
- const {
- local_time: isLocalTime,
- push_time_type,
- push_time,
- push_time_iso,
- } = changes;
+ const { local_time: isLocalTime, push_time_type, push_time, push_time_iso } = changes;
if (push_time_type === 'time') {
if (isLocalTime) {
@@ -22,7 +17,7 @@ export function extractExpiration(changes) {
expiration_time_type,
expiration_interval_unit,
expiration_interval_num,
- expiration_time
+ expiration_time,
} = changes;
if (push_expires) {
diff --git a/src/lib/getSiteDomain.js b/src/lib/getSiteDomain.js
index a80945c89f..b2da5a0bcc 100644
--- a/src/lib/getSiteDomain.js
+++ b/src/lib/getSiteDomain.js
@@ -6,6 +6,6 @@
* the root directory of this source tree.
*/
export default function getSiteDomain() {
- let host = location.host.split('.');
+ const host = location.host.split('.');
return location.protocol + '//' + host.slice(host.length - 2).join('.');
}
diff --git a/src/lib/howLongAgo.js b/src/lib/howLongAgo.js
index 04cd5a4d94..d5b92a04a6 100644
--- a/src/lib/howLongAgo.js
+++ b/src/lib/howLongAgo.js
@@ -22,7 +22,7 @@ export default function howLongAgo(date) {
if (isNaN(date)) {
return 'unknown time ago';
}
- let delta = new Date() - date;
+ const delta = new Date() - date;
if (delta < 0) {
return 'in the future';
}
diff --git a/src/lib/isInsidePopover.js b/src/lib/isInsidePopover.js
index f2ec37a3c0..517911d396 100644
--- a/src/lib/isInsidePopover.js
+++ b/src/lib/isInsidePopover.js
@@ -1,12 +1,12 @@
export default function isInsidePopover(node) {
- let cur = node.parentNode;
- while (cur && cur.nodeType === 1) {
- // If id starts with "fixed_wrapper", we consider it as the
- // root element of the Popover component
- if (/^fixed_wrapper/g.test(cur.id)) {
- return true;
- }
- cur = cur.parentNode;
+ let cur = node.parentNode;
+ while (cur && cur.nodeType === 1) {
+ // If id starts with "fixed_wrapper", we consider it as the
+ // root element of the Popover component
+ if (/^fixed_wrapper/g.test(cur.id)) {
+ return true;
+ }
+ cur = cur.parentNode;
}
return false;
}
diff --git a/src/lib/joinWithFinal.js b/src/lib/joinWithFinal.js
index bc7849e74d..1736402f1c 100644
--- a/src/lib/joinWithFinal.js
+++ b/src/lib/joinWithFinal.js
@@ -12,16 +12,23 @@
// joinWithFinal('your items: ', ['item1', 'item2', 'item3'], ', ', ', and ').join(''): 'your items: item1, item2, and item3'
export default (prefix, array, joiner, finalJoiner) => {
switch (array.length) {
- case 0: return [];
- case 1: return [prefix, array[0]];
- default: return [prefix].concat(array.map((node, index) => {
- if (index === array.length - 1) {
- return [node];
- } else if (index === array.length - 2) {
- return [node, finalJoiner];
- } else {
- return [node, joiner];
- }
- }).reduce((a,b) => a.concat(b), []));
+ case 0:
+ return [];
+ case 1:
+ return [prefix, array[0]];
+ default:
+ return [prefix].concat(
+ array
+ .map((node, index) => {
+ if (index === array.length - 1) {
+ return [node];
+ } else if (index === array.length - 2) {
+ return [node, finalJoiner];
+ } else {
+ return [node, joiner];
+ }
+ })
+ .reduce((a, b) => a.concat(b), [])
+ );
}
};
diff --git a/src/lib/keyMirror.js b/src/lib/keyMirror.js
index 1a0ef1a3f0..8a92d9a203 100644
--- a/src/lib/keyMirror.js
+++ b/src/lib/keyMirror.js
@@ -10,7 +10,7 @@
* themselves
*/
export default function keyMirror(keys) {
- let map = {};
+ const map = {};
for (let i = 0; i < keys.length; i++) {
map[keys[i]] = keys[i];
}
diff --git a/src/lib/passwordStrength.js b/src/lib/passwordStrength.js
index 84141adbae..366f11b0df 100644
--- a/src/lib/passwordStrength.js
+++ b/src/lib/passwordStrength.js
@@ -9,7 +9,7 @@ const rangeSize = {
az: 26,
AZ: 26,
num: 10,
- sym: 33
+ sym: 33,
};
// Returns 0 if the password does not meet length requirements
@@ -21,14 +21,14 @@ export default function passwordStrength(password) {
}
// We approximate entropy by determining which character sets are included in
// the password string.
- let seen = {
+ const seen = {
az: false,
AZ: false,
num: false,
- sym: false
+ sym: false,
};
for (let i = password.length; i--;) {
- let c = password.charCodeAt(i);
+ const c = password.charCodeAt(i);
if (c > 47 && c < 58) {
seen.num = true;
} else if (c > 64 && c < 91) {
@@ -38,11 +38,11 @@ export default function passwordStrength(password) {
}
}
let range = 0;
- for (let r in seen) {
+ for (const r in seen) {
if (seen[r]) {
range += rangeSize[r];
}
}
- let entropy = Math.log(range) / Math.log(2) * password.length;
- return (entropy > 60 ? 2 : 1);
+ const entropy = (Math.log(range) / Math.log(2)) * password.length;
+ return entropy > 60 ? 2 : 1;
}
diff --git a/src/lib/pluck.js b/src/lib/pluck.js
index 486458119d..7fefcab9fa 100644
--- a/src/lib/pluck.js
+++ b/src/lib/pluck.js
@@ -5,6 +5,6 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-export default function(array, key) {
+export default function (array, key) {
return array.map(item => item[key]);
}
diff --git a/src/lib/prettyNumber.js b/src/lib/prettyNumber.js
index b1208e85bb..e469f557a5 100644
--- a/src/lib/prettyNumber.js
+++ b/src/lib/prettyNumber.js
@@ -15,8 +15,8 @@ export default function prettyNumber(number, places) {
places = 3;
}
if (number.toFixed().length < places) {
- let fullLength = number.toString().length;
- let truncatedLength = number.toFixed().length;
+ const fullLength = number.toString().length;
+ const truncatedLength = number.toFixed().length;
if (fullLength > truncatedLength) {
// has a decimal piece
return number.toFixed(places - truncatedLength);
@@ -29,6 +29,6 @@ export default function prettyNumber(number, places) {
shortened /= 1000;
suffixIndex++;
}
- let remainder = places - shortened.toFixed().length;
+ const remainder = places - shortened.toFixed().length;
return shortened.toFixed(remainder) + SUFFIXES[suffixIndex];
}
diff --git a/src/lib/queryFromFilters.js b/src/lib/queryFromFilters.js
index bd74075c65..a42c15b1f5 100644
--- a/src/lib/queryFromFilters.js
+++ b/src/lib/queryFromFilters.js
@@ -15,16 +15,16 @@ export default function queryFromFilters(className, filters) {
} else if (typeof className === 'object' && className instanceof Parse.Relation) {
query = className.query();
}
- filters.forEach((filter) => {
+ filters.forEach(filter => {
addConstraint(query, filter);
});
return query;
}
function addQueryConstraintFromObject(query, filter, constraintType) {
- let compareTo = JSON.parse(filter.get('compareTo'));
- for (let key of Object.keys(compareTo)) {
- query[constraintType](filter.get('field')+'.'+key, compareTo[key]);
+ const compareTo = JSON.parse(filter.get('compareTo'));
+ for (const key of Object.keys(compareTo)) {
+ query[constraintType](filter.get('field') + '.' + key, compareTo[key]);
}
}
diff --git a/src/lib/renderFlowFooterChanges.js b/src/lib/renderFlowFooterChanges.js
index 0f6c1243f8..57974bedfc 100644
--- a/src/lib/renderFlowFooterChanges.js
+++ b/src/lib/renderFlowFooterChanges.js
@@ -6,7 +6,7 @@
* the root directory of this source tree.
*/
import joinWithFinal from 'lib/joinWithFinal';
-import React from 'react';
+import React from 'react';
import setDifference from 'lib/setDifference';
// Display changes in a FlowFooter. The first argument is
@@ -23,61 +23,95 @@ import setDifference from 'lib/setDifference';
// Note: key_name is snake_case because in most cases it will come directly from ruby, which
// uses snake_case
export default (changes, initial, fieldOptions) => {
- let booleanChanges = [];
- let stringChangesWithTo = [];
- let stringChanges = [];
- let additions = [];
- let setChanges = [];
- for (let key in changes) {
+ const booleanChanges = [];
+ const stringChangesWithTo = [];
+ const stringChanges = [];
+ const additions = [];
+ const setChanges = [];
+ for (const key in changes) {
if (fieldOptions[key]) {
if (fieldOptions[key].type === 'boolean') {
-
// If a boolean is changing, display whether it is now enabled or disabled.
booleanChanges.push(
- {changes[key] ? 'enabled' : 'disabled'} {fieldOptions[key].friendlyName}
+
+ {changes[key] ? 'enabled' : 'disabled'} {fieldOptions[key].friendlyName}
+
);
-
} else if (fieldOptions[key].type === 'addition') {
-
// If a new value is being added to a list, display that it has been added.
additions.push({fieldOptions[key].friendlyName} );
-
} else if (fieldOptions[key].showTo && changes[key] !== '') {
-
// If the caller wants to display the new value, and there is a new value,
// display what has changed, what it has changed to, and what it changed from if requested.
- stringChangesWithTo.push(
- changed your {fieldOptions[key].friendlyName}
- {fieldOptions[key].showFrom && initial[key] ? from {initial[key]} : null}
- {fieldOptions[key].showTo ? to {changes[key]} : null}
- );
+ stringChangesWithTo.push(
+
+ changed your {fieldOptions[key].friendlyName}
+ {fieldOptions[key].showFrom && initial[key] ? (
+
+ {' '}
+ from {initial[key]}
+
+ ) : null}
+ {fieldOptions[key].showTo ? (
+
+ {' '}
+ to {changes[key]}
+
+ ) : null}
+
+ );
} else if (fieldOptions[key].type === 'set') {
- let additionsToSet = setDifference(changes[key], initial[key], fieldOptions[key].equalityPredicate);
- let removalsFromSet = setDifference(initial[key], changes[key], fieldOptions[key].equalityPredicate);
+ const additionsToSet = setDifference(
+ changes[key],
+ initial[key],
+ fieldOptions[key].equalityPredicate
+ );
+ const removalsFromSet = setDifference(
+ initial[key],
+ changes[key],
+ fieldOptions[key].equalityPredicate
+ );
- let friendlyAddition = additionsToSet.length > 1 ? fieldOptions[key].friendlyNamePlural : fieldOptions[key].friendlyName;
- let friendlyRemoval = removalsFromSet.length > 1 ? fieldOptions[key].friendlyNamePlural : fieldOptions[key].friendlyName;
+ const friendlyAddition =
+ additionsToSet.length > 1
+ ? fieldOptions[key].friendlyNamePlural
+ : fieldOptions[key].friendlyName;
+ const friendlyRemoval =
+ removalsFromSet.length > 1
+ ? fieldOptions[key].friendlyNamePlural
+ : fieldOptions[key].friendlyName;
if (additionsToSet.length > 0) {
- setChanges.push(added {additionsToSet.length} {friendlyAddition} );
+ setChanges.push(
+
+ added{' '}
+
+ {additionsToSet.length} {friendlyAddition}
+
+
+ );
}
if (removalsFromSet.length > 0) {
- setChanges.push(removed {removalsFromSet.length} {friendlyRemoval} );
+ setChanges.push(
+
+ removed{' '}
+
+ {removalsFromSet.length} {friendlyRemoval}
+
+
+ );
}
-
} else {
-
// If the caller specifies no options, just display what has been changed.
stringChanges.push({fieldOptions[key].friendlyName} );
-
}
}
}
- let renderChangeList = (prefix, changes, isLastList) => {
+ const renderChangeList = (prefix, changes, isLastList) => {
return joinWithFinal(prefix, changes, ', ', isLastList ? ' and ' : ', ');
};
- let changesList = [
+ const changesList = [
{
changes: booleanChanges,
prefix: null,
@@ -99,10 +133,15 @@ export default (changes, initial, fieldOptions) => {
prefix: 'changed your ',
},
];
- let allChangeNodes = changesList.filter(({ changes }) => changes.length > 0).map(({ changes, prefix }, index, wholeList) =>
- renderChangeList(prefix, changes, index === wholeList.length - 1)
+ const allChangeNodes = changesList
+ .filter(({ changes }) => changes.length > 0)
+ .map(({ changes, prefix }, index, wholeList) =>
+ renderChangeList(prefix, changes, index === wholeList.length - 1)
+ );
+ return (
+
+ You've{' '}
+ {joinWithFinal(null, allChangeNodes, ', ', allChangeNodes.length < 3 ? ' and ' : ', and ')}.
+
);
- return
- You've {joinWithFinal(null, allChangeNodes, ', ', allChangeNodes.length < 3 ? ' and ' : ', and ')}.
- ;
};
diff --git a/src/lib/setDifference.js b/src/lib/setDifference.js
index d6853452df..9f5829cb1e 100644
--- a/src/lib/setDifference.js
+++ b/src/lib/setDifference.js
@@ -5,6 +5,9 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-export default function(first, second, equalityPredicate) {
- return first.filter(itemFromFirst => !second.find(itemFromSecond => equalityPredicate(itemFromFirst, itemFromSecond)));
+export default function (first, second, equalityPredicate) {
+ return first.filter(
+ itemFromFirst =>
+ !second.find(itemFromSecond => equalityPredicate(itemFromFirst, itemFromSecond))
+ );
}
diff --git a/src/lib/stores/AnalyticsQueryStore.js b/src/lib/stores/AnalyticsQueryStore.js
index eceadd274d..02bfa42589 100644
--- a/src/lib/stores/AnalyticsQueryStore.js
+++ b/src/lib/stores/AnalyticsQueryStore.js
@@ -6,95 +6,95 @@
* the root directory of this source tree.
*/
import { get, post, put } from 'lib/AJAX';
-import keyMirror from 'lib/keyMirror';
-import { Map } from 'immutable';
-import { registerStore } from 'lib/stores/StoreManager';
+import keyMirror from 'lib/keyMirror';
+import { Map } from 'immutable';
+import { registerStore } from 'lib/stores/StoreManager';
const LABEL_TO_KEY_MAPPING = {
// Display Type
- 'chart' : 'time_series',
- 'table' : 'table',
- 'json' : 'raw',
+ chart: 'time_series',
+ table: 'table',
+ json: 'raw',
// Source
- 'API Event' : 'parse_api_events',
- 'Custom Event' : 'parse_custom_events',
+ 'API Event': 'parse_api_events',
+ 'Custom Event': 'parse_custom_events',
// Column
- 'Request Type' : 'op',
- 'Class' : 'collection_name',
- 'Event Name' : 'event',
- 'Dimensions' : 'dimensions',
- 'Installation ID' : 'installation_id',
- 'Parse User ID' : 'parseuser_id',
- 'Parse SDK' : 'parse_sdk',
- 'Parse SDK Version' : 'parse_sdk_version',
- 'OS' : 'os',
- 'OS Version' : 'os_version',
- 'App Build Version' : 'app_build_version',
- 'App Display Version' : 'app_display_version',
- 'Timestamp (s)' : 'event_time',
- 'Latency (s)' : 'duration',
+ 'Request Type': 'op',
+ Class: 'collection_name',
+ 'Event Name': 'event',
+ Dimensions: 'dimensions',
+ 'Installation ID': 'installation_id',
+ 'Parse User ID': 'parseuser_id',
+ 'Parse SDK': 'parse_sdk',
+ 'Parse SDK Version': 'parse_sdk_version',
+ OS: 'os',
+ 'OS Version': 'os_version',
+ 'App Build Version': 'app_build_version',
+ 'App Display Version': 'app_display_version',
+ 'Timestamp (s)': 'event_time',
+ 'Latency (s)': 'duration',
// Aggregate
- 'Count' : 'count',
+ Count: 'count',
// TODO (hallucinogen): wtf is this countDistinct. It's not consistent with our current naming.
// Let's make it consistent after we deprecate old explorer
- 'Count Distinct' : 'countDistinct',
- 'Sum' : 'sum',
- 'Minimum' : 'min',
- 'Median' : 'median',
- '99th Percentile' : 'p99',
- 'Average' : 'avg',
+ 'Count Distinct': 'countDistinct',
+ Sum: 'sum',
+ Minimum: 'min',
+ Median: 'median',
+ '99th Percentile': 'p99',
+ Average: 'avg',
// Grouping
- 'Time (day)' : 'day',
- 'Time (hour)' : 'hour',
+ 'Time (day)': 'day',
+ 'Time (hour)': 'hour',
// Order
- 'Ascending' : true,
- 'Descending' : false
+ Ascending: true,
+ Descending: false,
};
// This is basically a const, but it's not declared inline.
-let KEY_TO_LABEL_MAPPING = {};
-for (let key in LABEL_TO_KEY_MAPPING) {
+const KEY_TO_LABEL_MAPPING = {};
+for (const key in LABEL_TO_KEY_MAPPING) {
KEY_TO_LABEL_MAPPING[LABEL_TO_KEY_MAPPING[key]] = key;
}
const LAST_FETCH_TIMEOUT = 60000;
-let queryToPayload = (query) => {
- let payload = {
+const queryToPayload = query => {
+ const payload = {
sources: [LABEL_TO_KEY_MAPPING[query.source]],
enabled: query.enabled,
type: LABEL_TO_KEY_MAPPING[query.type],
from: query.from,
- to: query.to
- }
+ to: query.to,
+ };
if (query.limit) {
payload.limit = query.limit;
}
if (query.aggregates && query.aggregates.length > 0) {
- payload.aggregates = query.aggregates.map(({col, op}) => ({
+ payload.aggregates = query.aggregates.map(({ col, op }) => ({
col: LABEL_TO_KEY_MAPPING[col],
- op: LABEL_TO_KEY_MAPPING[op]
+ op: LABEL_TO_KEY_MAPPING[op],
}));
}
if (query.groups && query.groups.length > 0) {
- payload.groups = query.groups.map((group) => LABEL_TO_KEY_MAPPING[group]);
+ payload.groups = query.groups.map(group => LABEL_TO_KEY_MAPPING[group]);
}
if (query.filters && query.filters.length > 0) {
- payload.filters = query.filters.map(({col, op, val}) => ({
+ payload.filters = query.filters.map(({ col, op, val }) => ({
col: LABEL_TO_KEY_MAPPING[col],
op,
- val
+ val,
}));
}
if (query.orders && query.orders.length > 0) {
- payload.orders = query.orders.map(({col, asc}) => ({
+ payload.orders = query.orders.map(({ col, asc }) => ({
col,
- asc: LABEL_TO_KEY_MAPPING[asc]
+ asc: LABEL_TO_KEY_MAPPING[asc],
}));
}
if (query.objectId) {
@@ -110,36 +110,36 @@ let queryToPayload = (query) => {
return payload;
};
-let payloadToQuery = (payload) => {
- let query = {
+const payloadToQuery = payload => {
+ const query = {
name: payload.name,
source: KEY_TO_LABEL_MAPPING[payload.sources[0]],
enabled: payload.enabled,
- type: KEY_TO_LABEL_MAPPING[payload.type]
+ type: KEY_TO_LABEL_MAPPING[payload.type],
};
if (payload.limit) {
query.limit = payload.limit;
}
if (payload.aggregates) {
- query.aggregates = payload.aggregates.map(({col, op}) => ({
+ query.aggregates = payload.aggregates.map(({ col, op }) => ({
col: KEY_TO_LABEL_MAPPING[col],
- op: KEY_TO_LABEL_MAPPING[op]
+ op: KEY_TO_LABEL_MAPPING[op],
}));
}
if (payload.groups) {
- query.groups = payload.groups.map((group) => KEY_TO_LABEL_MAPPING[group]);
+ query.groups = payload.groups.map(group => KEY_TO_LABEL_MAPPING[group]);
}
if (payload.filters) {
- query.filters = payload.filters.map(({col, op, val}) => ({
+ query.filters = payload.filters.map(({ col, op, val }) => ({
col: KEY_TO_LABEL_MAPPING[col],
op,
- val
+ val,
}));
}
if (payload.orders) {
- query.orders = payload.orders.map(({col, asc}) => ({
+ query.orders = payload.orders.map(({ col, asc }) => ({
col,
- asc: KEY_TO_LABEL_MAPPING[asc]
+ asc: KEY_TO_LABEL_MAPPING[asc],
}));
}
if (payload.objectId) {
@@ -167,7 +167,7 @@ export const ActionTypes = keyMirror([
function AnalyticsQueryStore(state, action) {
action.app.setParseKeys();
- let urlPrefix = `/apps/${action.app.slug}/explorer`;
+ const urlPrefix = `/apps/${action.app.slug}/explorer`;
switch (action.type) {
case ActionTypes.LIST:
@@ -181,10 +181,10 @@ function AnalyticsQueryStore(state, action) {
} else {
type = 'recent';
}
- return get(`${urlPrefix}/more?type=${type}&skip=0`).then((results) => {
- let queries = {};
+ return get(`${urlPrefix}/more?type=${type}&skip=0`).then(results => {
+ const queries = {};
if (results) {
- results.forEach((payload) => {
+ results.forEach(payload => {
queries[payload.objectId] = payloadToQuery(payload);
});
}
@@ -192,18 +192,23 @@ function AnalyticsQueryStore(state, action) {
});
case ActionTypes.FETCH:
case ActionTypes.CREATE:
- return post(urlPrefix, queryToPayload(action.query)).then((result) => {
+ return post(urlPrefix, queryToPayload(action.query)).then(result => {
result.objectId = result.id;
- let realResult = result[LABEL_TO_KEY_MAPPING[action.query.source]];
- return state.setIn(['queries', result.id], { ...action.query, result: realResult });
+ const realResult = result[LABEL_TO_KEY_MAPPING[action.query.source]];
+ return state.setIn(['queries', result.id], {
+ ...action.query,
+ result: realResult,
+ });
});
case ActionTypes.UPDATE:
return put(`${urlPrefix}/${action.query.objectId}`, queryToPayload(action.query)).then(() => {
return state.setIn(['queries', action.query.objectId], action.query);
});
case ActionTypes.DELETE:
- return put(`${urlPrefix}/${action.query.objectId}`, { isSaved: false }).then(() => {
+ return put(`${urlPrefix}/${action.query.objectId}`, {
+ isSaved: false,
+ }).then(() => {
return state.deleteIn(['queries', action.query.objectId]);
});
}
diff --git a/src/lib/stores/ConfigStore.js b/src/lib/stores/ConfigStore.js
index d30907b800..84638e9d35 100644
--- a/src/lib/stores/ConfigStore.js
+++ b/src/lib/stores/ConfigStore.js
@@ -5,14 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import keyMirror from 'lib/keyMirror';
-import Parse from 'parse';
-import { Map } from 'immutable';
+import keyMirror from 'lib/keyMirror';
+import Parse from 'parse';
+import { Map } from 'immutable';
import { registerStore } from 'lib/stores/StoreManager';
export const ActionTypes = keyMirror(['FETCH', 'SET', 'DELETE']);
-
// Config state should be an Immutable Map with the following fields:
// - lastFetch: the last time all data was fetched from the server
// - params: An Immutable Map of parameter strings to values
@@ -22,30 +21,35 @@ function ConfigStore(state, action) {
action.app.setParseKeys();
switch (action.type) {
case ActionTypes.FETCH:
- return Parse._request(
- 'GET',
- 'config',
- {},
- { useMasterKey: true }
- ).then((result) => {
- return Map({ lastFetch: new Date(), params: Map(result.params), masterKeyOnly: Map(result.masterKeyOnly) });
+ return Parse._request('GET', 'config', {}, { useMasterKey: true }).then(result => {
+ return Map({
+ lastFetch: new Date(),
+ params: Map(result.params),
+ masterKeyOnly: Map(result.masterKeyOnly),
+ });
});
case ActionTypes.SET:
return Parse._request(
'PUT',
'config',
- { params: { [action.param]: Parse._encode(action.value) }, masterKeyOnly: { [action.param]: action.masterKeyOnly} },
+ {
+ params: { [action.param]: Parse._encode(action.value) },
+ masterKeyOnly: { [action.param]: action.masterKeyOnly },
+ },
{ useMasterKey: true }
).then(() => {
return state
- .setIn(['params', action.param], action.value)
- .setIn(['masterKeyOnly', action.param], action.masterKeyOnly);
+ .setIn(['params', action.param], action.value)
+ .setIn(['masterKeyOnly', action.param], action.masterKeyOnly);
});
case ActionTypes.DELETE:
return Parse._request(
'PUT',
'config',
- { params: { [action.param]: { __op: 'Delete' } }, masterKeyOnly: { [action.param]: { __op: 'Delete' } } },
+ {
+ params: { [action.param]: { __op: 'Delete' } },
+ masterKeyOnly: { [action.param]: { __op: 'Delete' } },
+ },
{ useMasterKey: true }
).then(() => {
return state.deleteIn(['params', action.param]);
diff --git a/src/lib/stores/JobsStore.js b/src/lib/stores/JobsStore.js
index 8147dbdec6..c8b8d3086d 100644
--- a/src/lib/stores/JobsStore.js
+++ b/src/lib/stores/JobsStore.js
@@ -5,17 +5,12 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import keyMirror from 'lib/keyMirror';
-import Parse from 'parse';
-import { Map, List } from 'immutable';
+import keyMirror from 'lib/keyMirror';
+import Parse from 'parse';
+import { Map, List } from 'immutable';
import { registerStore } from 'lib/stores/StoreManager';
-export const ActionTypes = keyMirror([
- 'FETCH',
- 'CREATE',
- 'EDIT',
- 'DELETE'
-]);
+export const ActionTypes = keyMirror(['FETCH', 'CREATE', 'EDIT', 'DELETE']);
// Jobs state should be an Immutable Map with the following fields:
// - lastFetch: the last time all data was fetched from the server
@@ -29,35 +24,42 @@ function JobsStore(state, action) {
return Promise.resolve(state);
}
path = 'cloud_code/jobs?per_page=50';
- return Parse._request('GET', path, {}, { useMasterKey: true}).then((results) => {
+ return Parse._request('GET', path, {}, { useMasterKey: true }).then(results => {
return Map({ lastFetch: new Date(), jobs: List(results) });
});
case ActionTypes.CREATE:
path = 'cloud_code/jobs';
- return Parse._request('POST', path, action.schedule, {useMasterKey: true}).then((result) => {
- let { ...schedule } = action.schedule.job_schedule;
+ return Parse._request('POST', path, action.schedule, {
+ useMasterKey: true,
+ }).then(result => {
+ const { ...schedule } = action.schedule.job_schedule;
schedule.objectId = result.objectId;
schedule.startAfter = schedule.startAfter || new Date().toISOString();
return state.set('jobs', state.get('jobs').push(schedule));
});
case ActionTypes.EDIT:
path = `cloud_code/jobs/${action.jobId}`;
- return Parse._request('PUT', path, action.updates, {useMasterKey: true}).then(() => {
- let index = state.get('jobs').findIndex((j) => j.objectId === action.jobId);
- let current = state.get('jobs').get(index);
- let { ...update } = action.updates.job_schedule;
+ return Parse._request('PUT', path, action.updates, {
+ useMasterKey: true,
+ }).then(() => {
+ const index = state.get('jobs').findIndex(j => j.objectId === action.jobId);
+ const current = state.get('jobs').get(index);
+ const { ...update } = action.updates.job_schedule;
update.objectId = current.objectId;
update.timeOfDay = update.timeOfDay || current.timeOfDay;
return state.set('jobs', state.get('jobs').set(index, update));
});
case ActionTypes.DELETE:
path = `cloud_code/jobs/${action.jobId}`;
- return Parse._request('DELETE', path, {}, {useMasterKey: true}).then(() => {
- let index = state.get('jobs').findIndex((j) => j.objectId === action.jobId);
- return state.set('jobs', state.get('jobs').delete(index));
- }, () => {
- return state;
- });
+ return Parse._request('DELETE', path, {}, { useMasterKey: true }).then(
+ () => {
+ const index = state.get('jobs').findIndex(j => j.objectId === action.jobId);
+ return state.set('jobs', state.get('jobs').delete(index));
+ },
+ () => {
+ return state;
+ }
+ );
}
}
diff --git a/src/lib/stores/PushAudiencesStore.js b/src/lib/stores/PushAudiencesStore.js
index 0cc42e05a8..3bd51623e7 100644
--- a/src/lib/stores/PushAudiencesStore.js
+++ b/src/lib/stores/PushAudiencesStore.js
@@ -5,10 +5,10 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import keyMirror from 'lib/keyMirror';
-import Parse from 'parse';
-import { List, Map } from 'immutable';
-import { registerStore } from 'lib/stores/StoreManager';
+import keyMirror from 'lib/keyMirror';
+import Parse from 'parse';
+import { List, Map } from 'immutable';
+import { registerStore } from 'lib/stores/StoreManager';
export const ActionTypes = keyMirror(['FETCH', 'CREATE', 'DESTROY', 'ABORT_FETCH']);
@@ -24,8 +24,10 @@ function PushAudiencesStore(state, action) {
action.app.setParseKeys();
switch (action.type) {
case ActionTypes.FETCH:
- if (state && new Date() - state.get('lastFetch') < LASTFETCHTIMEOUT) { //check for stale store
- if (state.get('audiences') && state.get('audiences').size >= (action.min || 0)) { //check for valid audience size
+ if (state && new Date() - state.get('lastFetch') < LASTFETCHTIMEOUT) {
+ //check for stale store
+ if (state.get('audiences') && state.get('audiences').size >= (action.min || 0)) {
+ //check for valid audience size
return Promise.resolve(state);
}
}
@@ -33,31 +35,43 @@ function PushAudiencesStore(state, action) {
const promise = Parse._request('GET', path, {}, { useMasterKey: true });
return promise.then(({ results, showMore }) => {
- return Map({ lastFetch: new Date(), audiences: List(results), showMore: showMore});
+ return Map({
+ lastFetch: new Date(),
+ audiences: List(results),
+ showMore: showMore,
+ });
});
case ActionTypes.CREATE:
- return Parse._request('POST', 'push_audiences', { query: action.query, name: action.name, }, { useMasterKey: true })
- .then(({ new_audience }) => {
- return state.update('audiences',(audiences) => {
- return audiences.unshift({
- createdAt: new Date(),
- name: action.name,
- objectId: new_audience ? new_audience.objectId || -1 : -1,
- count: 0,
- query: JSON.parse(action.query),
- });
- });
+ return Parse._request(
+ 'POST',
+ 'push_audiences',
+ { query: action.query, name: action.name },
+ { useMasterKey: true }
+ ).then(({ new_audience }) => {
+ return state.update('audiences', audiences => {
+ return audiences.unshift({
+ createdAt: new Date(),
+ name: action.name,
+ objectId: new_audience ? new_audience.objectId || -1 : -1,
+ count: 0,
+ query: JSON.parse(action.query),
});
+ });
+ });
case ActionTypes.DESTROY:
- return Parse._request('DELETE', `push_audiences/${action.objectId}`, {}, { useMasterKey: true })
- .then(() => {
- return state.update('audiences',(audiences) => {
- let index = audiences.findIndex(function(audience) {
- return audience.objectId === action.objectId;
- });
- return audiences.delete(index);
- });
+ return Parse._request(
+ 'DELETE',
+ `push_audiences/${action.objectId}`,
+ {},
+ { useMasterKey: true }
+ ).then(() => {
+ return state.update('audiences', audiences => {
+ const index = audiences.findIndex(function (audience) {
+ return audience.objectId === action.objectId;
});
+ return audiences.delete(index);
+ });
+ });
case ActionTypes.ABORT_FETCH:
return Promise.resolve(state);
}
diff --git a/src/lib/stores/SchemaStore.js b/src/lib/stores/SchemaStore.js
index 2faf186dd5..3bc2a6fe62 100644
--- a/src/lib/stores/SchemaStore.js
+++ b/src/lib/stores/SchemaStore.js
@@ -5,8 +5,8 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import keyMirror from 'lib/keyMirror';
-import { Map } from 'immutable';
+import keyMirror from 'lib/keyMirror';
+import { Map } from 'immutable';
import { registerStore } from 'lib/stores/StoreManager';
export const ActionTypes = keyMirror([
@@ -29,90 +29,90 @@ function SchemaStore(state, action) {
if (state && new Date() - state.get('lastFetch') < 60000) {
return Promise.resolve(state);
}
- return action.app.apiRequest(
- 'GET',
- 'schemas',
- {},
- { useMasterKey: true }
- ).then(({ results }) => {
- let classes = {};
- let CLPs = {};
- if (results) {
- results.forEach(({ className, fields, classLevelPermissions }) => {
- classes[className] = Map(fields);
- CLPs[className] = Map(classLevelPermissions);
+ return action.app
+ .apiRequest('GET', 'schemas', {}, { useMasterKey: true })
+ .then(({ results }) => {
+ const classes = {};
+ const CLPs = {};
+ if (results) {
+ results.forEach(({ className, fields, classLevelPermissions }) => {
+ classes[className] = Map(fields);
+ CLPs[className] = Map(classLevelPermissions);
+ });
+ }
+ return Map({
+ lastFetch: new Date(),
+ classes: Map(classes),
+ CLPs: Map(CLPs),
});
- }
- return Map({
- lastFetch: new Date(),
- classes: Map(classes),
- CLPs: Map(CLPs),
});
- });
case ActionTypes.CREATE_CLASS:
- return action.app.apiRequest(
- 'POST',
- 'schemas/' + action.className,
- { className: action.className },
- { useMasterKey: true }
- ).then(({ fields, classLevelPermissions }) => {
- return state
- .setIn(['classes', action.className], Map(fields))
- .setIn(['CLPs', action.className], Map(classLevelPermissions));
- });
+ return action.app
+ .apiRequest(
+ 'POST',
+ 'schemas/' + action.className,
+ { className: action.className },
+ { useMasterKey: true }
+ )
+ .then(({ fields, classLevelPermissions }) => {
+ return state
+ .setIn(['classes', action.className], Map(fields))
+ .setIn(['CLPs', action.className], Map(classLevelPermissions));
+ });
case ActionTypes.DROP_CLASS:
- return action.app.apiRequest(
- 'DELETE',
- 'schemas/' + action.className,
- {},
- { useMasterKey: true }
- ).then(() => {
- return state
- .deleteIn(['classes', action.className])
- .deleteIn(['CLPs', action.className]);
- });
+ return action.app
+ .apiRequest('DELETE', 'schemas/' + action.className, {}, { useMasterKey: true })
+ .then(() => {
+ return state.deleteIn(['classes', action.className]).deleteIn(['CLPs', action.className]);
+ });
case ActionTypes.ADD_COLUMN:
- let newField = {
+ const newField = {
[action.name]: {
type: action.columnType,
required: action.required,
- defaultValue: action.defaultValue
- }
+ defaultValue: action.defaultValue,
+ },
};
if (action.columnType === 'Pointer' || action.columnType === 'Relation') {
newField[action.name].targetClass = action.targetClass;
}
- return action.app.apiRequest(
- 'PUT',
- 'schemas/' + action.className,
- { className: action.className, fields: newField },
- { useMasterKey: true }
- ).then(({ fields }) => {
- return state.setIn(['classes', action.className], Map(fields));
- });
+ return action.app
+ .apiRequest(
+ 'PUT',
+ 'schemas/' + action.className,
+ { className: action.className, fields: newField },
+ { useMasterKey: true }
+ )
+ .then(({ fields }) => {
+ return state.setIn(['classes', action.className], Map(fields));
+ });
case ActionTypes.DROP_COLUMN:
- let droppedField = {
+ const droppedField = {
[action.name]: {
- __op: 'Delete'
- }
+ __op: 'Delete',
+ },
};
- return action.app.apiRequest(
- 'PUT',
- 'schemas/' + action.className,
- { className: action.className, fields: droppedField },
- { useMasterKey: true }
- ).then(({ fields }) => {
- return state.setIn(['classes', action.className], Map(fields));
- });
+ return action.app
+ .apiRequest(
+ 'PUT',
+ 'schemas/' + action.className,
+ { className: action.className, fields: droppedField },
+ { useMasterKey: true }
+ )
+ .then(({ fields }) => {
+ return state.setIn(['classes', action.className], Map(fields));
+ });
case ActionTypes.SET_CLP:
- return action.app.apiRequest(
- 'PUT',
- 'schemas/' + action.className,
- { classLevelPermissions: action.clp },
- { useMasterKey: true }
- ).then(({ classLevelPermissions, className }) => {
- return state.setIn(['CLPs', className], Map(classLevelPermissions));
- });
+ return action.app
+ .apiRequest(
+ 'PUT',
+ 'schemas/' + action.className,
+ { classLevelPermissions: action.clp },
+ { useMasterKey: true }
+ )
+ .then(({ classLevelPermissions, className }) => {
+ return state.setIn(['CLPs', className], Map(classLevelPermissions));
+ });
}
}
diff --git a/src/lib/stores/StateManager.js b/src/lib/stores/StateManager.js
index 216dcbfa67..d771efc945 100644
--- a/src/lib/stores/StateManager.js
+++ b/src/lib/stores/StateManager.js
@@ -42,7 +42,7 @@ export function setAppState(name, app, state) {
if (!name) {
throw new Error('Cannot set state without a state identifier');
}
- let prev = getAppState(app.applicationId, name);
+ const prev = getAppState(app.applicationId, name);
appStates[app.applicationId][name] = state;
return prev;
}
@@ -62,7 +62,7 @@ export function setGlobalState(name, state) {
if (!name) {
throw new Error('Cannot set state without a state identifier');
}
- let prev = getGlobalState(name);
+ const prev = getGlobalState(name);
globalStates[name] = state;
return prev;
}
diff --git a/src/lib/stores/StoreManager.js b/src/lib/stores/StoreManager.js
index dd12cbf4de..0f8b59eed4 100644
--- a/src/lib/stores/StoreManager.js
+++ b/src/lib/stores/StoreManager.js
@@ -7,50 +7,43 @@
*/
import * as StateManager from 'lib/stores/StateManager';
-let stores = {};
+const stores = {};
let subCount = 0;
export function registerStore(name, store, isGlobal) {
if (stores[name]) {
- throw new Error(
- 'Conflict! Attempted to register multiple stores with the name ' + name
- );
+ throw new Error('Conflict! Attempted to register multiple stores with the name ' + name);
}
stores[name] = {
store: store,
subscribers: {},
- isGlobal: !!isGlobal
+ isGlobal: !!isGlobal,
};
}
export function getStore(name) {
- let storeData = stores[name];
+ const storeData = stores[name];
if (!storeData) {
- throw new Error(
- 'Unknown store! Attempted to retrieve store with the name ' + name
- );
+ throw new Error('Unknown store! Attempted to retrieve store with the name ' + name);
}
- let stateGetter = (storeData.isGlobal ?
- StateManager.getGlobalState :
- StateManager.getAppState
- );
+ const stateGetter = storeData.isGlobal ? StateManager.getGlobalState : StateManager.getAppState;
return {
getData: stateGetter.bind(null, name),
isGlobal: storeData.isGlobal,
dispatch(type, params, app) {
- let action = {...params, type, app};
- let newState = storeData.store(stateGetter(name, app), action);
+ const action = { ...params, type, app };
+ const newState = storeData.store(stateGetter(name, app), action);
if (newState instanceof Promise) {
- return newState.then((result) => {
+ return newState.then(result => {
if (storeData.isGlobal) {
StateManager.setGlobalState(name, result);
} else {
StateManager.setAppState(name, app, result);
}
- for (let id in storeData.subscribers) {
+ for (const id in storeData.subscribers) {
storeData.subscribers[id](result);
}
});
@@ -60,18 +53,18 @@ export function getStore(name) {
} else {
StateManager.setAppState(name, app, newState);
}
- for (let id in storeData.subscribers) {
+ for (const id in storeData.subscribers) {
storeData.subscribers[id](newState);
}
}
},
subscribe(cb) {
- let id = 'sub' + subCount++;
+ const id = 'sub' + subCount++;
storeData.subscribers[id] = cb;
return id;
},
unsubscribe(id) {
delete storeData.subscribers[id];
- }
- }
+ },
+ };
}
diff --git a/src/lib/stores/WebhookStore.js b/src/lib/stores/WebhookStore.js
index 2e390efef6..11d203c353 100644
--- a/src/lib/stores/WebhookStore.js
+++ b/src/lib/stores/WebhookStore.js
@@ -5,14 +5,13 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
-import keyMirror from 'lib/keyMirror';
-import Parse from 'parse';
-import { Map, List } from 'immutable';
+import keyMirror from 'lib/keyMirror';
+import Parse from 'parse';
+import { Map, List } from 'immutable';
import { registerStore } from 'lib/stores/StoreManager';
export const ActionTypes = keyMirror(['FETCH', 'CREATE', 'EDIT', 'DELETE']);
-
// Webhook state should be an Immutable Map with the following fields:
// - lastFetch: the last time all data was fetched from the server
// - hooks: An Immutable List of hooks
@@ -21,26 +20,16 @@ function WebhookStore(state, action) {
action.app.setParseKeys();
switch (action.type) {
case ActionTypes.FETCH:
- let functionsPromise = Parse._request(
- 'GET',
- 'hooks/functions',
- {},
- { useMasterKey: true }
- );
- let triggersPromise = Parse._request(
- 'GET',
- 'hooks/triggers',
- {},
- { useMasterKey: true }
- );
- return Promise.all([functionsPromise, triggersPromise]).then((
- [functions,
- triggers]
- ) => {
- return Map({ lastFetch: new Date(), webhooks: List(functions.concat(triggers))});
+ const functionsPromise = Parse._request('GET', 'hooks/functions', {}, { useMasterKey: true });
+ const triggersPromise = Parse._request('GET', 'hooks/triggers', {}, { useMasterKey: true });
+ return Promise.all([functionsPromise, triggersPromise]).then(([functions, triggers]) => {
+ return Map({
+ lastFetch: new Date(),
+ webhooks: List(functions.concat(triggers)),
+ });
});
case ActionTypes.CREATE:
- let addHookToStore = hook => state.set('webhooks', state.get('webhooks').push(hook));
+ const addHookToStore = hook => state.set('webhooks', state.get('webhooks').push(hook));
if (action.functionName !== undefined) {
return Parse._request(
'POST',
@@ -72,10 +61,12 @@ function WebhookStore(state, action) {
url: action.hookURL,
},
{ useMasterKey: true }
- ).then((hook) => {
- let index = state.get('webhooks').findIndex(existingHook => existingHook.functionName === hook.functionName);
+ ).then(hook => {
+ const index = state
+ .get('webhooks')
+ .findIndex(existingHook => existingHook.functionName === hook.functionName);
return state.setIn(['webhooks', index], hook);
- })
+ });
} else {
return Parse._request(
'PUT',
@@ -85,9 +76,13 @@ function WebhookStore(state, action) {
},
{ useMasterKey: true }
).then(hook => {
- let index = state.get('webhooks').findIndex(existingHook =>
- existingHook.className === hook.className && existingHook.triggerName === hook.triggerName
- );
+ const index = state
+ .get('webhooks')
+ .findIndex(
+ existingHook =>
+ existingHook.className === hook.className &&
+ existingHook.triggerName === hook.triggerName
+ );
return state.setIn(['webhooks', index], hook);
});
}
@@ -99,7 +94,12 @@ function WebhookStore(state, action) {
{ __op: 'Delete' },
{ useMasterKey: true }
).then(() => {
- return state.set('webhooks', state.get('webhooks').filter(existingHook => existingHook.functionName != action.functionName));
+ return state.set(
+ 'webhooks',
+ state
+ .get('webhooks')
+ .filter(existingHook => existingHook.functionName != action.functionName)
+ );
});
} else {
return Parse._request(
@@ -108,9 +108,18 @@ function WebhookStore(state, action) {
{ __op: 'Delete' },
{ useMasterKey: true }
).then(() => {
- return state.set('webhooks', state.get('webhooks').filter(existingHook =>
- !(existingHook.className === action.triggerClass && existingHook.triggerName == action.triggerName)
- ));
+ return state.set(
+ 'webhooks',
+ state
+ .get('webhooks')
+ .filter(
+ existingHook =>
+ !(
+ existingHook.className === action.triggerClass &&
+ existingHook.triggerName == action.triggerName
+ )
+ )
+ );
});
}
}
diff --git a/src/lib/stringCompare.js b/src/lib/stringCompare.js
index e70d5295dc..ce8962f978 100644
--- a/src/lib/stringCompare.js
+++ b/src/lib/stringCompare.js
@@ -6,5 +6,5 @@
* the root directory of this source tree.
*/
export default function stringCompare(a, b) {
- return (a < b ? -1 : (a > b ? 1 : 0));
+ return a < b ? -1 : a > b ? 1 : 0;
}
diff --git a/src/lib/stringList.js b/src/lib/stringList.js
index f5779c21ba..398e640f72 100644
--- a/src/lib/stringList.js
+++ b/src/lib/stringList.js
@@ -17,7 +17,7 @@
* @return {String} human - friendly list
*/
export default function stringList(strings, endDelineator = 'and') {
- let progress = [];
+ const progress = [];
strings.forEach((s, i) => {
if (i > 0) {
if (i === strings.length - 1) {
diff --git a/src/lib/subscribeTo.js b/src/lib/subscribeTo.js
index 331b342012..67723570ab 100644
--- a/src/lib/subscribeTo.js
+++ b/src/lib/subscribeTo.js
@@ -10,7 +10,7 @@ import * as StoreManager from 'lib/stores/StoreManager';
import { CurrentApp } from 'context/currentApp';
export default function subscribeTo(name, prop) {
- return function(Component) {
+ return function (Component) {
const store = StoreManager.getStore(name);
const displayName = Component.displayName || Component.name || 'Component';
@@ -23,31 +23,31 @@ export default function subscribeTo(name, prop) {
}, [currentApp]);
React.useEffect(() => {
- const handleNewData = (newData) => {
+ const handleNewData = newData => {
if (data !== newData) {
setData(newData);
}
- }
+ };
const subscriptionId = store.subscribe(handleNewData);
return () => {
store.unsubscribe(subscriptionId);
- }
- }, [])
+ };
+ }, []);
- let dispatch = (type, params={}) => {
+ const dispatch = (type, params = {}) => {
if (store.isGlobal) {
return store.dispatch(type, params);
}
return store.dispatch(type, params, currentApp);
};
- let extras = {
+ const extras = {
[prop]: {
data,
dispatch,
- }
+ },
};
return ;
diff --git a/src/lib/tests/Authentication.test.js b/src/lib/tests/Authentication.test.js
index df2fe102de..98ed9a9b95 100644
--- a/src/lib/tests/Authentication.test.js
+++ b/src/lib/tests/Authentication.test.js
@@ -9,8 +9,8 @@ jest.dontMock('../../../Parse-Dashboard/Authentication.js');
jest.dontMock('bcryptjs');
const Authentication = require('../../../Parse-Dashboard/Authentication');
-const apps = [{appId: 'test123'}, {appId: 'test789'}];
-const readOnlyApps = apps.map((app) => {
+const apps = [{ appId: 'test123' }, { appId: 'test789' }];
+const readOnlyApps = apps.map(app => {
app.readOnly = true;
return app;
});
@@ -18,12 +18,12 @@ const readOnlyApps = apps.map((app) => {
const unencryptedUsers = [
{
user: 'parse.dashboard',
- pass: 'abc123'
+ pass: 'abc123',
},
{
user: 'parse.apps',
pass: 'xyz789',
- apps: apps
+ apps: apps,
},
{
user: 'parse.readonly',
@@ -34,21 +34,26 @@ const unencryptedUsers = [
user: 'parse.readonly.apps',
pass: 'abc123',
apps: readOnlyApps,
- }
+ },
];
const encryptedUsers = [
{
user: 'parse.dashboard',
- pass: '$2a$08$w92YfzwkhB3WGFTBjHwZLO2tSwNIS2rX0qQER.TF8izEzWF5M.U8S'
+ pass: '$2a$08$w92YfzwkhB3WGFTBjHwZLO2tSwNIS2rX0qQER.TF8izEzWF5M.U8S',
},
{
user: 'parse.apps',
pass: '$2a$08$B666bpJqE9v/R5KNbgfOMOjycvHzv6zWs0sGky/QuBZb4HY0M6LE2',
- apps: apps
- }
-]
+ apps: apps,
+ },
+];
-function createAuthenticationResult(isAuthenticated, matchingUsername, appsUserHasAccessTo, isReadOnly) {
+function createAuthenticationResult(
+ isAuthenticated,
+ matchingUsername,
+ appsUserHasAccessTo,
+ isReadOnly
+) {
isReadOnly = !!isReadOnly;
return {
isAuthenticated,
@@ -56,86 +61,102 @@ function createAuthenticationResult(isAuthenticated, matchingUsername, appsUserH
appsUserHasAccessTo,
isReadOnly,
otpMissingLength: false,
- otpValid: true
- }
+ otpValid: true,
+ };
}
describe('Authentication', () => {
it('does not authenticate with no users', () => {
- let authentication = new Authentication(null, false);
- expect(authentication.authenticate({name: 'parse.dashboard', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(null, false);
+ expect(authentication.authenticate({ name: 'parse.dashboard', pass: 'abc123' })).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('does not authenticate with no auth', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate(null))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate(null)).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('does not authenticate invalid user', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.invalid', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.invalid', pass: 'abc123' })).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('does not authenticate valid user with invalid unencrypted password', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.dashboard', pass: 'xyz789'}))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.dashboard', pass: 'xyz789' })).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('authenticates valid user with valid unencrypted password', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.dashboard', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(true, 'parse.dashboard', null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.dashboard', pass: 'abc123' })).toEqual(
+ createAuthenticationResult(true, 'parse.dashboard', null)
+ );
});
it('returns apps if valid user', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.apps', pass: 'xyz789'}))
- .toEqual(createAuthenticationResult(true, 'parse.apps', apps));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.apps', pass: 'xyz789' })).toEqual(
+ createAuthenticationResult(true, 'parse.apps', apps)
+ );
});
it('authenticates valid user with valid encrypted password', () => {
- let authentication = new Authentication(encryptedUsers, true);
- expect(authentication.authenticate({name: 'parse.dashboard', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(true, 'parse.dashboard', null));
+ const authentication = new Authentication(encryptedUsers, true);
+ expect(authentication.authenticate({ name: 'parse.dashboard', pass: 'abc123' })).toEqual(
+ createAuthenticationResult(true, 'parse.dashboard', null)
+ );
});
it('does not authenticate valid user with invalid encrypted password', () => {
- let authentication = new Authentication(encryptedUsers, true);
- expect(authentication.authenticate({name: 'parse.dashboard', pass: 'xyz789'}))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(encryptedUsers, true);
+ expect(authentication.authenticate({ name: 'parse.dashboard', pass: 'xyz789' })).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('authenticates valid user with valid username and usernameOnly', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.dashboard'}, true))
- .toEqual(createAuthenticationResult(true, 'parse.dashboard', null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.dashboard' }, true)).toEqual(
+ createAuthenticationResult(true, 'parse.dashboard', null)
+ );
});
it('does not authenticate valid user with valid username and no usernameOnly', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.dashboard'}))
- .toEqual(createAuthenticationResult(false, null, null));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.dashboard' })).toEqual(
+ createAuthenticationResult(false, null, null)
+ );
});
it('authenticates valid user with valid username and usernameOnly and encrypted password', () => {
- let authentication = new Authentication(encryptedUsers, true);
- expect(authentication.authenticate({name: 'parse.dashboard'}, true))
- .toEqual(createAuthenticationResult(true, 'parse.dashboard', null));
+ const authentication = new Authentication(encryptedUsers, true);
+ expect(authentication.authenticate({ name: 'parse.dashboard' }, true)).toEqual(
+ createAuthenticationResult(true, 'parse.dashboard', null)
+ );
});
it('makes readOnly auth when specified', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.readonly', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(true, 'parse.readonly', null, true));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(authentication.authenticate({ name: 'parse.readonly', pass: 'abc123' })).toEqual(
+ createAuthenticationResult(true, 'parse.readonly', null, true)
+ );
});
it('makes readOnly auth when specified in apps', () => {
- let authentication = new Authentication(unencryptedUsers, false);
- expect(authentication.authenticate({name: 'parse.readonly.apps', pass: 'abc123'}))
- .toEqual(createAuthenticationResult(true, 'parse.readonly.apps', readOnlyApps, false));
+ const authentication = new Authentication(unencryptedUsers, false);
+ expect(
+ authentication.authenticate({
+ name: 'parse.readonly.apps',
+ pass: 'abc123',
+ })
+ ).toEqual(createAuthenticationResult(true, 'parse.readonly.apps', readOnlyApps, false));
});
});
diff --git a/src/lib/tests/BrowserCell.test.js b/src/lib/tests/BrowserCell.test.js
index 00adbd2886..e2ea22b589 100644
--- a/src/lib/tests/BrowserCell.test.js
+++ b/src/lib/tests/BrowserCell.test.js
@@ -8,46 +8,44 @@
jest.dontMock('../../components/BrowserCell/BrowserCell.react');
jest.mock('idb-keyval');
-import React from 'react';
+import React from 'react';
import renderer from 'react-test-renderer';
const BrowserCell = require('../../components/BrowserCell/BrowserCell.react').default;
describe('BrowserCell', () => {
-
describe('Required fields', () => {
-
it('should not highlight 0 value', () => {
- const component = renderer.create(
-
- ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.props.className).not.toContain('required');
});
it('should not highlight false value', () => {
- const component = renderer.create(
-
- ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.props.className).not.toContain('required');
});
it('should not highlight empty string value', () => {
- const component = renderer.create(
-
- ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.props.className).not.toContain('required');
});
it('should highlight null value', () => {
- const component = renderer.create(
-
- ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.props.className).toContain('required');
});
it('should highlight undefined value', () => {
- const component = renderer.create(
-
- ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.props.className).toContain('required');
});
});
diff --git a/src/lib/tests/Button.test.js b/src/lib/tests/Button.test.js
index 70d8c38ce3..4d0d8ff91d 100644
--- a/src/lib/tests/Button.test.js
+++ b/src/lib/tests/Button.test.js
@@ -7,13 +7,13 @@
*/
jest.dontMock('../../components/Button/Button.react');
-import React from 'react';
+import React from 'react';
import renderer from 'react-test-renderer';
const Button = require('../../components/Button/Button.react').default;
describe('Button', () => {
it('has a default state', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer.create( ).toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable');
expect(component.children[0].type).toBe('span');
@@ -21,44 +21,50 @@ describe('Button', () => {
});
it('can be primary', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer.create( ).toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable primary');
});
it('can be colored', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer.create( ).toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable red');
});
it('can be colored and primary', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable primary red');
});
it('can be disabled', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable disabled');
});
it('special-cases white disabled buttons', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer
+ .create( )
+ .toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable disabled white');
});
it('can indidate progress', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer.create( ).toJSON();
expect(component.type).toBe('button');
expect(component.props.className).toBe('button unselectable progress');
});
it('can override width', () => {
- const component = renderer.create( ).toJSON();
+ const component = renderer.create( ).toJSON();
expect(component.type).toBe('button');
expect(component.props.style.width).toBe('300px');
});
diff --git a/src/lib/tests/Charting.test.js b/src/lib/tests/Charting.test.js
index 429cff1c0a..167cad33ce 100644
--- a/src/lib/tests/Charting.test.js
+++ b/src/lib/tests/Charting.test.js
@@ -10,63 +10,74 @@ const Charting = require('../Charting');
describe('timeAxisBuckets', () => {
it('determines an appropriate bucket size and boundaries', () => {
- expect(Charting.timeAxisBuckets(new Date(Date.UTC(2015, 2, 1, 10, 10, 10)), new Date(Date.UTC(2015, 2, 2, 10, 10, 10)))).toEqual(
- [
- new Date(Date.UTC(2015, 2, 1, 10, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 11, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 12, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 13, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 14, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 15, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 16, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 17, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 18, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 19, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 20, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 21, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 22, 0, 0)),
- new Date(Date.UTC(2015, 2, 1, 23, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 1, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 2, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 3, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 4, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 5, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 6, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 7, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 8, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 9, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 10, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 11, 0, 0)),
- ]
- );
+ expect(
+ Charting.timeAxisBuckets(
+ new Date(Date.UTC(2015, 2, 1, 10, 10, 10)),
+ new Date(Date.UTC(2015, 2, 2, 10, 10, 10))
+ )
+ ).toEqual([
+ new Date(Date.UTC(2015, 2, 1, 10, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 11, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 12, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 13, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 14, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 15, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 16, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 17, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 18, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 19, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 20, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 21, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 22, 0, 0)),
+ new Date(Date.UTC(2015, 2, 1, 23, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 1, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 2, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 3, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 4, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 5, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 6, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 7, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 8, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 9, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 10, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 11, 0, 0)),
+ ]);
- expect(Charting.timeAxisBuckets(new Date(Date.UTC(2015, 2, 1, 10, 10, 10)), new Date(Date.UTC(2015, 2, 7, 10, 10, 10)))).toEqual(
- [
- new Date(Date.UTC(2015, 2, 1, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 2, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 3, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 4, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 5, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 6, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 7, 0, 0, 0)),
- new Date(Date.UTC(2015, 2, 8, 0, 0, 0)),
- ]
- );
+ expect(
+ Charting.timeAxisBuckets(
+ new Date(Date.UTC(2015, 2, 1, 10, 10, 10)),
+ new Date(Date.UTC(2015, 2, 7, 10, 10, 10))
+ )
+ ).toEqual([
+ new Date(Date.UTC(2015, 2, 1, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 2, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 3, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 4, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 5, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 6, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 7, 0, 0, 0)),
+ new Date(Date.UTC(2015, 2, 8, 0, 0, 0)),
+ ]);
- expect(Charting.timeAxisBuckets(
- new Date(Date.UTC(2015, 2, 1, 10, 10, 10)),
- new Date(Date.UTC(2015, 3, 7, 10, 10, 10))
- ).length).toBe(39);
+ expect(
+ Charting.timeAxisBuckets(
+ new Date(Date.UTC(2015, 2, 1, 10, 10, 10)),
+ new Date(Date.UTC(2015, 3, 7, 10, 10, 10))
+ ).length
+ ).toBe(39);
- expect(Charting.timeAxisBuckets(new Date(Date.UTC(2015, 2, 1, 10, 10, 10)), new Date(Date.UTC(2015, 4, 1, 10, 10, 10)))).toEqual(
- [
- new Date(Date.UTC(2015, 2, 1, 0, 0, 0)),
- new Date(Date.UTC(2015, 3, 1, 0, 0, 0)),
- new Date(Date.UTC(2015, 4, 1, 0, 0, 0)),
- new Date(Date.UTC(2015, 5, 1, 0, 0, 0)),
- ]
- );
+ expect(
+ Charting.timeAxisBuckets(
+ new Date(Date.UTC(2015, 2, 1, 10, 10, 10)),
+ new Date(Date.UTC(2015, 4, 1, 10, 10, 10))
+ )
+ ).toEqual([
+ new Date(Date.UTC(2015, 2, 1, 0, 0, 0)),
+ new Date(Date.UTC(2015, 3, 1, 0, 0, 0)),
+ new Date(Date.UTC(2015, 4, 1, 0, 0, 0)),
+ new Date(Date.UTC(2015, 5, 1, 0, 0, 0)),
+ ]);
});
});
@@ -74,7 +85,9 @@ describe('valueAxisBuckets', () => {
it('determines an appropriate bucket size and boundaries', () => {
expect(Charting.valueAxisBuckets(4)).toEqual([0, 1, 2, 3, 4, 5]);
expect(Charting.valueAxisBuckets(6)).toEqual([0, 1, 2, 3, 4, 5, 6, 7]);
- expect(Charting.valueAxisBuckets(14)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
+ expect(Charting.valueAxisBuckets(14)).toEqual([
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ ]);
expect(Charting.valueAxisBuckets(15)).toEqual([0, 10, 20]);
expect(Charting.valueAxisBuckets(62)).toEqual([0, 10, 20, 30, 40, 50, 60, 70]);
expect(Charting.valueAxisBuckets(160)).toEqual([0, 100, 200]);
diff --git a/src/lib/tests/ColumnPreferences.test.js b/src/lib/tests/ColumnPreferences.test.js
index ff70a9e5f5..73aa216f11 100644
--- a/src/lib/tests/ColumnPreferences.test.js
+++ b/src/lib/tests/ColumnPreferences.test.js
@@ -11,14 +11,14 @@
jest.dontMock('../ColumnPreferences');
const ColumnPreferences = require('../ColumnPreferences');
-let mockStorage = {};
+const mockStorage = {};
window.localStorage = {
setItem(key, value) {
mockStorage[key] = value;
},
getItem(key) {
return mockStorage[key] || null;
- }
+ },
};
describe('ColumnPreferences', () => {
@@ -32,52 +32,260 @@ describe('ColumnPreferences', () => {
});
it('stores and retrieves preferences', () => {
- ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
- expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }]);
+ ColumnPreferences.updatePreferences(
+ [
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ],
+ 'testapp',
+ 'Klass'
+ );
+ expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ]);
});
it('separates preferences for different classes', () => {
- ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
- ColumnPreferences.updatePreferences([{ name: 'objectId', width: 150 }, { name: 'updatedAt', width: 120 }], 'testapp', '_User');
- expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }]);
- expect(ColumnPreferences.getPreferences('testapp', '_User')).toEqual([{ name: 'objectId', width: 150 }, { name: 'updatedAt', width: 120 }]);
+ ColumnPreferences.updatePreferences(
+ [
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ],
+ 'testapp',
+ 'Klass'
+ );
+ ColumnPreferences.updatePreferences(
+ [
+ { name: 'objectId', width: 150 },
+ { name: 'updatedAt', width: 120 },
+ ],
+ 'testapp',
+ '_User'
+ );
+ expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ]);
+ expect(ColumnPreferences.getPreferences('testapp', '_User')).toEqual([
+ { name: 'objectId', width: 150 },
+ { name: 'updatedAt', width: 120 },
+ ]);
});
it('can retrive column orderings', () => {
- ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
+ ColumnPreferences.updatePreferences(
+ [
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ],
+ 'testapp',
+ 'Klass'
+ );
expect(ColumnPreferences.getOrder({ objectId: {}, createdAt: {} }, 'testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'createdAt', width: 150, visible: true }]
+ [
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'createdAt',
+ width: 150,
+ visible: true,
+ },
+ ]
);
});
it('tacks unknown columns onto the end', () => {
- ColumnPreferences.updatePreferences([{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }], 'testapp', 'Klass');
- expect(ColumnPreferences.getOrder({ objectId: {}, updatedAt: {}, createdAt: {}, someField: {} }, 'testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'createdAt', width: 150, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }, { cached: true, required: false, name: 'someField', width: 150, visible: true }]
- );
- expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'createdAt', width: 150, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }, { cached: true, required: false, name: 'someField', width: 150, visible: true }]
+ ColumnPreferences.updatePreferences(
+ [
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ ],
+ 'testapp',
+ 'Klass'
);
+ expect(
+ ColumnPreferences.getOrder(
+ { objectId: {}, updatedAt: {}, createdAt: {}, someField: {} },
+ 'testapp',
+ 'Klass'
+ )
+ ).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'createdAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'someField',
+ width: 150,
+ visible: true,
+ },
+ ]);
+ expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'createdAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'someField',
+ width: 150,
+ visible: true,
+ },
+ ]);
});
it('removes columns that no longer exist', () => {
ColumnPreferences.updatePreferences(
- [{ name: 'objectId', width: 100 }, { name: 'createdAt', width: 150 }, { name: 'updatedAt', width: 150 }, { name: 'someField', width: 150 }],
+ [
+ { name: 'objectId', width: 100 },
+ { name: 'createdAt', width: 150 },
+ { name: 'updatedAt', width: 150 },
+ { name: 'someField', width: 150 },
+ ],
'testapp',
'Klass'
);
- expect(ColumnPreferences.getOrder({ objectId: {}, createdAt: {}, updatedAt: {} }, 'testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'createdAt', width: 150, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }]
- );
- expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'createdAt', width: 150, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }]
- );
+ expect(
+ ColumnPreferences.getOrder({ objectId: {}, createdAt: {}, updatedAt: {} }, 'testapp', 'Klass')
+ ).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'createdAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ ]);
+ expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'createdAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ ]);
- expect(ColumnPreferences.getOrder({ objectId: {}, updatedAt: {}, someField: {} }, 'testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }, { cached: true, required: false, name: 'someField', width: 150, visible: true }]
- );
- expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual(
- [{ cached: true, required: false, name: 'objectId', width: 100, visible: true }, { cached: true, required: false, name: 'updatedAt', width: 150, visible: true }, { cached: true, required: false, name: 'someField', width: 150, visible: true }]
- );
+ expect(
+ ColumnPreferences.getOrder({ objectId: {}, updatedAt: {}, someField: {} }, 'testapp', 'Klass')
+ ).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'someField',
+ width: 150,
+ visible: true,
+ },
+ ]);
+ expect(ColumnPreferences.getPreferences('testapp', 'Klass')).toEqual([
+ {
+ cached: true,
+ required: false,
+ name: 'objectId',
+ width: 100,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'updatedAt',
+ width: 150,
+ visible: true,
+ },
+ {
+ cached: true,
+ required: false,
+ name: 'someField',
+ width: 150,
+ visible: true,
+ },
+ ]);
});
});
diff --git a/src/lib/tests/DateUtils.test.js b/src/lib/tests/DateUtils.test.js
index 080fa67013..1b667ffeea 100644
--- a/src/lib/tests/DateUtils.test.js
+++ b/src/lib/tests/DateUtils.test.js
@@ -33,8 +33,8 @@ describe('shortMonth', () => {
describe('nextMonth', () => {
it('returns the first day of the next month', () => {
- let start = new Date(2001, 2, 3, 4, 5, 6);
- let next = DateUtils.nextMonth(start);
+ const start = new Date(2001, 2, 3, 4, 5, 6);
+ const next = DateUtils.nextMonth(start);
expect(next.getFullYear()).toBe(2001);
expect(next.getMonth()).toBe(3);
expect(next.getDate()).toBe(1);
@@ -46,8 +46,8 @@ describe('nextMonth', () => {
describe('prevMonth', () => {
it('returns the first day of the previous month', () => {
- let start = new Date(2001, 2, 3, 4, 5, 6);
- let next = DateUtils.prevMonth(start);
+ const start = new Date(2001, 2, 3, 4, 5, 6);
+ const next = DateUtils.prevMonth(start);
expect(next.getFullYear()).toBe(2001);
expect(next.getMonth()).toBe(1);
expect(next.getDate()).toBe(1);
diff --git a/src/lib/tests/Markdown.test.js b/src/lib/tests/Markdown.test.js
index f21e6de57b..cc7651b899 100644
--- a/src/lib/tests/Markdown.test.js
+++ b/src/lib/tests/Markdown.test.js
@@ -11,7 +11,7 @@ describe('Markdown', () => {
it('can render examples', () => {
jest.dontMock('../../components/Markdown/Markdown.example');
const example = require('../../components/Markdown/Markdown.example');
- example.demos.forEach((example) => {
+ example.demos.forEach(example => {
example.render();
});
});
diff --git a/src/lib/tests/PlatformCard.test.js b/src/lib/tests/PlatformCard.test.js
index 7dcb322a43..0430833674 100644
--- a/src/lib/tests/PlatformCard.test.js
+++ b/src/lib/tests/PlatformCard.test.js
@@ -11,7 +11,7 @@ describe('PlatformCard', () => {
it('can render examples', () => {
jest.dontMock('../../components/PlatformCard/PlatformCard.example');
const example = require('../../components/PlatformCard/PlatformCard.example');
- example.demos.forEach((example) => {
+ example.demos.forEach(example => {
example.render();
});
});
diff --git a/src/lib/tests/e2e.js b/src/lib/tests/e2e.js
index a1d72f6f96..3ab680b515 100644
--- a/src/lib/tests/e2e.js
+++ b/src/lib/tests/e2e.js
@@ -8,59 +8,61 @@
jest.disableAutomock();
-var express = require('express');
-var rp = require('request-promise');
-var ParseDashboard = require('../../../Parse-Dashboard/app');
+const express = require('express');
+const rp = require('request-promise');
+const ParseDashboard = require('../../../Parse-Dashboard/app');
-var dashboardSettings = {
- 'apps': [
+const dashboardSettings = {
+ apps: [
{
- 'serverURL': 'http://localhost:5051/parse',
- 'appId': 'appId',
- 'masterKey': 'masterKey',
- 'appName': 'MyApp'
- }
- ]
+ serverURL: 'http://localhost:5051/parse',
+ appId: 'appId',
+ masterKey: 'masterKey',
+ appName: 'MyApp',
+ },
+ ],
};
describe('e2e', () => {
- it('loads the dashboard on /dashboard', (done) => {
- let app = express();
+ it('loads the dashboard on /dashboard', done => {
+ const app = express();
let server;
- var p = new Promise(resolve => {
+ const p = new Promise(resolve => {
app.use('/dashboard', ParseDashboard(dashboardSettings));
server = app.listen(5051, resolve);
});
- return p.then(() => {
- return rp('http://localhost:5051/dashboard');
- })
- .then(result => {
- let bundleLocation = result.match(/