Skip to content

Commit 663a0fa

Browse files
committed
Merge pull request #192 from drew-gross/push-send-page
Push send page
2 parents 124919d + e3d36b7 commit 663a0fa

File tree

16 files changed

+276
-181
lines changed

16 files changed

+276
-181
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Parse Dashboard Changelog
2+
3+
### 1.0.6
4+
5+
* Send push notifications from the dashboard
6+
7+
### 1.0.5
8+
9+
* Fix new features notification
10+
11+
### 1.0.4
12+
13+
* Class level permissions editor
45.5 KB
Loading
22.8 KB
Loading
53.8 KB
Loading
52.5 KB
Loading

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"Logs Viewer",
77
"Parse Config",
88
"API Console",
9-
"Class Level Permissions Editor"
9+
"Class Level Permissions Editor",
10+
"Send Push Notifications"
1011
],
1112
"description": "The Parse Dashboard",
1213
"keywords": [
@@ -15,7 +16,7 @@
1516
],
1617
"homepage": "https://github.com/ParsePlatform/parse-dashboard",
1718
"bugs": "https://github.com/ParsePlatform/parse-dashboard/issues",
18-
"version": "1.0.5",
19+
"version": "1.0.6",
1920
"repository": {
2021
"type": "git",
2122
"url": "https://github.com/ParsePlatform/parse-dashboard"

src/components/PushAudienceDialog/PushAudienceDialog.react.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import TextInput from 'components/TextInput/TextInput.react';
2727
import Toggle from 'components/Toggle/Toggle.react';
2828
import { List, Map } from 'immutable';
2929

30+
const PARSE_SERVER_SUPPORTS_SAVED_AUDIENCES = false;
31+
const AUDIENCE_SIZE_FETCHING_ENABLED = false;
32+
3033
let filterFormatter = (filters, schema) => {
3134
return filters.map((filter) => {
3235
let type = schema[filter.get('field')];
@@ -174,11 +177,11 @@ export default class PushAudienceDialog extends React.Component {
174177
let audienceSize = PushUtils.formatCountDetails(this.state.audienceSize, this.state.approximate);
175178
let customFooter = (
176179
<div className={styles.footer}>
177-
<div
180+
{AUDIENCE_SIZE_FETCHING_ENABLED ? <div
178181
className={styles.audienceSize}>
179182
<div className={styles.audienceSizeText}>AUDIENCE SIZE</div>
180183
<div className={styles.audienceSizeDescription}>{audienceSize}</div>
181-
</div>
184+
</div> : null}
182185
<Button
183186
value='Cancel'
184187
onClick={this.props.secondaryAction}/>
@@ -202,12 +205,14 @@ export default class PushAudienceDialog extends React.Component {
202205
let futureUseSegment = [];
203206

204207
if (!this.props.disableNewSegment) {
205-
futureUseSegment.push(
206-
<Field
207-
key={'saveForFuture'}
208-
label={<Label text='Save this audience for future use?'/>}
209-
input={<Toggle value={this.state.saveForFuture} type={Toggle.Types.YES_NO} onChange={this.handleSaveForFuture.bind(this)} />} />
210-
);
208+
if (PARSE_SERVER_SUPPORTS_SAVED_AUDIENCES) {
209+
futureUseSegment.push(
210+
<Field
211+
key={'saveForFuture'}
212+
label={<Label text='Save this audience for future use?'/>}
213+
input={<Toggle value={this.state.saveForFuture} type={Toggle.Types.YES_NO} onChange={this.handleSaveForFuture.bind(this)} />} />
214+
);
215+
}
211216

212217
if (this.state.saveForFuture) {
213218
futureUseSegment.push(

src/components/PushAudiencesSelector/PushAudiencesOption.react.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import PushAudiencesBaseRow from 'components/PushAudiencesSelector/PushAudi
1919

2020
const FORM_PREFIX = 'audience_radio';
2121

22+
const AUDIENCE_SIZE_FETCHING_ENABLED = false;
23+
const AUDIENCE_CREATED_DATE_ENABLED = false;
24+
2225
export default class PushAudiencesOption extends PushAudiencesBaseRow {
2326
constructor() {
2427
super();
@@ -58,7 +61,6 @@ export default class PushAudiencesOption extends PushAudiencesBaseRow {
5861
</div>
5962
);
6063

61-
let countDetails = PushUtils.formatCountDetails(this.state.count, this.state.approximate);
6264
return (
6365
<label htmlFor={inputId} className={[styles.row, this.props.id === 'everyone' ? styles.everyone : ''].join(' ')}>
6466
<div className={[styles.cell, styles.col1].join(' ')}>
@@ -108,12 +110,12 @@ export default class PushAudiencesOption extends PushAudiencesBaseRow {
108110
</div>
109111
</div>
110112
</div>
111-
<div className={[styles.cell, styles.col2].join(' ')}>
112-
{countDetails}
113-
</div>
114-
<div className={[styles.cell, styles.col3].join(' ')}>
113+
{AUDIENCE_SIZE_FETCHING_ENABLED ? <div className={[styles.cell, styles.col2].join(' ')}>
114+
{PushUtils.formatCountDetails(this.state.count, this.state.approximate)}
115+
</div> : null}
116+
{AUDIENCE_CREATED_DATE_ENABLED ? <div className={[styles.cell, styles.col3].join(' ')}>
115117
{yearMonthDayFormatter(this.props.createdAt)}
116-
</div>
118+
</div> : null}
117119
</label>
118120
);
119121
}

src/components/PushAudiencesSelector/PushAudiencesSelector.react.js

Lines changed: 92 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,107 @@
55
* This source code is licensed under the license found in the LICENSE file in
66
* the root directory of this source tree.
77
*/
8+
import * as PushConstants from 'dashboard/Push/PushConstants.js';
89
import PropTypes from 'lib/PropTypes';
910
import PushAudiencesOption from 'components/PushAudiencesSelector/PushAudiencesOption.react';
1011
import React from 'react';
1112
import styles from 'components/PushAudiencesSelector/PushAudiencesSelector.scss';
13+
import { fromJS } from 'immutable';
1214

13-
let pushAudiencesHelper = (props, audiences) => {
14-
if (!audiences){
15-
return null;
16-
}
17-
let _audiences = [];
18-
audiences.map((data) => {
19-
_audiences.push(<PushAudiencesOption
20-
icon={data.icon}
21-
key={data.objectId}
22-
id={data.objectId}
23-
name={data.name}
24-
query={data.query}
25-
createdAt={new Date(data.createdAt)}
26-
isChecked={props.current === data.objectId ? true : false}
27-
onChange={props.onChange.bind(undefined, data.objectId, data.query)}
28-
onEditAudience={props.onEditAudience}
29-
schema={props.schema}
30-
filters={data.filters} />);
31-
});
32-
return _audiences;
33-
};
15+
const AUDIENCE_SIZE_FETCHING_ENABLED = false;
16+
const AUDIENCE_CREATED_DATE_ENABLED = false;
17+
18+
const PushAudiencesOptions = ({
19+
current,
20+
onChange,
21+
onEditAudience,
22+
schema,
23+
audiences,
24+
}) => <div>
25+
{audiences.map(({
26+
icon,
27+
objectId,
28+
name,
29+
query,
30+
createdAt,
31+
filters,
32+
}) => {
33+
const queryOrFilters = objectId === PushConstants.NEW_SEGMENT_ID ?
34+
filters.push(fromJS({
35+
field: 'deviceType',
36+
constraint: 'containedIn',
37+
array: query.deviceType['$in'],
38+
})) :
39+
query;
40+
return <div><PushAudiencesOption
41+
icon={icon}
42+
key={objectId}
43+
id={objectId}
44+
name={name}
45+
query={query}
46+
createdAt={new Date(createdAt)}
47+
isChecked={current === objectId ? true : false}
48+
//Super janky. Only works because open dashboard doesn't have saved audiences
49+
onChange={() => onChange(objectId, queryOrFilters)}
50+
onEditAudience={onEditAudience}
51+
schema={schema}
52+
filters={filters} />
53+
</div>
54+
})}
55+
</div>
3456

35-
let PushAudiencesSelector = (props) => {
36-
return (
37-
<div className={styles.container}>
38-
<div className={styles.body}>
39-
{pushAudiencesHelper(props, props.defaultAudience ? [props.defaultAudience] : null)}
40-
{pushAudiencesHelper(props, props.newSegment ? [props.newSegment] : null)}
41-
{pushAudiencesHelper(props, props.audiences)}
42-
</div>
43-
<div className={styles.header}>
44-
<div className={[styles.cell, styles.col1].join(' ')}>
45-
Audience
46-
</div>
47-
<div className={[styles.cell, styles.col2].join(' ')}>
48-
Size
49-
</div>
50-
<div className={[styles.cell, styles.col3].join(' ')}>
51-
Created
52-
</div>
53-
</div>
54-
{props.children}
57+
let PushAudiencesSelector = ({
58+
defaultAudience,
59+
newSegment,
60+
audiences,
61+
children,
62+
current,
63+
onChange,
64+
onEditAudience,
65+
schema,
66+
}) => <div className={styles.container}>
67+
<div className={styles.body}>
68+
<PushAudiencesOptions
69+
current={current}
70+
onChange={onChange}
71+
onEditAudience={onEditAudience}
72+
schema={schema}
73+
audiences={defaultAudience ? [defaultAudience] : []} />
74+
<PushAudiencesOptions
75+
current={current}
76+
onChange={onChange}
77+
onEditAudience={onEditAudience}
78+
schema={schema}
79+
audiences={newSegment ? [newSegment] : []} />
80+
<PushAudiencesOptions
81+
current={current}
82+
onChange={onChange}
83+
onEditAudience={onEditAudience}
84+
schema={schema}
85+
audiences={audiences} />
86+
</div>
87+
<div className={styles.header}>
88+
<div className={[styles.cell, styles.col1].join(' ')}>
89+
Audience
5590
</div>
56-
);
57-
}
91+
{AUDIENCE_SIZE_FETCHING_ENABLED ? <div className={[styles.cell, styles.col2].join(' ')}>
92+
Size
93+
</div> : null}
94+
{AUDIENCE_CREATED_DATE_ENABLED ? <div className={[styles.cell, styles.col3].join(' ')}>
95+
Created
96+
</div> : null}
97+
</div>
98+
{children}
99+
</div>
58100

59101
PushAudiencesSelector.propTypes = {
60-
audiences: PropTypes.object.isRequired.describe(
61-
'Immutable List of push audiences.'
62-
),
63-
defaultAudience: PropTypes.object.describe(
64-
'Default push audience option. Not added to the store. Everyone.'
65-
),
66-
newSegment: PropTypes.object.describe(
67-
'New segment (one time use) push audience option. Not added to the store.'
68-
),
69-
current: PropTypes.string.isRequired.describe(
70-
'id of the currently selected row.'
71-
),
72-
onChange: PropTypes.func.isRequired.describe(
73-
'callback to be executed when option has changed'
74-
),
75-
schema: PropTypes.object.describe(
76-
'Schema of installation'
77-
),
78-
onEditAudience: PropTypes.func.isRequired.describe(
79-
'Callback that is executed on click of edit audience for new segment.'
80-
),
102+
audiences: PropTypes.object.isRequired.describe('Immutable List of push audiences.'),
103+
defaultAudience: PropTypes.object.describe('Default push audience option. Not added to the store. Everyone.'),
104+
newSegment: PropTypes.object.describe('New segment (one time use) push audience option. Not added to the store.'),
105+
current: PropTypes.string.isRequired.describe('id of the currently selected row.'),
106+
onChange: PropTypes.func.isRequired.describe('callback to be executed when option has changed'),
107+
schema: PropTypes.object.describe('Schema of installation'),
108+
onEditAudience: PropTypes.func.isRequired.describe('Callback that is executed on click of edit audience for new segment.'),
81109
};
82110

83111
export default PushAudiencesSelector;

src/components/PushPreview/PushPreview.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
}
9494

9595
.ios {
96-
background-image: url(/images/lazer/push_previews/iphone.jpg);
96+
background-image: url(/images/iphone.jpg);
9797
background-size: 325px auto;
9898
font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
9999
color: white;
@@ -159,7 +159,7 @@
159159
}
160160

161161
.android {
162-
background-image: url(/images/lazer/push_previews/android.jpg);
162+
background-image: url(/images/android.jpg);
163163
background-size: 325px auto;
164164
font-family: 'Roboto', 'Helvetica Neue', 'Arial', sans-serif;
165165

@@ -229,7 +229,7 @@
229229
}
230230

231231
.osx {
232-
background-image: url(/images/lazer/push_previews/laptop.jpg);
232+
background-image: url(/images/laptop.jpg);
233233
background-size: 325px auto;
234234
font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;
235235
color: #555252;
@@ -272,7 +272,7 @@
272272
}
273273

274274
.windows {
275-
background-image: url(/images/lazer/push_previews/windowsphone.jpg);
275+
background-image: url(/images/windowsphone.jpg);
276276
background-size: 325px auto;
277277
font-family: 'Arial', sans-serif;
278278
color: white;

src/dashboard/Dashboard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ class Dashboard extends React.Component {
226226
<Route path='config' component={Config} />
227227
<Route path='api_console' component={ApiConsole} />
228228
<Route path='migration' component={Migration} />
229-
<Redirect from='push' to='/apps/:appId/push/activity/all' />
229+
<Redirect from='push' to='/apps/:appId/push/new' />
230230
<Redirect from='push/activity' to='/apps/:appId/push/activity/all' />
231231
<Route path='push/activity/:category' component={PushIndex} />
232232
<Route path='push/audiences' component={PushAudiencesIndex} />

src/dashboard/DashboardView.react.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,14 @@ export default class DashboardView extends React.Component {
9696
}
9797
let pushSubsections = [];
9898

99-
// The push UI requires immediate and scheduled push (and some ruby endpoints that we will have to remove)
100-
/*
101-
if (features.push && features.push.immediatePush && features.push.scheduledPush) {
102-
pushSubsections({
99+
if (features.push && features.push.immediatePush) {
100+
pushSubsections.push({
103101
name: 'Send New Push',
104-
link: '/push/activity'
102+
link: '/push/new'
105103
});
106104
}
105+
// The push UI requires immediate and scheduled push (and some ruby endpoints that we will have to remove)
106+
/*
107107
108108
if (features.push && features.push.storedPushData) {
109109
pushSubsections.push({

src/dashboard/Push/PushAudiencesData.react.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
*/
88
import * as PushAudiencesStore from 'lib/stores/PushAudiencesStore';
99
import * as PushConstants from './PushConstants';
10-
11-
import { center } from 'stylesheets/base.scss';
12-
import { List, Map } from 'immutable';
13-
1410
import Button from 'components/Button/Button.react';
1511
import LoaderContainer from 'components/LoaderContainer/LoaderContainer.react';
1612
import ParseApp from 'lib/ParseApp';
@@ -19,8 +15,9 @@ import PushAudienceDialog from 'components/PushAudienceDialog/PushAudienceD
1915
import PushAudiencesSelector from 'components/PushAudiencesSelector/PushAudiencesSelector.react';
2016
import queryFromFilters from 'lib/queryFromFilters';
2117
import React from 'react';
22-
2318
import styles from './PushAudiencesData.scss';
19+
import { center } from 'stylesheets/base.scss';
20+
import { List, Map } from 'immutable';
2421

2522
const XHR_KEY = 'PushAudiencesData';
2623

@@ -128,6 +125,11 @@ export default class PushAudiencesData extends React.Component {
128125

129126
query.deviceType = { $in: platforms };
130127

128+
// Horrible code here is due to old rails code that sent pushes through it's own endpoint, while Parse Server sends through Parse.Push.
129+
// Ideally, we would pass a Parse.Query around everywhere.
130+
parseQuery.containedIn('deviceType', platforms);
131+
this.props.onChange(saveForFuture ? (() => {throw "Audiences not supported"})() : PushConstants.NEW_SEGMENT_ID, parseQuery, 1 /* TODO: get the read device count */);
132+
131133
if (saveForFuture){
132134
this.props.pushAudiencesStore.dispatch(PushAudiencesStore.ActionTypes.CREATE, {
133135
query: JSON.stringify(query),

0 commit comments

Comments
 (0)