refactor(scheduler): move all event data into metadata
Summary: Moved events into metadata. Removed a lot of code Test Plan: todo Reviewers: juan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2866
|
@ -48,7 +48,7 @@ We're working on building a plugin index that makes it super easy to add them to
|
|||
#### Bundled Plugins
|
||||
Great starting points for creating your own plugins!
|
||||
- [Translate](https://github.com/nylas/N1/tree/master/internal_packages/composer-translate) — Works with 10 languages
|
||||
- [Scheduler](https://github.com/nylas/N1/tree/master/internal_packages/N1-Scheduler) — Show your availability to schedule a meeting with someone
|
||||
- [Scheduler](https://github.com/nylas/N1/tree/master/internal_packages/composer-scheduler) — Show your availability to schedule a meeting with someone
|
||||
- [Quick Replies](https://github.com/nylas/N1/tree/master/internal_packages/composer-templates) — Send emails faster with templates
|
||||
- [Send Later](https://github.com/nylas/N1/tree/master/internal_packages/send-later) — Schedule your emails to be sent at a later time
|
||||
- [Open Tracking](https://github.com/nylas/N1/tree/master/internal_packages/open-tracking) — See if your emails have been read
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
import NylasStore from 'nylas-store'
|
||||
import SchedulerActions from './scheduler-actions'
|
||||
import {Event, Message, Actions, DraftStore, DatabaseStore} from 'nylas-exports'
|
||||
import {PLUGIN_ID} from './scheduler-constants'
|
||||
|
||||
// moment-round upon require patches `moment` with new functions.
|
||||
require('moment-round')
|
||||
|
||||
/**
|
||||
* Maintains the creation of "Proposed Times" when scheduling with people.
|
||||
*
|
||||
* The proposed times are displayed in various calendar views.
|
||||
*
|
||||
*/
|
||||
class ProposedTimeMainWindowStore extends NylasStore {
|
||||
activate() {
|
||||
this.unsubscribers = [
|
||||
SchedulerActions.confirmChoices.listen(this._onConfirmChoices),
|
||||
]
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.unsubscribers.forEach(unsub => unsub())
|
||||
}
|
||||
|
||||
/**
|
||||
* This removes the metadata on the draft and creates an `Event` on
|
||||
* `draft.events`
|
||||
*/
|
||||
_convertToDraftEvent(draft) {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
return DraftStore.sessionForClientId(draft.clientId).then((session) => {
|
||||
if (metadata.pendingEvent) {
|
||||
const event = new Event().fromJSON(metadata.pendingEvent);
|
||||
session.changes.add({events: [event]});
|
||||
} else {
|
||||
session.changes.add({events: []})
|
||||
}
|
||||
|
||||
delete metadata.uid
|
||||
delete metadata.proposals
|
||||
delete metadata.pendingEvent
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
|
||||
return session.changes.commit()
|
||||
});
|
||||
}
|
||||
|
||||
_convertToPendingEvent(draft, proposals) {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
metadata.proposals = proposals;
|
||||
|
||||
// This is used to so the backend can reference which draft
|
||||
// corresponds to which sent message. The backend uses the key `uid`
|
||||
metadata.uid = draft.clientId;
|
||||
|
||||
if (draft.events.length > 0) {
|
||||
return DraftStore.sessionForClientId(draft.clientId).then((session) => {
|
||||
metadata.pendingEvent = draft.events[0].toJSON();
|
||||
session.changes.add({events: []});
|
||||
return session.changes.commit().then(() => {
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
})
|
||||
});
|
||||
}
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will bundle up and attach the choices as metadata on the draft.
|
||||
*
|
||||
* Once we attach metadata to the draft, we need to make sure we clear
|
||||
* the start and end times of the event.
|
||||
*/
|
||||
_onConfirmChoices = ({proposals, draftClientId}) => {
|
||||
this._pendingSave = true;
|
||||
this.trigger();
|
||||
|
||||
DatabaseStore.find(Message, draftClientId).then((draft) => {
|
||||
if (proposals.length === 0) {
|
||||
return this._convertToDraftEvent(draft)
|
||||
}
|
||||
return this._convertToPendingEvent(draft, proposals);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProposedTimeMainWindowStore()
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 788 B After Width: | Height: | Size: 788 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 511 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -2,8 +2,6 @@ import React, {Component, PropTypes} from 'react';
|
|||
import NewEventCard from './new-event-card'
|
||||
import {PLUGIN_ID} from '../scheduler-constants'
|
||||
import {Utils, Event, Actions, DraftStore} from 'nylas-exports';
|
||||
const MEETING_REQUEST = "MEETING_REQUEST"
|
||||
const PENDING_EVENT = "PENDING_EVENT"
|
||||
|
||||
/**
|
||||
* When you're creating an event you can either be creating:
|
||||
|
@ -11,22 +9,17 @@ const PENDING_EVENT = "PENDING_EVENT"
|
|||
* 1. A Meeting Request with a specific start and end time
|
||||
* 2. OR a `pendingEvent` template that has a set of proposed times.
|
||||
*
|
||||
* The former (1) is represented by an `Event` object on the `draft.events`
|
||||
* field of a draft.
|
||||
* Both are represented by a `pendingEvent` object on the `metadata` that
|
||||
* holds the JSONified representation of the `Event`
|
||||
*
|
||||
* The latter (2) is represented by a `pendingEvent` key on the metadata
|
||||
* of the `draft`.
|
||||
*
|
||||
* These are mutually exclusive and shouldn't exist at the same time on a
|
||||
* draft.
|
||||
* #2 adds a set of `proposals` on the metadata object.
|
||||
*/
|
||||
export default class NewEventCardContainer extends Component {
|
||||
static displayName = 'NewEventCardContainer';
|
||||
|
||||
static propTypes = {
|
||||
draftClientId: PropTypes.string,
|
||||
threadId: PropTypes.string,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -36,7 +29,7 @@ export default class NewEventCardContainer extends Component {
|
|||
this._usub = () => {}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentWillMount() {
|
||||
this._mounted = true;
|
||||
this._loadDraft(this.props.draftClientId);
|
||||
}
|
||||
|
@ -63,73 +56,30 @@ export default class NewEventCardContainer extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
_eventType(draft) {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID);
|
||||
const hasPendingEvent = metadata && metadata.pendingEvent
|
||||
if (draft.events && draft.events.length > 0) {
|
||||
if (hasPendingEvent) {
|
||||
throw new Error(`Assertion Failure. Can't have both a pendingEvent \
|
||||
and an event on a draft at the same time!`);
|
||||
}
|
||||
return MEETING_REQUEST
|
||||
} else if (hasPendingEvent) {
|
||||
return PENDING_EVENT
|
||||
_onDraftChange = () => {
|
||||
this.setState({event: this._getEvent()});
|
||||
}
|
||||
|
||||
_getEvent() {
|
||||
const metadata = this._session.draft().metadataForPluginId(PLUGIN_ID);
|
||||
if (metadata && metadata.pendingEvent) {
|
||||
return new Event().fromJSON(metadata.pendingEvent || {})
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
_onDraftChange = () => {
|
||||
const draft = this._session.draft();
|
||||
|
||||
let event = null;
|
||||
const eventType = this._eventType(draft)
|
||||
|
||||
if (eventType === MEETING_REQUEST) {
|
||||
event = draft.events[0]
|
||||
} else if (eventType === PENDING_EVENT) {
|
||||
event = this._getPendingEvent(draft.metadataForPluginId(PLUGIN_ID))
|
||||
}
|
||||
|
||||
this.setState({event});
|
||||
}
|
||||
|
||||
_getPendingEvent(metadata) {
|
||||
return new Event().fromJSON(metadata.pendingEvent || {})
|
||||
}
|
||||
|
||||
_updateDraftEvent(newData) {
|
||||
const draft = this._session.draft();
|
||||
const data = newData
|
||||
const event = Object.assign(draft.events[0].clone(), data);
|
||||
if (!Utils.isEqual(event, draft.events[0])) {
|
||||
this._session.changes.add({events: [event]}); // triggers draft change
|
||||
this._session.changes.commit();
|
||||
}
|
||||
}
|
||||
|
||||
_updatePendingEvent(newData) {
|
||||
_updateEvent = (newData) => {
|
||||
const draft = this._session.draft()
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID)
|
||||
const pendingEvent = Object.assign(this._getPendingEvent(metadata).clone(), newData)
|
||||
const pendingEventJSON = pendingEvent.toJSON()
|
||||
const newEvent = Object.assign(this._getEvent().clone(), newData)
|
||||
const pendingEventJSON = newEvent.toJSON()
|
||||
if (!Utils.isEqual(pendingEventJSON, metadata.pendingEvent)) {
|
||||
metadata.pendingEvent = pendingEventJSON;
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
_onEventChange = (newData) => {
|
||||
const eventType = this._eventType(this._session.draft());
|
||||
if (eventType === MEETING_REQUEST) {
|
||||
this._updateDraftEvent(newData)
|
||||
} else if (eventType === PENDING_EVENT) {
|
||||
this._updatePendingEvent(newData)
|
||||
}
|
||||
}
|
||||
|
||||
_onEventRemove = () => {
|
||||
this._session.changes.add({events: []});
|
||||
this._session.changes.commit();
|
||||
_removeEvent = () => {
|
||||
const draft = this._session.draft()
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID);
|
||||
if (metadata) {
|
||||
|
@ -145,8 +95,8 @@ and an event on a draft at the same time!`);
|
|||
card = (
|
||||
<NewEventCard event={this.state.event}
|
||||
draft={this._session.draft()}
|
||||
onRemove={this._onEventRemove}
|
||||
onChange={this._onEventChange}
|
||||
onRemove={this._removeEvent}
|
||||
onChange={this._updateEvent}
|
||||
onParticipantsClick={() => {}}
|
||||
/>
|
||||
)
|
|
@ -159,7 +159,12 @@ export default class NewEventCard extends React.Component {
|
|||
_renderTimePicker() {
|
||||
const metadata = this.props.draft.metadataForPluginId(PLUGIN_ID);
|
||||
if (metadata && metadata.proposals) {
|
||||
return <ProposedTimeList event={this.props.event} proposals={metadata.proposals} />
|
||||
return (
|
||||
<ProposedTimeList event={this.props.event}
|
||||
draft={this.props.draft}
|
||||
proposals={metadata.proposals}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const startVal = (this.props.event.start) * 1000;
|
|
@ -11,6 +11,7 @@ const TZ = moment.tz(Utils.timeZone).format("z");
|
|||
export default class ProposedTimeList extends React.Component {
|
||||
static propTypes = {
|
||||
draft: React.PropTypes.object,
|
||||
event: React.PropTypes.object,
|
||||
inEmail: React.PropTypes.bool,
|
||||
proposals: React.PropTypes.array.isRequired,
|
||||
}
|
||||
|
@ -49,7 +50,7 @@ export default class ProposedTimeList extends React.Component {
|
|||
<div>
|
||||
<h2 style={styles}>
|
||||
{this._renderB64Img("description")}
|
||||
{((this.props.draft.events || [])[0] || {}).title || this.props.draft.subject}
|
||||
{this.props.event.title || this.props.draft.subject}
|
||||
</h2>
|
||||
<span style={{margin: "0 10px"}}>
|
||||
{this._renderB64Img("time")}
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
Event,
|
||||
Actions,
|
||||
Calendar,
|
||||
APIError,
|
||||
NylasAPI,
|
||||
|
@ -54,61 +55,59 @@ export default class SchedulerComposerButton extends React.Component {
|
|||
}
|
||||
|
||||
_onDraftChange() {
|
||||
const draft = this._session.draft();
|
||||
this.setState({
|
||||
enabled: draft.events && draft.events.length > 0,
|
||||
});
|
||||
this.setState({enabled: this._hasPendingEvent()});
|
||||
}
|
||||
|
||||
_hasPendingEvent() {
|
||||
const metadata = this._session.draft().metadataForPluginId(PLUGIN_ID);
|
||||
return metadata && metadata.pendingEvent
|
||||
}
|
||||
|
||||
_onClick = () => {
|
||||
if (!this._session) { return }
|
||||
const draft = this._session.draft()
|
||||
if (draft.events.length === 0) { // API can only handle one event
|
||||
NylasAPI.authPlugin(PLUGIN_ID, PLUGIN_NAME, draft.accountId)
|
||||
.then(() => {
|
||||
DatabaseStore.findAll(Calendar, {accountId: draft.accountId})
|
||||
.then((allCalendars) => {
|
||||
if (allCalendars.length === 0) {
|
||||
throw new Error(`Can't create an event. The Account \
|
||||
${draft.accountId} has no calendars.`);
|
||||
}
|
||||
NylasAPI.authPlugin(PLUGIN_ID, PLUGIN_NAME, draft.accountId)
|
||||
.then(() => {
|
||||
DatabaseStore.findAll(Calendar, {accountId: draft.accountId})
|
||||
.then((allCalendars) => {
|
||||
if (allCalendars.length === 0) {
|
||||
throw new Error(`Can't create an event. The Account \
|
||||
${draft.accountId} has no calendars.`);
|
||||
}
|
||||
|
||||
const cals = allCalendars.filter(c => !c.readOnly);
|
||||
const cals = allCalendars.filter(c => !c.readOnly);
|
||||
|
||||
if (cals.length === 0) {
|
||||
NylasEnv.showErrorDialog(`This account has no editable \
|
||||
if (cals.length === 0) {
|
||||
NylasEnv.showErrorDialog(`This account has no editable \
|
||||
calendars. We can't create an event for you. Please make sure you have an \
|
||||
editable calendar with your account provider.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const start = moment().ceil(30, 'minutes');
|
||||
const end = moment(start).add(1, 'hour');
|
||||
|
||||
// TODO Have a default calendar config
|
||||
const event = new Event({
|
||||
end: end.unix(),
|
||||
start: start.unix(),
|
||||
calendarId: cals[0].id,
|
||||
});
|
||||
this._session.changes.add({events: [event]});
|
||||
this._session.changes.commit()
|
||||
})
|
||||
}).catch((error) => {
|
||||
let title = "Error"
|
||||
let msg = `Unfortunately scheduling is not currently available. \
|
||||
Please try again later.\n\nError: ${error}`
|
||||
if (!(error instanceof APIError)) {
|
||||
NylasEnv.reportError(error);
|
||||
} else if (error.statusCode === 400) {
|
||||
NylasEnv.reportError(error);
|
||||
} else if (NylasAPI.TimeoutErrorCodes.includes(error.statusCode)) {
|
||||
title = "Offline"
|
||||
msg = `Scheduling does not work offline. Please try again when you come back online.`
|
||||
return;
|
||||
}
|
||||
NylasEnv.showErrorDialog({title, message: msg});
|
||||
|
||||
const start = moment().ceil(30, 'minutes');
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
metadata.uid = draft.clientId;
|
||||
metadata.pendingEvent = new Event({
|
||||
calendarId: cals[0].id,
|
||||
start: start.unix(),
|
||||
end: moment(start).add(1, 'hour').unix(),
|
||||
}).toJSON();
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
let title = "Error"
|
||||
let msg = `Unfortunately scheduling is not currently available. \
|
||||
Please try again later.\n\nError: ${error}`
|
||||
if (!(error instanceof APIError)) {
|
||||
NylasEnv.reportError(error);
|
||||
} else if (error.statusCode === 400) {
|
||||
NylasEnv.reportError(error);
|
||||
} else if (NylasAPI.TimeoutErrorCodes.includes(error.statusCode)) {
|
||||
title = "Offline"
|
||||
msg = `Scheduling does not work offline. Please try again when you come back online.`
|
||||
}
|
||||
NylasEnv.showErrorDialog({title, message: msg});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -117,7 +116,7 @@ Please try again later.\n\nError: ${error}`
|
|||
onClick={this._onClick}
|
||||
title="Add an event…"
|
||||
>
|
||||
<RetinaImg url="nylas://N1-Scheduler/assets/ic-composer-scheduler@2x.png"
|
||||
<RetinaImg url="nylas://composer-scheduler/assets/ic-composer-scheduler@2x.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask}
|
||||
/>
|
||||
</button>)
|
|
@ -1,8 +1,7 @@
|
|||
import _ from 'underscore'
|
||||
import React from 'react'
|
||||
import {PLUGIN_ID} from '../scheduler-constants'
|
||||
import ProposedTimeList from './proposed-time-list'
|
||||
import {Actions, RegExpUtils, ComposerExtension} from 'nylas-exports'
|
||||
import {Event, Actions, RegExpUtils, ComposerExtension} from 'nylas-exports'
|
||||
|
||||
/**
|
||||
* Inserts the set of Proposed Times into the body of the HTML email.
|
||||
|
@ -45,7 +44,7 @@ export default class SchedulerComposerExtension extends ComposerExtension {
|
|||
return contentBefore + wrapS + markup + wrapE + contentAfter
|
||||
}
|
||||
|
||||
static _prepareEvent(inEvent, draft) {
|
||||
static _prepareEvent(inEvent, draft, metadata) {
|
||||
const event = inEvent
|
||||
if (!event.title || event.title.length === 0) {
|
||||
event.title = draft.subject;
|
||||
|
@ -58,15 +57,37 @@ export default class SchedulerComposerExtension extends ComposerExtension {
|
|||
status: "noreply",
|
||||
}
|
||||
})
|
||||
|
||||
if (metadata.proposals) {
|
||||
event.end = null
|
||||
event.start = null
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
// We must set the `preparedEvent` to be exactly what could be posted to
|
||||
// the /events endpoint of the API.
|
||||
static _cleanEventJSON(rawJSON) {
|
||||
const json = rawJSON;
|
||||
delete json.client_id;
|
||||
delete json.id;
|
||||
json.when = {
|
||||
object: "timespan",
|
||||
start: json._start,
|
||||
end: json._end,
|
||||
}
|
||||
delete json._start
|
||||
delete json._end
|
||||
return json
|
||||
}
|
||||
|
||||
static _insertProposalsIntoBody(draft, metadata) {
|
||||
const nextDraft = draft;
|
||||
if (metadata && metadata.proposals) {
|
||||
const el = React.createElement(ProposedTimeList,
|
||||
{
|
||||
draft: nextDraft,
|
||||
event: metadata.pendingEvent,
|
||||
inEmail: true,
|
||||
proposals: metadata.proposals,
|
||||
});
|
||||
|
@ -81,18 +102,11 @@ export default class SchedulerComposerExtension extends ComposerExtension {
|
|||
const self = SchedulerComposerExtension
|
||||
let nextDraft = draft.clone();
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID)
|
||||
|
||||
if (nextDraft.events.length > 0) {
|
||||
if (metadata && metadata.pendingEvent) {
|
||||
throw new Error(`Assertion Failure. Can't have both a pendingEvent \
|
||||
and an event on a draft at the same time!`);
|
||||
}
|
||||
const event = self._prepareEvent(nextDraft.events[0].clone(), draft)
|
||||
nextDraft.events = [event]
|
||||
} else if (metadata && metadata.pendingEvent) {
|
||||
nextDraft = self._insertProposalsIntoBody(nextDraft, metadata)
|
||||
const event = self._prepareEvent(_.clone(metadata.pendingEvent), draft);
|
||||
metadata.pendingEvent = event;
|
||||
if (metadata && metadata.pendingEvent) {
|
||||
nextDraft = self._insertProposalsIntoBody(nextDraft, metadata);
|
||||
const nextEvent = new Event().fromJSON(metadata.pendingEvent);
|
||||
const nextEventPrepared = self._prepareEvent(nextEvent, draft, metadata);
|
||||
metadata.pendingEvent = self._cleanEventJSON(nextEventPrepared.toJSON());
|
||||
Actions.setMetadata(nextDraft, PLUGIN_ID, metadata);
|
||||
}
|
||||
|
|
@ -6,14 +6,10 @@ import ProposedTimeCalendarStore from './proposed-time-calendar-store'
|
|||
import ProposedTimeMainWindowStore from './proposed-time-main-window-store'
|
||||
import SchedulerComposerExtension from './composer/scheduler-composer-extension';
|
||||
|
||||
import {PLUGIN_ID, PLUGIN_URL} from './scheduler-constants'
|
||||
|
||||
import {
|
||||
Actions,
|
||||
WorkspaceStore,
|
||||
ComponentRegistry,
|
||||
ExtensionRegistry,
|
||||
RegisterDraftForPluginTask,
|
||||
} from 'nylas-exports'
|
||||
|
||||
export function activate() {
|
||||
|
@ -40,22 +36,6 @@ export function activate() {
|
|||
{role: 'Composer:ActionButton'});
|
||||
|
||||
ExtensionRegistry.Composer.register(SchedulerComposerExtension)
|
||||
|
||||
const errorMessage = `There was a temporary problem setting up \
|
||||
these proposed times. Please manually follow up to schedule your event.`
|
||||
|
||||
this._usub = Actions.sendDraftSuccess.listen(({message, draftClientId}) => {
|
||||
if (!NylasEnv.isMainWindow()) return;
|
||||
if (message.metadataForPluginId(PLUGIN_ID)) {
|
||||
const task = new RegisterDraftForPluginTask({
|
||||
errorMessage,
|
||||
draftClientId,
|
||||
messageId: message.id,
|
||||
pluginServerUrl: `${PLUGIN_URL}/plugins/register-message`,
|
||||
});
|
||||
Actions.queueTask(task);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +52,5 @@ export function deactivate() {
|
|||
ComponentRegistry.unregister(NewEventCardContainer);
|
||||
ComponentRegistry.unregister(SchedulerComposerButton);
|
||||
ExtensionRegistry.Composer.unregister(SchedulerComposerExtension);
|
||||
this._usub()
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import _ from 'underscore'
|
||||
import NylasStore from 'nylas-store'
|
||||
import moment from 'moment'
|
||||
import Proposal from './proposal'
|
||||
import NylasStore from 'nylas-store'
|
||||
import SchedulerActions from './scheduler-actions'
|
||||
import {Event, Message, Actions, DraftStore, DatabaseStore} from 'nylas-exports'
|
||||
import {PLUGIN_ID, CALENDAR_ID} from './scheduler-constants'
|
||||
import {Event} from 'nylas-exports'
|
||||
import {CALENDAR_ID} from './scheduler-constants'
|
||||
|
||||
// moment-round upon require patches `moment` with new functions.
|
||||
require('moment-round')
|
||||
|
@ -99,71 +99,6 @@ class ProposedTimeCalendarStore extends NylasStore {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This removes the metadata on the draft and creates an `Event` on
|
||||
* `draft.events`
|
||||
*/
|
||||
_convertToDraftEvent(draft) {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
return DraftStore.sessionForClientId(draft.clientId).then((session) => {
|
||||
if (metadata.pendingEvent) {
|
||||
const event = new Event().fromJSON(metadata.pendingEvent);
|
||||
session.changes.add({events: [event]});
|
||||
} else {
|
||||
session.changes.add({events: []})
|
||||
}
|
||||
|
||||
delete metadata.uid
|
||||
delete metadata.proposals
|
||||
delete metadata.pendingEvent
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
|
||||
return session.changes.commit()
|
||||
});
|
||||
}
|
||||
|
||||
_convertToPendingEvent(draft, proposals) {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
metadata.proposals = proposals;
|
||||
|
||||
// This is used to so the backend can reference which draft
|
||||
// corresponds to which sent message. The backend uses the key `uid`
|
||||
metadata.uid = draft.clientId;
|
||||
|
||||
if (draft.events.length > 0) {
|
||||
return DraftStore.sessionForClientId(draft.clientId).then((session) => {
|
||||
metadata.pendingEvent = draft.events[0].toJSON();
|
||||
session.changes.add({events: []});
|
||||
return session.changes.commit().then(() => {
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
})
|
||||
});
|
||||
}
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will bundle up and attach the choices as metadata on the draft.
|
||||
*
|
||||
* Once we attach metadata to the draft, we need to make sure we clear
|
||||
* the start and end times of the event.
|
||||
*/
|
||||
_onConfirmChoices = (proposals) => {
|
||||
this._pendingSave = true;
|
||||
this.trigger();
|
||||
|
||||
const {draftClientId} = NylasEnv.getWindowProps();
|
||||
|
||||
DatabaseStore.find(Message, draftClientId).then((draft) => {
|
||||
if (proposals.length === 0) {
|
||||
return this._convertToDraftEvent(draft)
|
||||
}
|
||||
return this._convertToPendingEvent(draft, proposals);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProposedTimeCalendarStore()
|
|
@ -0,0 +1,46 @@
|
|||
import NylasStore from 'nylas-store'
|
||||
import SchedulerActions from './scheduler-actions'
|
||||
import {Message, Actions, DatabaseStore} from 'nylas-exports'
|
||||
import {PLUGIN_ID} from './scheduler-constants'
|
||||
|
||||
// moment-round upon require patches `moment` with new functions.
|
||||
require('moment-round')
|
||||
|
||||
/**
|
||||
* Maintains the creation of "Proposed Times" when scheduling with people.
|
||||
*
|
||||
* The proposed times are displayed in various calendar views.
|
||||
*
|
||||
*/
|
||||
class ProposedTimeMainWindowStore extends NylasStore {
|
||||
activate() {
|
||||
this.unsubscribers = [
|
||||
SchedulerActions.confirmChoices.listen(this._onConfirmChoices),
|
||||
]
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.unsubscribers.forEach(unsub => unsub())
|
||||
}
|
||||
|
||||
/**
|
||||
* This will bundle up and attach the choices as metadata on the draft.
|
||||
*
|
||||
* Once we attach metadata to the draft, we need to make sure we clear
|
||||
* the start and end times of the event.
|
||||
*/
|
||||
_onConfirmChoices = ({proposals = [], draftClientId}) => {
|
||||
this._pendingSave = true;
|
||||
this.trigger();
|
||||
|
||||
DatabaseStore.find(Message, draftClientId).then((draft) => {
|
||||
const metadata = draft.metadataForPluginId(PLUGIN_ID) || {};
|
||||
if (proposals.length === 0) {
|
||||
delete metadata.proposals
|
||||
}
|
||||
Actions.setMetadata(draft, PLUGIN_ID, metadata);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProposedTimeMainWindowStore()
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "N1-Scheduler",
|
||||
"title":"N1 Scheduler",
|
||||
"name": "composer-scheduler",
|
||||
"title":"Scheduler",
|
||||
"description": "The easiest way to schedule events",
|
||||
"main": "./lib/main",
|
||||
"version": "0.1.0",
|
|
@ -1,13 +1,10 @@
|
|||
import {
|
||||
Actions,
|
||||
ComponentRegistry,
|
||||
ExtensionRegistry,
|
||||
RegisterDraftForPluginTask,
|
||||
} from 'nylas-exports';
|
||||
import LinkTrackingButton from './link-tracking-button';
|
||||
import LinkTrackingComposerExtension from './link-tracking-composer-extension';
|
||||
import LinkTrackingMessageExtension from './link-tracking-message-extension';
|
||||
import {PLUGIN_ID, PLUGIN_URL} from './link-tracking-constants'
|
||||
|
||||
|
||||
export function activate() {
|
||||
|
@ -17,22 +14,6 @@ export function activate() {
|
|||
ExtensionRegistry.Composer.register(LinkTrackingComposerExtension);
|
||||
|
||||
ExtensionRegistry.MessageView.register(LinkTrackingMessageExtension);
|
||||
|
||||
const errorMessage = `There was a problem saving your link tracking \
|
||||
settings. This message will not have link tracking.`
|
||||
|
||||
this._usub = Actions.sendDraftSuccess.listen(({message, draftClientId}) => {
|
||||
if (!NylasEnv.isMainWindow()) return;
|
||||
if (message.metadataForPluginId(PLUGIN_ID)) {
|
||||
const task = new RegisterDraftForPluginTask({
|
||||
errorMessage,
|
||||
draftClientId,
|
||||
messageId: message.id,
|
||||
pluginServerUrl: `${PLUGIN_URL}/plugins/register-message`,
|
||||
});
|
||||
Actions.queueTask(task);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function serialize() {}
|
||||
|
@ -41,5 +22,4 @@ export function deactivate() {
|
|||
ComponentRegistry.unregister(LinkTrackingButton);
|
||||
ExtensionRegistry.Composer.unregister(LinkTrackingComposerExtension);
|
||||
ExtensionRegistry.MessageView.unregister(LinkTrackingMessageExtension);
|
||||
this._usub()
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import {
|
||||
Actions,
|
||||
ComponentRegistry,
|
||||
ExtensionRegistry,
|
||||
RegisterDraftForPluginTask,
|
||||
} from 'nylas-exports';
|
||||
import OpenTrackingButton from './open-tracking-button';
|
||||
import OpenTrackingIcon from './open-tracking-icon';
|
||||
import OpenTrackingMessageStatus from './open-tracking-message-status';
|
||||
import OpenTrackingComposerExtension from './open-tracking-composer-extension';
|
||||
import {PLUGIN_ID, PLUGIN_URL} from './open-tracking-constants'
|
||||
|
||||
export function activate() {
|
||||
ComponentRegistry.register(OpenTrackingButton,
|
||||
|
@ -21,22 +18,6 @@ export function activate() {
|
|||
{role: 'MessageHeaderStatus'});
|
||||
|
||||
ExtensionRegistry.Composer.register(OpenTrackingComposerExtension);
|
||||
|
||||
const errorMessage = `There was a problem saving your read receipt \
|
||||
settings. You will not get a read receipt for this message.`
|
||||
|
||||
this._usub = Actions.sendDraftSuccess.listen(({message, draftClientId}) => {
|
||||
if (!NylasEnv.isMainWindow()) return;
|
||||
if (message.metadataForPluginId(PLUGIN_ID)) {
|
||||
const task = new RegisterDraftForPluginTask({
|
||||
errorMessage,
|
||||
draftClientId,
|
||||
messageId: message.id,
|
||||
pluginServerUrl: `${PLUGIN_URL}/plugins/register-message`,
|
||||
});
|
||||
Actions.queueTask(task);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function serialize() {}
|
||||
|
@ -46,5 +27,4 @@ export function deactivate() {
|
|||
ComponentRegistry.unregister(OpenTrackingIcon);
|
||||
ComponentRegistry.unregister(OpenTrackingMessageStatus);
|
||||
ExtensionRegistry.Composer.unregister(OpenTrackingComposerExtension);
|
||||
this._usub()
|
||||
}
|
||||
|
|
|
@ -9,8 +9,11 @@ import {
|
|||
SendDraftTask,
|
||||
NylasAPI,
|
||||
SoundRegistry,
|
||||
SyncbackMetadataTask,
|
||||
} from 'nylas-exports';
|
||||
|
||||
import NotifyPluginsOfSendTask from '../../src/flux/tasks/notify-plugis-of-send-task'
|
||||
|
||||
const DBt = DatabaseTransaction.prototype;
|
||||
const withoutWhitespace = (s) => s.replace(/[\n\r\s]/g, '');
|
||||
|
||||
|
@ -169,7 +172,8 @@ describe("SendDraftTask", () => {
|
|||
it("should queue tasks to sync back the metadata on the new message", () => {
|
||||
spyOn(Actions, 'queueTask')
|
||||
waitsForPromise(() => this.task.performRemote().then(() => {
|
||||
const metadataTasks = Actions.queueTask.calls.map((call) => call.args[0]);
|
||||
let metadataTasks = Actions.queueTask.calls.map((call) => call.args[0]);
|
||||
metadataTasks = metadataTasks.filter((task) => task instanceof SyncbackMetadataTask)
|
||||
expect(metadataTasks.length).toEqual(this.draft.pluginMetadata.length);
|
||||
this.draft.pluginMetadata.forEach((pluginMetadatum, idx) => {
|
||||
expect(metadataTasks[idx].clientId).toEqual(this.draft.clientId);
|
||||
|
@ -179,6 +183,27 @@ describe("SendDraftTask", () => {
|
|||
}));
|
||||
});
|
||||
|
||||
it("should queue a task to register the messageID with the plugin server", () => {
|
||||
spyOn(Actions, 'queueTask')
|
||||
waitsForPromise(() => this.task.performRemote().then(() => {
|
||||
let tasks = Actions.queueTask.calls.map((call) => call.args[0]);
|
||||
tasks = tasks.filter((task) => task instanceof NotifyPluginsOfSendTask)
|
||||
expect(tasks.length).toEqual(1);
|
||||
expect(tasks[0].accountId).toEqual(this.draft.accountId);
|
||||
expect(tasks[0].messageId).toEqual(this.response.id);
|
||||
}));
|
||||
});
|
||||
|
||||
it("shouldn't queue a NotifyPluginsOfSendTask if there's no metadata", () => {
|
||||
spyOn(Actions, 'queueTask');
|
||||
this.draft.pluginMetadata = []
|
||||
waitsForPromise(() => this.task.performRemote().then(() => {
|
||||
let tasks = Actions.queueTask.calls.map((call) => call.args[0]);
|
||||
tasks = tasks.filter((task) => task instanceof NotifyPluginsOfSendTask)
|
||||
expect(tasks.length).toEqual(0);
|
||||
}));
|
||||
});
|
||||
|
||||
it("should play a sound", () => {
|
||||
spyOn(NylasEnv.config, "get").andReturn(true)
|
||||
waitsForPromise(() => this.task.performRemote().then(() => {
|
||||
|
|
|
@ -78,7 +78,7 @@ class Application
|
|||
|
||||
if not @config.get('core.disabledPackagesInitialized')
|
||||
exampleNewNames = {
|
||||
'N1-Scheduler': 'N1-Scheduler',
|
||||
'N1-Scheduler': 'composer-scheduler',
|
||||
'N1-Composer-Templates': 'composer-templates',
|
||||
'N1-Composer-Translate': 'composer-translate',
|
||||
'N1-Message-View-on-Github':'message-view-on-github',
|
||||
|
|
|
@ -84,8 +84,11 @@ export default class TimePicker extends React.Component {
|
|||
el.setSelectionRange(0, el.value.length)
|
||||
}
|
||||
|
||||
_onBlur = () => {
|
||||
this.setState({focused: false})
|
||||
_onBlur = (event) => {
|
||||
this.setState({focused: false});
|
||||
if (Array.from(event.relatedTarget.classList).includes("time-options")) {
|
||||
return
|
||||
}
|
||||
this._saveIfValid(this.state.rawText)
|
||||
}
|
||||
|
||||
|
@ -192,7 +195,7 @@ export default class TimePicker extends React.Component {
|
|||
})
|
||||
|
||||
return (
|
||||
<div className={className}>{opts}</div>
|
||||
<div className={className} tabIndex={-1}>{opts}</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ FocusedContentStore = require './focused-content-store'
|
|||
BaseDraftTask = require '../tasks/base-draft-task'
|
||||
SendDraftTask = require '../tasks/send-draft-task'
|
||||
SyncbackDraftFilesTask = require '../tasks/syncback-draft-files-task'
|
||||
SyncbackDraftEventsTask = require '../tasks/syncback-draft-events-task'
|
||||
SyncbackDraftTask = require '../tasks/syncback-draft-task'
|
||||
DestroyDraftTask = require '../tasks/destroy-draft-task'
|
||||
|
||||
|
@ -342,8 +341,6 @@ class DraftStore
|
|||
_queueDraftAssetTasks: (draft) =>
|
||||
if draft.files.length > 0 or draft.uploads.length > 0
|
||||
Actions.queueTask(new SyncbackDraftFilesTask(draft.clientId))
|
||||
if draft.events.length
|
||||
Actions.queueTask(new SyncbackDraftEventsTask(draft.clientId))
|
||||
|
||||
_isPopout: ->
|
||||
NylasEnv.getWindowType() is "composer"
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import Task from './task'
|
||||
import {APIError} from '../errors'
|
||||
import NylasAPI from '../nylas-api'
|
||||
|
||||
// We use our local `request` so we can track the outgoing calls and
|
||||
// generate consistent error objects
|
||||
import nylasRequest from '../../nylas-request'
|
||||
import EdgehillAPI from '../edgehill-api'
|
||||
import SyncbackMetadataTask from './syncback-metadata-task'
|
||||
|
||||
/**
|
||||
* If a plugin:
|
||||
|
@ -50,45 +48,56 @@ import nylasRequest from '../../nylas-request'
|
|||
* The task will POST to your backend url the draftClientId and the
|
||||
* coresponding messageId
|
||||
*/
|
||||
export default class RegisterDraftForPluginTask extends Task {
|
||||
export default class NotifyPluginsOfSendTask extends Task {
|
||||
constructor(opts = {}) {
|
||||
super(opts)
|
||||
this.accountId = opts.accountId
|
||||
this.messageId = opts.messageId
|
||||
this.errorMessage = opts.errorMessage
|
||||
this.draftClientId = opts.draftClientId
|
||||
this.pluginServerUrl = opts.pluginServerUrl
|
||||
this.messageClientId = opts.messageClientId
|
||||
this.errorMessage = `We had trouble connecting to the plugin server. \
|
||||
Any plugins you used in your sent message will not be available.`
|
||||
}
|
||||
|
||||
isDependentOnTask(other) {
|
||||
return (other instanceof SyncbackMetadataTask) && (other.clientId === this.messageClientId)
|
||||
}
|
||||
|
||||
performLocal() {
|
||||
this.validateRequiredFields([
|
||||
"messageId",
|
||||
"draftClientId",
|
||||
"pluginServerUrl",
|
||||
"accountId",
|
||||
"messageClientId",
|
||||
]);
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
performRemote() {
|
||||
return new Promise((resolve) => {
|
||||
nylasRequest.post({url: this.pluginServerUrl, body: {
|
||||
message_id: this.messageId,
|
||||
uid: this.draftClientId,
|
||||
}}, (err) => {
|
||||
if (err instanceof APIError) {
|
||||
const msg = `${this.errorMessage}\n\n${err.message}`
|
||||
if (NylasAPI.PermanentErrorCodes.includes(err.statusCode)) {
|
||||
NylasEnv.showErrorDialog(msg, {showInMainWindow: true})
|
||||
return resolve([Task.Status.Failed, err])
|
||||
EdgehillAPI.request({
|
||||
method: "POST",
|
||||
path: "/plugins/send-successful",
|
||||
body: {
|
||||
message_id: this.messageId,
|
||||
account_id: this.accountId,
|
||||
},
|
||||
success: () => {
|
||||
return resolve(Task.Status.Success)
|
||||
},
|
||||
error: (err) => {
|
||||
if (err instanceof APIError) {
|
||||
const msg = `${this.errorMessage}\n\n${err.message}`
|
||||
if (NylasAPI.PermanentErrorCodes.includes(err.statusCode)) {
|
||||
NylasEnv.showErrorDialog(msg, {showInMainWindow: true})
|
||||
return resolve([Task.Status.Failed, err])
|
||||
}
|
||||
return resolve(Task.Status.Retry)
|
||||
}
|
||||
return resolve(Task.Status.Retry)
|
||||
} else if (err) {
|
||||
const msg = `${this.errorMessage}\n\n${err.message}`
|
||||
NylasEnv.reportError(err);
|
||||
NylasEnv.showErrorDialog(msg, {showInMainWindow: true})
|
||||
return resolve([Task.Status.Failed, err])
|
||||
}
|
||||
return resolve(Task.Status.Success)
|
||||
},
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import DatabaseStore from '../stores/database-store';
|
|||
import AccountStore from '../stores/account-store';
|
||||
import BaseDraftTask from './base-draft-task';
|
||||
import SyncbackMetadataTask from './syncback-metadata-task';
|
||||
import NotifyPluginsOfSendTask from './notify-plugins-of-send-task';
|
||||
|
||||
export default class SendDraftTask extends BaseDraftTask {
|
||||
|
||||
|
@ -38,6 +39,7 @@ export default class SendDraftTask extends BaseDraftTask {
|
|||
)
|
||||
);
|
||||
})
|
||||
.then(this.updatePluginMetadata)
|
||||
.then(this.onSuccess)
|
||||
.catch(this.onError);
|
||||
}
|
||||
|
@ -89,13 +91,26 @@ export default class SendDraftTask extends BaseDraftTask {
|
|||
});
|
||||
}
|
||||
|
||||
onSuccess = () => {
|
||||
// Queue a task to save metadata on the message
|
||||
updatePluginMetadata = () => {
|
||||
this.message.pluginMetadata.forEach((m) => {
|
||||
const task = new SyncbackMetadataTask(this.message.clientId, this.message.constructor.name, m.pluginId);
|
||||
Actions.queueTask(task);
|
||||
const t1 = new SyncbackMetadataTask(this.message.clientId,
|
||||
this.message.constructor.name, m.pluginId);
|
||||
Actions.queueTask(t1);
|
||||
});
|
||||
|
||||
if (this.message.pluginMetadata.length > 0) {
|
||||
const t2 = new NotifyPluginsOfSendTask({
|
||||
accountId: this.message.accountId,
|
||||
messageId: this.message.id,
|
||||
messageClientId: this.message.clientId,
|
||||
});
|
||||
Actions.queueTask(t2);
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
onSuccess = () => {
|
||||
Actions.sendDraftSuccess({message: this.message, messageClientId: this.message.clientId, draftClientId: this.draftClientId});
|
||||
NylasAPI.makeDraftDeletionRequest(this.draft);
|
||||
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
import Task from './task';
|
||||
import {APIError} from '../errors';
|
||||
import NylasAPI from '../nylas-api';
|
||||
import BaseDraftTask from './base-draft-task';
|
||||
import DatabaseStore from '../stores/database-store';
|
||||
import Event from '../models/event';
|
||||
|
||||
export default class SyncbackDraftEventsTask extends BaseDraftTask {
|
||||
|
||||
constructor(draftClientId) {
|
||||
super(draftClientId);
|
||||
this._appliedEvents = null;
|
||||
}
|
||||
|
||||
label() {
|
||||
return "Creating meeting request...";
|
||||
}
|
||||
|
||||
performRemote() {
|
||||
return this.refreshDraftReference()
|
||||
.then(this.uploadEvents)
|
||||
.then(this.applyChangesToDraft)
|
||||
.thenReturn(Task.Status.Success)
|
||||
.catch((err) => {
|
||||
if (err instanceof BaseDraftTask.DraftNotFoundError) {
|
||||
return Promise.resolve(Task.Status.Continue);
|
||||
}
|
||||
if (err instanceof APIError && !NylasAPI.PermanentErrorCodes.includes(err.statusCode)) {
|
||||
return Promise.resolve(Task.Status.Retry);
|
||||
}
|
||||
return Promise.resolve([Task.Status.Failed, err]);
|
||||
});
|
||||
}
|
||||
|
||||
uploadEvents = () => {
|
||||
const events = this.draft.events;
|
||||
if (events && events.length) {
|
||||
const event = events[0]; // only upload one
|
||||
return this.uploadEvent(event).then((savedEvent) => {
|
||||
if (savedEvent) {
|
||||
this._appliedEvents = [savedEvent];
|
||||
}
|
||||
Promise.resolve();
|
||||
});
|
||||
}
|
||||
return Promise.resolve()
|
||||
};
|
||||
|
||||
uploadEvent = (event) => {
|
||||
return NylasAPI.makeRequest({
|
||||
path: "/events?notify_participants=true",
|
||||
accountId: this.draft.accountId,
|
||||
method: "POST",
|
||||
body: this._prepareEventJson(event),
|
||||
returnsModel: true,
|
||||
}).then(json =>{
|
||||
return (new Event()).fromJSON(json);
|
||||
});
|
||||
};
|
||||
|
||||
_prepareEventJson(inEvent) {
|
||||
const event = inEvent.fromDraft(this.draft)
|
||||
const json = event.toJSON();
|
||||
delete json.id;
|
||||
json.when = {
|
||||
start_time: json._start,
|
||||
end_time: json._end,
|
||||
};
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
applyChangesToDraft = () => {
|
||||
return DatabaseStore.inTransaction((t) => {
|
||||
return this.refreshDraftReference().then(() => {
|
||||
this.draft.events = this._appliedEvents;
|
||||
return t.persistModel(this.draft);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -50,7 +50,6 @@ class NylasExports
|
|||
@load "Actions", 'flux/actions'
|
||||
|
||||
# API Endpoints
|
||||
@load "nylasRequest", 'nylas-request' # An extend `request` module
|
||||
@load "NylasAPI", 'flux/nylas-api'
|
||||
@load "NylasSyncStatusStore", 'flux/stores/nylas-sync-status-store'
|
||||
@load "EdgehillAPI", 'flux/edgehill-api'
|
||||
|
@ -109,14 +108,12 @@ class NylasExports
|
|||
@require "DestroyCategoryTask", 'flux/tasks/destroy-category-task'
|
||||
@require "ChangeUnreadTask", 'flux/tasks/change-unread-task'
|
||||
@require "SyncbackDraftFilesTask", 'flux/tasks/syncback-draft-files-task'
|
||||
@require "SyncbackDraftEventsTask", 'flux/tasks/syncback-draft-events-task'
|
||||
@require "SyncbackDraftTask", 'flux/tasks/syncback-draft-task'
|
||||
@require "ChangeStarredTask", 'flux/tasks/change-starred-task'
|
||||
@require "DestroyModelTask", 'flux/tasks/destroy-model-task'
|
||||
@require "SyncbackModelTask", 'flux/tasks/syncback-model-task'
|
||||
@require "SyncbackMetadataTask", 'flux/tasks/syncback-metadata-task'
|
||||
@require "ReprocessMailRulesTask", 'flux/tasks/reprocess-mail-rules-task'
|
||||
@require "RegisterDraftForPluginTask", 'flux/tasks/register-draft-for-plugin-task'
|
||||
|
||||
# Stores
|
||||
# These need to be required immediately since some Stores are
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
import _ from 'underscore'
|
||||
import request from 'request'
|
||||
import Utils from './flux/models/utils'
|
||||
import Actions from './flux/actions'
|
||||
import {APIError} from './flux/errors'
|
||||
/**
|
||||
* A light wrapper around the `request` library to make sure we're logging
|
||||
* requests and retruning standard error types.
|
||||
*/
|
||||
|
||||
const origInit = request.Request.prototype.init
|
||||
|
||||
request.Request.prototype.init = function nylasRequestInit(options) {
|
||||
const opts = options;
|
||||
const requestId = Utils.generateTempId()
|
||||
opts.startTime = Date.now();
|
||||
Actions.willMakeAPIRequest({
|
||||
request: opts,
|
||||
requestId,
|
||||
});
|
||||
const origCallback = opts.callback || function noop() {}
|
||||
|
||||
// It's a super common error to pass an object `body`, but forget to
|
||||
// pass `json:true`. If you don't pass `json:true` the body won't be
|
||||
// automatically stringified. We'll take care of doing that for you.
|
||||
if (!_.isString(opts.body) && !_.isArray(opts.body)) {
|
||||
if (opts.json === null || opts.json === undefined) {
|
||||
opts.json = true;
|
||||
}
|
||||
}
|
||||
|
||||
opts.callback = (error, response, body) => {
|
||||
let statusCode;
|
||||
let apiError;
|
||||
|
||||
if (error) {
|
||||
if (error.code) {
|
||||
statusCode = error.code;
|
||||
apiError = new APIError({
|
||||
error, response, body, statusCode,
|
||||
requestOptions: opts,
|
||||
});
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
} else if (response) {
|
||||
statusCode = response.statusCode;
|
||||
if (statusCode > 299) {
|
||||
apiError = new APIError({
|
||||
error, response, body, statusCode,
|
||||
requestOptions: opts,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw new Error("Got a request with no error and no response!")
|
||||
}
|
||||
|
||||
Actions.didMakeAPIRequest({
|
||||
request: opts,
|
||||
statusCode,
|
||||
error,
|
||||
requestId,
|
||||
})
|
||||
|
||||
if (apiError) {
|
||||
NylasEnv.errorLogger.apiDebug(apiError)
|
||||
}
|
||||
|
||||
return origCallback(apiError, response, body)
|
||||
}
|
||||
this.callback = opts.callback
|
||||
return origInit.call(this, opts)
|
||||
}
|
||||
|
||||
export default request
|