From 33d55b0ea490a3d22c1be720d7aaa3d8e5e04ecb Mon Sep 17 00:00:00 2001 From: TheExGenesis <7385326+TheExGenesis@users.noreply.github.com> Date: Wed, 9 Feb 2022 01:31:26 +0000 Subject: [PATCH] feat(email): sync and push to gmail inbox (read, archive) --- .../executables/addEmailMessage.js | 16 ++++-- ...tEmailsAsRead.js => modifyEmailsLabels.js} | 5 +- .../executables/updateGmailInbox.js | 49 +++++++++++++++---- .../unigraph.email/package.json | 6 +-- .../UnigraphCore/ListObjectView.tsx | 6 ++- .../src/examples/email/Email.tsx | 30 +++++++++++- .../src/examples/email/EmailSettings.tsx | 32 +++++++++++- packages/unigraph-dev-explorer/src/utils.tsx | 9 ++++ 8 files changed, 128 insertions(+), 25 deletions(-) rename packages/default-packages/unigraph.email/executables/{setEmailsAsRead.js => modifyEmailsLabels.js} (93%) diff --git a/packages/default-packages/unigraph.email/executables/addEmailMessage.js b/packages/default-packages/unigraph.email/executables/addEmailMessage.js index 423264f9..6507fe1a 100644 --- a/packages/default-packages/unigraph.email/executables/addEmailMessage.js +++ b/packages/default-packages/unigraph.email/executables/addEmailMessage.js @@ -57,9 +57,10 @@ const results = dontCheckUnique ]); const readMask = []; +const inboxMask = []; const toAdd = []; -const inbox_els = []; +const inboxEls = []; let count = 0; for (let i = 0; i < dest.length; ++i) { @@ -67,6 +68,7 @@ for (let i = 0; i < dest.length; ++i) { count++; toAdd.push(dest[i]); readMask.push(msgs[i]?.read); + inboxMask.push(msgs[i]?.inbox); } } @@ -92,21 +94,25 @@ for (let i = 0; i < toAddChunks.length; i += 1) { console.log(e); } } - +console.log('addEmailMessage'); +const isSoftPushMode = unigraph.getState('settings/email/emailSoftInboxPushMode').value; +console.log({ isSoftPushMode }); uids.forEach((el, index) => { - if (!readMask[index] && el) inbox_els.push(el); + // if (!readMask[index] && el) inboxEls.push(el); + const shouldPushEl = (isSoftPushMode ? !readMask[index] : inboxMask[index]) && el; + if (shouldPushEl) inboxEls.push(el); }); await unigraph.runExecutable('$/executable/add-item-to-list', { where: '$/entity/inbox', - item: inbox_els.reverse(), + item: inboxEls.reverse(), }); setTimeout( () => unigraph.addNotification({ name: 'Inboxes synced', from: 'unigraph.email', - content: `Added ${count} emails (${inbox_els.length} unread).`, + content: `Added ${count} emails (${inboxEls.length} unread).`, actions: [], }), 1000, diff --git a/packages/default-packages/unigraph.email/executables/setEmailsAsRead.js b/packages/default-packages/unigraph.email/executables/modifyEmailsLabels.js similarity index 93% rename from packages/default-packages/unigraph.email/executables/setEmailsAsRead.js rename to packages/default-packages/unigraph.email/executables/modifyEmailsLabels.js index a3240b7c..63c0774d 100644 --- a/packages/default-packages/unigraph.email/executables/setEmailsAsRead.js +++ b/packages/default-packages/unigraph.email/executables/modifyEmailsLabels.js @@ -1,4 +1,4 @@ -const { uids } = context.params; +const { uids, addLabelIds, removeLabelIds } = context.params; const { google } = require('googleapis'); const gmailClientId = unigraph.getSecret('google', 'client_id'); @@ -59,6 +59,7 @@ if (account?.uid) { await gmail.users.messages.batchModify({ userId: 'me', ids: mids, - removeLabelIds: ['UNREAD'], + addLabelIds, + removeLabelIds, }); } diff --git a/packages/default-packages/unigraph.email/executables/updateGmailInbox.js b/packages/default-packages/unigraph.email/executables/updateGmailInbox.js index 41a8852b..62141ce5 100644 --- a/packages/default-packages/unigraph.email/executables/updateGmailInbox.js +++ b/packages/default-packages/unigraph.email/executables/updateGmailInbox.js @@ -3,6 +3,7 @@ const { google } = require('googleapis'); const gmailClientId = unigraph.getSecret('google', 'client_id'); const gmailClientSecret = unigraph.getSecret('google', 'client_secret'); const fetch = require('node-fetch'); +const _ = require('lodash/fp'); const account = ( await unigraph.getQueries([ @@ -68,7 +69,7 @@ if (account?.uid) { auth, }); - const res = await gmail.users.messages.list({ userId: 'me', maxResults: 25 }); + const res = await gmail.users.messages.list({ userId: 'me', maxResults: 50, includeSpamTrash: true }); const messages = res.data.messages.map((el) => el.id); const msgIdResps = await Promise.all( @@ -90,14 +91,33 @@ if (account?.uid) { <~type> { parIds as uid } }`, ]); - const newMsgs = messages.filter((el, index) => results[index].length === 0); - let newMsgResps = await Promise.all( - newMsgs.map((id) => gmail.users.messages.get({ userId: 'me', id, format: 'raw' })), + const msgResps = await Promise.all( + messages.map((id) => gmail.users.messages.get({ userId: 'me', id, format: 'raw' })), ); + const getOldUid = _.curry((condition, el, index) => { + const isCondition = condition(el); + const isOld = results[index].length !== 0; + return isCondition && isOld ? results[index]?.[0].uid : undefined; + }); + const isInOriginTrash = getOldUid( + (el) => el.data.labelIds?.includes('TRASH') || el.data.labelIds?.includes('SPAM'), + ); + const isInOriginInbox = getOldUid((el) => el.data.labelIds?.includes('INBOX')); + + const uidsToDelete = msgResps.map(isInOriginTrash).filter((el) => el !== undefined); + const uidsToInbox = msgResps.map(isInOriginInbox).filter((el) => el !== undefined); + const uidsToRemoveInbox = msgResps.map(_.negate(isInOriginInbox)).filter((el) => el !== undefined); + + uidsToDelete.forEach(unigraph.deleteObject); + + // TODO: remove or hide deleted msgs + // Do not insert drafts to Unigraph - newMsgResps = newMsgResps.filter((el) => !el.data.labelIds?.includes('DRAFT')); + const newMsgResps = msgResps + .filter((el, index) => results[index].length === 0) + .filter((el) => _.intersection(el.data.labelIds, ['DRAFT', 'TRASH', 'SPAM']).length === 0); if (newMsgResps.length) { await unigraph.runExecutable('$/executable/add-email', { @@ -105,15 +125,24 @@ if (account?.uid) { messages: newMsgResps.map((el) => ({ message: Buffer.from(el.data.raw, 'base64').toString(), read: !el.data.labelIds?.includes('UNREAD'), + inbox: el.data.labelIds?.includes('INBOX'), })), }); } - - const readMsgs = msgIdResps.map((el, index) => - el.data.labelIds.includes('UNREAD') ? undefined : results[index]?.[0]?.uid, - ); + console.log({ + newMsgRespsLen: newMsgResps.length, + newMsgResps: newMsgResps.map((el) => el.data.labelIds), + uidsToRemoveInbox: uidsToRemoveInbox.length, + uidsToInbox: uidsToInbox.length, + uidsToDeleteLen: uidsToDelete.length, + uidsToDelete, + }); + await unigraph.runExecutable('$/executable/add-item-to-list', { + where: '$/entity/inbox', + item: uidsToInbox.reverse(), + }); await unigraph.runExecutable('$/executable/delete-item-from-list', { where: '$/entity/inbox', - item: readMsgs.filter((el) => el !== undefined), + item: uidsToRemoveInbox, }); } diff --git a/packages/default-packages/unigraph.email/package.json b/packages/default-packages/unigraph.email/package.json index 6d83f97b..ccf8850c 100644 --- a/packages/default-packages/unigraph.email/package.json +++ b/packages/default-packages/unigraph.email/package.json @@ -42,11 +42,11 @@ "concurrency": 1 }, { - "id": "set-emails-as-read", + "id": "modify-emails-labels", "env": "routine/js", - "src": "executables/setEmailsAsRead.js", + "src": "executables/modifyEmailsLabels.js", "editable": true, - "name": "Set emails as read" + "name": "Add or remove labels from emails" } ], "entities": [ diff --git a/packages/unigraph-dev-explorer/src/components/UnigraphCore/ListObjectView.tsx b/packages/unigraph-dev-explorer/src/components/UnigraphCore/ListObjectView.tsx index 01a0f740..605fe522 100644 --- a/packages/unigraph-dev-explorer/src/components/UnigraphCore/ListObjectView.tsx +++ b/packages/unigraph-dev-explorer/src/components/UnigraphCore/ListObjectView.tsx @@ -1,3 +1,4 @@ +import { isInboxPushModeSoft } from '../../examples/email/EmailSettings'; import { DynamicObjectListView } from '../ObjectView/DynamicObjectListView'; /** Dependent on the specific definition of object!! */ @@ -21,7 +22,6 @@ export const ListObjectQuery = (uid: string) => `(func: uid(${uid})) { uid type { } }`; - export function ListObjectView({ data, callbacks, ...attributes }: any) { const listValue = data?._value?.children; @@ -49,8 +49,10 @@ export function ListObjectView({ data, callbacks, ...attributes }: any) { // TODO: expand this into more general cases - e.g. over a hook const emails = types.filter((el) => el.type === '$/schema/email_message'); if (emails.length) { - window.unigraph.runExecutable('$/executable/set-emails-as-read', { + window.unigraph.runExecutable('$/executable/modify-emails-labels', { uids: emails.map((el) => el.uid), + removeLabelIds: isInboxPushModeSoft() ? ['UNREAD'] : ['INBOX'], + addLabelIds: [], }); } }); diff --git a/packages/unigraph-dev-explorer/src/examples/email/Email.tsx b/packages/unigraph-dev-explorer/src/examples/email/Email.tsx index fdedb1bc..704f174e 100644 --- a/packages/unigraph-dev-explorer/src/examples/email/Email.tsx +++ b/packages/unigraph-dev-explorer/src/examples/email/Email.tsx @@ -1,16 +1,17 @@ import { Avatar, ListItem as div, ListItemAvatar, ListItemText } from '@material-ui/core'; -import React from 'react'; +import React, { useEffect } from 'react'; import { pkg as emailPackage } from 'unigraph-dev-common/lib/data/unigraph.email.pkg'; import { byUpdatedAt, unpad } from 'unigraph-dev-common/lib/utils/entityUtils'; import Sugar from 'sugar'; import { Link } from '@material-ui/icons'; import { UnigraphObject } from 'unigraph-dev-common/lib/utils/utils'; import { DynamicViewRenderer } from '../../global.d'; -import { getComponentFromPage } from '../../utils'; +import { getComponentFromPage, getOrInitLocalStorage } from '../../utils'; import { DynamicObjectListView } from '../../components/ObjectView/DynamicObjectListView'; import { registerDetailedDynamicViews, registerDynamicViews, withUnigraphSubscription } from '../../unigraph-react'; import { AutoDynamicView } from '../../components/ObjectView/AutoDynamicView'; import { AutoDynamicViewDetailed } from '../../components/ObjectView/AutoDynamicViewDetailed'; +import { isInboxPushModeSoft } from './EmailSettings'; type AEmail = { name: string; @@ -31,6 +32,15 @@ const EmailListBody: React.FC<{ data: any[] }> = ({ data }) => ( ); const EmailMessageDetailed: DynamicViewRenderer = ({ data, callbacks }) => { + useEffect(() => { + if (!isInboxPushModeSoft()) { + window.unigraph.runExecutable('$/executable/modify-emails-labels', { + uids: [data.uid], + removeLabelIds: ['UNREAD'], + addLabelIds: [], + }); + } + }, []); return ( { ); }; +const defaultEmailSettings = { emailSoftInboxPushMode: false }; export const init = () => { + const emailSettings = getOrInitLocalStorage('emailSettings', defaultEmailSettings); + const emailSoftInboxPushMode = window.unigraph.addState( + 'settings/email/emailSoftInboxPushMode', + emailSettings.emailSoftInboxPushMode ?? false, + ); + emailSoftInboxPushMode.subscribe((val: boolean) => { + window.localStorage.setItem( + 'emailSettings', + JSON.stringify({ + ...JSON.parse(window.localStorage.getItem('emailSettings')!), + emailSoftInboxPushMode: val, + }), + ); + }); + registerDynamicViews({ '$/schema/email_message': EmailMessage }); registerDetailedDynamicViews({ '$/schema/email_message': EmailMessageDetailed, diff --git a/packages/unigraph-dev-explorer/src/examples/email/EmailSettings.tsx b/packages/unigraph-dev-explorer/src/examples/email/EmailSettings.tsx index 5c008f56..350b72c9 100644 --- a/packages/unigraph-dev-explorer/src/examples/email/EmailSettings.tsx +++ b/packages/unigraph-dev-explorer/src/examples/email/EmailSettings.tsx @@ -1,14 +1,23 @@ -import { Button, Typography } from '@material-ui/core'; +import { Button, List, ListItem, ListItemSecondaryAction, ListItemText, Switch, Typography } from '@material-ui/core'; import React, { useEffect } from 'react'; import { useEffectOnce } from 'react-use'; import { pkg } from 'unigraph-dev-common/lib/data/unigraph.email.pkg'; import { getRandomInt } from 'unigraph-dev-common/lib/api/unigraph'; import { TabContext } from '../../utils'; +export const isInboxPushModeSoft = () => window.unigraph.getState('settings/email/emailSoftInboxPushMode').value; + export function EmailSettings({}) { const [loaded, setLoaded] = React.useState(false); const [account, setAccount] = React.useState({}); const tabContext = React.useContext(TabContext); + + const emailSoftInboxPushModeState = window.unigraph.getState('settings/email/emailSoftInboxPushMode'); + const [emailSoftInboxPushMode, setEmailInboxPushMode] = React.useState(emailSoftInboxPushModeState.value); + emailSoftInboxPushModeState.subscribe((newState: boolean) => { + setEmailInboxPushMode(newState); + }); + useEffectOnce(() => { window.unigraph.ensurePackage('unigraph.email', pkg).then(() => setLoaded(true)); }); @@ -69,6 +78,27 @@ export function EmailSettings({}) { + + false} key="email-inbox-push-mode"> + + + { + emailSoftInboxPushModeState.setValue(!emailSoftInboxPushMode); + }} + checked={emailSoftInboxPushMode} + inputProps={{ + 'aria-labelledby': 'switch-list-label-email-soft-inbox-push-mode', + }} + /> + + + ) : ( <>Loading... diff --git a/packages/unigraph-dev-explorer/src/utils.tsx b/packages/unigraph-dev-explorer/src/utils.tsx index ca432d7b..a22f8e84 100644 --- a/packages/unigraph-dev-explorer/src/utils.tsx +++ b/packages/unigraph-dev-explorer/src/utils.tsx @@ -6,6 +6,7 @@ import stringify from 'json-stable-stringify'; import _ from 'lodash'; import React from 'react'; import { unpad } from 'unigraph-dev-common/lib/utils/entityUtils'; +import { isJsonString } from 'unigraph-dev-common/lib/utils/utils'; export const NavigationContext = React.createContext<(location: string) => any>((location: string) => ({})); @@ -556,3 +557,11 @@ export function typeHasDetailedView(type: string) { export function typeHasDynamicView(type: string) { return Object.keys(window.unigraph.getState('registry/dynamicView').value).includes(type); } + +export const getOrInitLocalStorage = (key: string, defaultValue: any) => { + if (!isJsonString(window.localStorage.getItem(key))) { + window.localStorage.setItem(key, JSON.stringify(defaultValue)); + return defaultValue; + } + return JSON.parse(window.localStorage.getItem(key) || ''); +};