Give Task classes formal attributes just like Models

This commit is contained in:
Ben Gotow 2017-07-07 15:58:49 -07:00
parent bb64766334
commit a77b81181f
17 changed files with 166 additions and 100 deletions

View file

@ -72,8 +72,6 @@ export default class MailsyncBridge {
}
task.validate();
task.sequentialId = ++this._currentSequentialId;
task.status = 'local';
this.sendMessageToAccount(task.accountId, {type: 'task-queued', task: task});
}

View file

@ -88,9 +88,6 @@ export default class Category extends Model {
localStatus: Attributes.Object({
modelKey: 'localStatus',
}),
_refcount: Attributes.Number({
modelKey: '_refcount',
}),
});
static Types = {

View file

@ -168,10 +168,6 @@ export default class Message extends ModelWithMetadata {
itemClass: Folder,
}),
folderUID: Attributes.Number({
modelKey: 'folderUID',
}),
});
static naturalSortOrder() {

View file

@ -40,7 +40,7 @@ export default class Model {
this.fromJSON(data);
} else {
for (const key of Object.keys(this.constructor.attributes)) {
if (data[key]) {
if (data[key] !== undefined) {
this[key] = data[key];
}
}
@ -61,7 +61,7 @@ export default class Model {
fromJSON(json) {
for (const key of Object.keys(this.constructor.attributes)) {
const attr = this.constructor.attributes[key];
const attrValue = json[attr.jsonKey];
const attrValue = json[attr.jsonKey || key];
if (attrValue !== undefined) {
this[key] = attr.fromJSON(attrValue);
}
@ -82,7 +82,7 @@ export default class Model {
if (attrValue === undefined) {
continue;
}
json[attr.jsonKey] = attr.toJSON(attrValue);
json[attr.jsonKey || key] = attr.toJSON(attrValue);
}
json.__cls = this.constructor.name
return json;

View file

@ -30,7 +30,6 @@ class DraftFactory
version: 0
unread: false
starred: false
folderUID: 0
headerMessageId: Utils.generateTempId() + "@" + require('os').hostname()
from: [account.defaultMe()]
date: (new Date)

View file

@ -1,5 +1,6 @@
import Category from '../models/category';
import ChangeMailTask from './change-mail-task';
import Attributes from '../attributes';
import Folder from '../models/folder';
// Public: Create a new task to apply labels to a message or thread.
//
@ -14,11 +15,15 @@ import ChangeMailTask from './change-mail-task';
//
export default class ChangeFolderTask extends ChangeMailTask {
constructor(options = {}) {
super(options);
this.source = options.source
this.taskDescription = options.taskDescription;
this.folder = options.folder;
static attributes = Object.assign({}, ChangeMailTask.attributes, {
folder: Attributes.Object({
modelKey: 'folder',
ItemClass: Folder,
}),
});
constructor(data = {}) {
super(data);
}
label() {
@ -33,10 +38,7 @@ export default class ChangeFolderTask extends ChangeMailTask {
return this.taskDescription;
}
let folderText = " to folder";
if (this.folder instanceof Category) {
folderText = ` to ${this.folder.displayName}`;
}
const folderText = ` to ${this.folder.displayName}`;
if (this.threadIds.length > 1) {
return `Moved ${this.threadIds.length} threads${folderText}`;

View file

@ -1,6 +1,6 @@
import Label from '../models/label';
import Category from '../models/category';
import ChangeMailTask from './change-mail-task';
import Attributes from '../attributes';
// Public: Create a new task to apply labels to a message or thread.
//
@ -9,14 +9,22 @@ import ChangeMailTask from './change-mail-task';
// - labelsToRemove: An {Array} of {Category}s or {Category} ids to remove
// - threads: An {Array} of {Thread}s or {Thread} ids
// - messages: An {Array} of {Message}s or {Message} ids
//
export default class ChangeLabelsTask extends ChangeMailTask {
static attributes = Object.assign({}, ChangeMailTask.attributes, {
labelsToAdd: Attributes.Collection({
modelKey: 'labelsToAdd',
ItemClass: Label,
}),
labelsToRemove: Attributes.Collection({
modelKey: 'labelsToRemove',
ItemClass: Label,
}),
});
constructor(options = {}) {
super(options);
this.source = options.source
this.labelsToAdd = options.labelsToAdd || [];
this.labelsToRemove = options.labelsToRemove || [];
this.taskDescription = options.taskDescription;
}
label() {
@ -35,35 +43,29 @@ export default class ChangeLabelsTask extends ChangeMailTask {
const removed = this.labelsToRemove[0];
const added = this.labelsToAdd[0];
const objectsAvailable = (added || removed) instanceof Category;
// Note: In the future, we could move this logic to the task
// factory and pass the string in as this.taskDescription (ala Snooze), but
// it's nice to have them declaratively based on the actual labels.
if (objectsAvailable) {
// Spam / trash interactions are always "moves" because they're the three
// folders of Gmail. If another folder is involved, we need to decide to
// return either "Moved to Bla" or "Added Bla".
if (added && added.name === 'spam') {
return `Marked${countString} as Spam`;
} else if (removed && removed.name === 'spam') {
return `Unmarked${countString} as Spam`;
} else if (added && added.name === 'trash') {
return `Trashed${countString}`;
} else if (removed && removed.name === 'trash') {
return `Removed${countString} from Trash`;
}
if (this.labelsToAdd.length === 0 && this.labelsToRemove.find(l => l.role === 'inbox')) {
return `Archived${countString}`;
} else if (this.labelsToRemove.length === 0 && this.labelsToAdd.find(l => l.role === 'inbox')) {
return `Unarchived${countString}`;
}
if (this.labelsToAdd.length === 1 && this.labelsToRemove.length === 0) {
return `Added ${added.displayName}${countString ? ' to' : ''}${countString}`;
}
if (this.labelsToAdd.length === 0 && this.labelsToRemove.length === 1) {
return `Removed ${removed.displayName}${countString ? ' from' : ''}${countString}`;
}
// Spam / trash interactions are always "moves" because they're the three
// folders of Gmail. If another folder is involved, we need to decide to
// return either "Moved to Bla" or "Added Bla".
if (added && added.name === 'spam') {
return `Marked${countString} as Spam`;
} else if (removed && removed.name === 'spam') {
return `Unmarked${countString} as Spam`;
} else if (added && added.name === 'trash') {
return `Trashed${countString}`;
} else if (removed && removed.name === 'trash') {
return `Removed${countString} from Trash`;
}
if (this.labelsToAdd.length === 0 && this.labelsToRemove.find(l => l.role === 'inbox')) {
return `Archived${countString}`;
} else if (this.labelsToRemove.length === 0 && this.labelsToAdd.find(l => l.role === 'inbox')) {
return `Unarchived${countString}`;
}
if (this.labelsToAdd.length === 1 && this.labelsToRemove.length === 0) {
return `Added ${added.displayName}${countString ? ' to' : ''}${countString}`;
}
if (this.labelsToAdd.length === 0 && this.labelsToRemove.length === 1) {
return `Removed ${removed.displayName}${countString ? ' from' : ''}${countString}`;
}
return `Changed labels${countString ? ' on' : ''}${countString}`;
}

View file

@ -1,4 +1,5 @@
import Task from './task';
import Attributes from '../attributes';
/*
Public: The ChangeMailTask is a base class for all tasks that modify sets
@ -20,8 +21,20 @@ requests. It does not call {ChangeMailTask::changesToModel}.
*/
export default class ChangeMailTask extends Task {
constructor({threads, thread, messages, message} = {}) {
super();
static attributes = Object.assign({}, ChangeMailTask.attributes, {
taskDescription: Attributes.String({
modelKey: 'taskDescription',
}),
threadIds: Attributes.Collection({
modelKey: 'threadIds',
}),
messageIds: Attributes.Collection({
modelKey: 'messageIds',
}),
});
constructor({threads, thread, messages, message, ...rest} = {}) {
super(rest);
const t = threads || [];
if (thread) {

View file

@ -1,15 +1,22 @@
/* eslint no-unused-vars: 0*/
import _ from 'underscore';
import Attributes from '../attributes';
import Thread from '../models/thread';
import Actions from '../actions'
import DatabaseStore from '../stores/database-store';
import ChangeMailTask from './change-mail-task';
export default class ChangeStarredTask extends ChangeMailTask {
constructor(options = {}) {
super(options);
this.source = options.source;
this.starred = options.starred;
static attributes = Object.assign({}, ChangeMailTask.attributes, {
starred: Attributes.Boolean({
modelKey: 'starred',
}),
});
constructor({starred, ...rest} = {}) {
super(rest);
this.starred = starred;
}
label() {

View file

@ -2,15 +2,25 @@
import _ from 'underscore';
import Thread from '../models/thread';
import Actions from '../actions'
import Attributes from '../attributes';
import DatabaseStore from '../stores/database-store';
import ChangeMailTask from './change-mail-task';
export default class ChangeUnreadTask extends ChangeMailTask {
constructor(options = {}) {
super(options);
this.source = options.source;
this.unread = options.unread;
this._canBeUndone = options.canBeUndone;
static attributes = Object.assign({}, ChangeMailTask.attributes, {
starred: Attributes.Boolean({
modelKey: 'unread',
}),
_canBeUndone: Attributes.Boolean({
modelKey: '_canBeUndone',
}),
});
constructor({unread, canBeUndone, ...rest} = {}) {
super(rest);
this.unread = unread;
this._canBeUndone = canBeUndone;
}
label() {

View file

@ -1,12 +1,13 @@
import Task from './task';
import Attributes from '../attributes';
export default class DestroyCategoryTask extends Task {
constructor({path, accountId} = {}) {
super();
this.path = path;
this.accountId = accountId;
}
static attributes = Object.assign({}, Task.attributes, {
path: Attributes.String({
modelKey: 'path',
}),
});
label() {
return `Deleting ${this.category.displayType()} ${this.category.displayName}`

View file

@ -1,9 +1,11 @@
import Task from './task';
import Attributes from '../attributes';
export default class DestroyDraftTask extends Task {
constructor(accountId, headerMessageId) {
super();
this.accountId = accountId;
this.headerMessageId = headerMessageId;
}
static attributes = Object.assign({}, Task.attributes, {
headerMessageId: Attributes.String({
modelKey: 'headerMessageId',
}),
});
}

View file

@ -3,6 +3,8 @@ import AccountStore from '../stores/account-store';
import Task from './task';
import Actions from '../actions';
import SoundRegistry from '../../registries/sound-registry';
import Attributes from '../attributes';
import Message from '../models/message';
const OPEN_TRACKING_ID = NylasEnv.packages.pluginIdFor('open-tracking')
const LINK_TRACKING_ID = NylasEnv.packages.pluginIdFor('link-tracking')
@ -10,12 +12,32 @@ const LINK_TRACKING_ID = NylasEnv.packages.pluginIdFor('link-tracking')
export default class SendDraftTask extends Task {
constructor(draft, {playSound = true, emitError = true, allowMultiSend = true} = {}) {
super();
this.draft = draft;
static attributes = Object.assign({}, Task.attributes, {
draft: Attributes.Object({
modelKey: 'draft',
itemClass: Message,
}),
headerMessageId: Attributes.String({
modelKey: 'headerMessageId',
}),
emitError: Attributes.Boolean({
modelKey: 'emitError',
}),
playSound: Attributes.Boolean({
modelKey: 'playSound',
}),
allowMultiSend: Attributes.Boolean({
modelKey: 'allowMultiSend',
}),
perRecipientBodies: Attributes.Collection({
modelKey: 'perRecipientBodies',
}),
});
constructor({draft, playSound = true, emitError = true, allowMultiSend = true, ...rest} = {}) {
super(rest);
this.accountId = (draft || {}).accountId;
this.headerMessageId = (draft || {}).headerMessageId;
this.emitError = emitError
this.playSound = playSound
this.allowMultiSend = allowMultiSend

View file

@ -1,13 +1,25 @@
import Task from './task';
import Attributes from '../attributes';
export default class SyncbackCategoryTask extends Task {
constructor({existingPath, path, accountId} = {}) {
super()
static attributes = Object.assign({}, Task.attributes, {
path: Attributes.String({
modelKey: 'path',
}),
existingPath: Attributes.String({
modelKey: 'existingPath',
}),
created: Attributes.Object({
modelKey: 'created',
}),
});
constructor({existingPath, path, accountId, ...rest} = {}) {
super(rest);
this.existingPath = existingPath;
this.path = path;
this.accountId = accountId;
this.created = null;
}
label() {

View file

@ -1,8 +1,21 @@
import Task from './task';
import Attributes from '../attributes';
import Message from '../models/message';
export default class SyncbackDraftTask extends Task {
constructor(draft) {
super();
static attributes = Object.assign({}, Task.attributes, {
headerMessageId: Attributes.String({
modelKey: 'headerMessageId',
}),
draft: Attributes.Object({
modelKey: 'draft',
itemClass: Message,
}),
});
constructor({draft, ...rest} = {}) {
super(rest);
this.draft = draft;
this.accountId = (draft || {}).accountId;
this.headerMessageId = (draft || {}).headerMessageId;

View file

@ -15,14 +15,6 @@ export default class SyncbackMetadataTask extends SyncbackModelTask {
return DatabaseObjectRegistry.get(this.modelClassName);
}
isDependentOnTask(otherTask) {
return (
otherTask instanceof SyncbackMetadataTask &&
otherTask.pluginId === this.pluginId &&
otherTask.sequentialId < this.sequentialId
)
}
makeRequest = async (model) => {
if (!model.serverId) {
throw new Error(`Can't syncback metadata for a ${this.modelClassName} instance that doesn't have a serverId`)

View file

@ -27,6 +27,9 @@ export default class Task extends Model {
queryable: true,
modelKey: 'status',
}),
source: Attributes.String({
modelKey: 'source',
}),
error: Attributes.Object({
modelKey: 'error',
}),
@ -39,13 +42,10 @@ export default class Task extends Model {
// `super`.
//
// On construction, all Tasks instances are given a unique `id`.
constructor() {
super();
this.version = 1;
this._rememberedToCallSuper = true;
this.id = generateTempId();
constructor(data) {
super(data);
this.id = this.id || generateTempId();
this.accountId = null;
this.sequentialId = null; // set when queued
}
// Public: Override to raise exceptions if your task is missing required