From 33ffe7b2e4013b98a5efed157721acd71570aea3 Mon Sep 17 00:00:00 2001 From: Sofia Acosta Date: Mon, 6 Feb 2023 22:12:03 -0600 Subject: [PATCH] partial implementation of luxon --- api/controllers/AdminController.js | 52 +++++++------- api/models/event/mixin.js | 20 +++--- api/services/RequestValidation.js | 27 +++---- api/services/Search/util.js | 32 ++++----- cron.js | 18 ++--- lib/group/digest2/formatData.js | 10 ++- lib/group/digest2/savedSearches.js | 31 ++++---- lib/group/digest2/util.js | 24 ++++--- package.json | 2 +- test/unit/services/Search.test.js | 16 ++--- test/unit/services/digest2.test.js | 110 ++++++++++++++--------------- yarn.lock | 17 ++--- 12 files changed, 183 insertions(+), 176 deletions(-) diff --git a/api/controllers/AdminController.js b/api/controllers/AdminController.js index c172fe68e..a0a093064 100644 --- a/api/controllers/AdminController.js +++ b/api/controllers/AdminController.js @@ -1,8 +1,8 @@ -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import { merge, transform, sortBy } from 'lodash' // TODO: this is old and broken -var rawMetricsQuery = startTime => Promise.props({ +const rawMetricsQuery = startTime => Promise.props({ community: Community.query(q => { q.select(['id', 'name', 'created_at', 'avatar_url']) }).query(), @@ -31,39 +31,39 @@ var rawMetricsQuery = startTime => Promise.props({ module.exports = { loginAsUser: function (req, res) { return User.find(req.param('userId')) - .then(user => UserSession.login(req, user, 'admin')) - .then(() => res.redirect('/app')) + .then(user => UserSession.login(req, user, 'admin')) + .then(() => res.redirect('/app')) }, rawMetrics: function (req, res) { - const startTime = moment().subtract(3, 'months').toDate() + const startTime = DateTime.now().minus({ months: 3 }).toJSDate() return rawMetricsQuery(startTime) - .then(props => { - let result = props.community.reduce((acc, c) => { - acc[c.id] = merge(c, {events: []}) - return acc - }, {}) + .then(props => { + let result = props.community.reduce((acc, c) => { + acc[c.id] = merge(c, { events: [] }) + return acc + }, {}) - result.none = {id: 'none', name: 'No community', events: []} + result.none = { id: 'none', name: 'No community', events: [] } - ;['user', 'post', 'comment'].forEach(name => { - props[name].forEach(item => { - const key = item.community_id || 'none' - result[key].events.push({ - time: Date.parse(item.created_at), - user_id: item.user_id || item.id, - name + ;['user', 'post', 'comment'].forEach(name => { + props[name].forEach(item => { + const key = item.community_id || 'none' + result[key].events.push({ + time: Date.parse(item.created_at), + user_id: item.user_id || item.id, + name + }) }) }) - }) - result = transform(result, (acc, c, k) => { - if (c.events.length === 0) return - c.events = sortBy(c.events, 'time') - acc[k] = c - }, {}) + result = transform(result, (acc, c, k) => { + if (c.events.length === 0) return + c.events = sortBy(c.events, 'time') + acc[k] = c + }, {}) - res.ok(result) - }) + res.ok(result) + }) } } diff --git a/api/models/event/mixin.js b/api/models/event/mixin.js index b5bfb46f4..bd7ab5c16 100644 --- a/api/models/event/mixin.js +++ b/api/models/event/mixin.js @@ -1,5 +1,5 @@ import { uniq, difference } from 'lodash/fp' -import moment from 'moment-timezone' +import { DateTime } from 'luxon' export default { isEvent () { @@ -8,7 +8,7 @@ export default { eventInvitees: function () { return this.belongsToMany(User).through(EventInvitation, 'event_id', 'user_id') - .withPivot('response') + .withPivot('response') }, eventInvitations: function () { @@ -49,24 +49,26 @@ export default { prettyEventDates: function (startTime, endTime) { if (!startTime && !endTime) return null - const start = moment(startTime) - const end = moment(endTime) + const dateNoYearWithTime = { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' } + const dateNoYearNoMonthWithTime = { weekday: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' } + const start = startTime instanceof Date ? DateTime.fromJSDate(startTime) : typeof startTime === 'number' ? DateTime.fromMillis(startTime) : DateTime.fromISO(startTime) + const end = endTime instanceof Date ? DateTime.fromJSDate(endTime) : typeof endTime === 'number' ? DateTime.fromMillis(endTime) : DateTime.fromISO(endTime) - const from = start.format('ddd, MMM D [at] h:mmA') + const from = start.toLocaleString(dateNoYearWithTime) let to = '' if (endTime) { if (end.month() !== start.month()) { - to = end.format(' - ddd, MMM D [at] h:mmA') + to = ` - ${end.toLocaleString(dateNoYearWithTime)}` } else if (end.day() !== start.day()) { - to = end.format(' - ddd D [at] h:mmA') + to = ` - ${end.toLocaleString(dateNoYearNoMonthWithTime)}` } else { - to = end.format(' - h:mmA') + to = ` - ${end.toLocaleString(DateTime.TIME_WITH_SECONDS)}` } } - return from + to + " UTC" + return from + to + ' UTC' }, createInviteNotifications: async function (userId, inviteeIds) { diff --git a/api/services/RequestValidation.js b/api/services/RequestValidation.js index d04f2468c..1057e8c47 100644 --- a/api/services/RequestValidation.js +++ b/api/services/RequestValidation.js @@ -1,27 +1,28 @@ -var moment = require('moment-timezone'); +const { DateTime } = require('luxon') module.exports = { - requireTimeRange: function(req, res) { - var valid = true; + requireTimeRange: function (req, res) { + let valid = true _.each(['start_time', 'end_time'], function (attr) { - var value = req.param(attr); + const value = req.param(attr) if (!value) { - res.badRequest(attr + ' is missing'); - valid = false; - return false; // break from each + res.badRequest(attr + ' is missing') + valid = false + return false // break from each } + const time = value instanceof Date ? DateTime.fromJSDate(value) : typeof value === 'number' ? DateTime.fromMillis(value) : DateTime.fromISO(value) - if (!moment(value).isValid()) { - res.badRequest(attr + ' is not a valid ISO8601 date string'); - valid = false; - return false; // break from each + if (!time.isValid) { + res.badRequest(attr + ' is not a valid ISO8601 date string') + valid = false + return false // break from each } - }); + }) - return valid; + return valid } } diff --git a/api/services/Search/util.js b/api/services/Search/util.js index 1fc117884..227fdb56b 100644 --- a/api/services/Search/util.js +++ b/api/services/Search/util.js @@ -1,6 +1,6 @@ import { GraphQLYogaError } from '@graphql-yoga/node' import { curry, includes, isEmpty, values } from 'lodash' -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import addTermToQueryBuilder from './addTermToQueryBuilder' export const filterAndSortPosts = curry((opts, q) => { @@ -32,7 +32,7 @@ export const filterAndSortPosts = curry((opts, q) => { reactions: 'posts.num_people_reacts', start_time: 'posts.start_time', updated: 'posts.updated_at', - votes: 'posts.num_people_reacts', // Need to remove once Mobile has been ported to reactions + votes: 'posts.num_people_reacts' // Need to remove once Mobile has been ported to reactions } const sort = sortColumns[sortBy] || values(sortColumns).find(v => v === 'posts.' + sortBy || v === sortBy) @@ -51,41 +51,41 @@ export const filterAndSortPosts = curry((opts, q) => { const { CHAT, DISCUSSION, REQUEST, OFFER, PROJECT, EVENT, RESOURCE } = Post.Type if (isAnnouncement) { - q.where('announcement', true).andWhere('posts.created_at', '>=', moment().subtract(1, 'month').toDate()) + q.where('announcement', true).andWhere('posts.created_at', '>=', DateTime.now().minus({ months: 1 }).toJSDate()) } if (isFulfilled === true) { q.where(q2 => { q2.whereNotNull('posts.fulfilled_at') - .orWhere('posts.end_time', '<', moment().toDate()) + .orWhere('posts.end_time', '<', DateTime.now().toJSDate()) }) } else if (isFulfilled === false) { q.whereNull('posts.fulfilled_at') - .andWhere(q2 => { - q2.whereNull('posts.end_time') - .orWhere('posts.end_time', '>=', moment().toDate()) - }) + .andWhere(q2 => { + q2.whereNull('posts.end_time') + .orWhere('posts.end_time', '>=', DateTime.now().toJSDate()) + }) } if (activePostsOnly) { q.whereNull('posts.fulfilled_at') - .andWhere(q2 => { - q2.whereNull('posts.end_time') - .orWhere('posts.end_time', '>=', moment().toDate()) - }) + .andWhere(q2 => { + q2.whereNull('posts.end_time') + .orWhere('posts.end_time', '>=', DateTime.now().toJSDate()) + }) } if (afterTime) { q.where(q2 => q2.where('posts.start_time', '>=', afterTime) - .orWhere('posts.end_time', '>=', afterTime) + .orWhere('posts.end_time', '>=', afterTime) ) } if (beforeTime) { q.where(q2 => q2.where('posts.start_time', '<', beforeTime) - .andWhere('posts.end_time', '<', beforeTime) + .andWhere('posts.end_time', '<', beforeTime) ) } @@ -111,7 +111,7 @@ export const filterAndSortPosts = curry((opts, q) => { if (!includes(values(Post.Type), type)) { throw new GraphQLYogaError(`unknown post type: "${type}"`) } - q.where({'posts.type': type}) + q.where({ 'posts.type': type }) } if (!isEmpty(search)) { @@ -144,7 +144,6 @@ export const filterAndSortPosts = curry((opts, q) => { } else if (sort) { q.orderBy(sort, order || (sortBy === 'order' ? 'asc' : 'desc')) } - }) export const filterAndSortUsers = curry(({ autocomplete, boundingBox, order, search, sortBy }, q) => { @@ -186,7 +185,6 @@ export const filterAndSortUsers = curry(({ autocomplete, boundingBox, order, sea }) export const filterAndSortGroups = curry((opts, q) => { - const { search, sortBy = 'name', boundingBox, order } = opts if (search) { diff --git a/cron.js b/cron.js index 3b614aa1b..60c93aeff 100644 --- a/cron.js +++ b/cron.js @@ -1,12 +1,12 @@ /* globals Nexudus */ -require("@babel/register") -var skiff = require('./lib/skiff') // this must be required first -var moment = require('moment-timezone') -var rollbar = require('./lib/rollbar') -var sails = skiff.sails -var digest2 = require('./lib/group/digest2') -var Promise = require('bluebird') -var { red } = require('chalk') +require('@babel/register') +const skiff = require('./lib/skiff') // this must be required first +const { DateTime } = require('luxon') +const rollbar = require('./lib/rollbar') +const sails = skiff.sails +const digest2 = require('./lib/group/digest2') +const Promise = require('bluebird') +const { red } = require('chalk') const savedSearches = require('./lib/group/digest2/savedSearches') const sendAndLogDigests = type => @@ -82,7 +82,7 @@ var runJob = Promise.method(name => { throw new Error(`Unknown job name: "${name}"`) } sails.log.debug(`Running ${name} job`) - const now = moment.tz('America/Los_Angeles') + const now = DateTime.now().setZone('America/Los_Angeles') return Promise.all(job(now)) }) diff --git a/lib/group/digest2/formatData.js b/lib/group/digest2/formatData.js index 5e1eb179c..238a18592 100644 --- a/lib/group/digest2/formatData.js +++ b/lib/group/digest2/formatData.js @@ -2,8 +2,7 @@ import { curry, every, filter, find, isEmpty, map, pickBy, sortBy } from 'lodash/fp' -import moment from 'moment-timezone' -import { TextHelpers } from 'hylo-shared' +import { DateTime } from 'luxon' const isChat = post => post.get('type') === 'chat' const isRequest = post => post.get('type') === 'request' @@ -18,7 +17,12 @@ export const presentAuthor = obj => const humanDate = (date) => { if (!date) return null - return moment(date).format('MMMM D, YYYY @ h:mma') + const formattedDate = date instanceof Date + ? DateTime.fromJSDate(date).toLocaleString(DateTime.DATETIME_FULL) + : typeof date === 'number' + ? DateTime.fromMillis(date).toLocaleString(DateTime.DATETIME_FULL) + : DateTime.fromISO(date).toLocaleString(DateTime.DATETIME_FULL) + return formattedDate } const presentPost = curry((slug, post) => { diff --git a/lib/group/digest2/savedSearches.js b/lib/group/digest2/savedSearches.js index 46b7e538a..dfd60281a 100644 --- a/lib/group/digest2/savedSearches.js +++ b/lib/group/digest2/savedSearches.js @@ -1,14 +1,21 @@ import { merge, pick, pickBy } from 'lodash/fp' -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import { pluralize } from '../../util/normalize' import { presentAuthor } from '../digest2/formatData' import { sendToUser } from '../digest2' -const humanDate = date => moment(date).format('MMMM D, YYYY') +const humanDate = date => { + const formattedDate = date instanceof Date + ? DateTime.fromJSDate(date).toLocaleString(DateTime.DATETIME_FULL) + : typeof date === 'number' + ? DateTime.fromMillis(date).toLocaleString(DateTime.DATETIME_FULL) + : DateTime.fromISO(date).toLocaleString(DateTime.DATETIME_FULL) + return formattedDate +} const presentPost = async (p, context, slug) => { - const post = await Post.where({id: p.id}).fetch() - await post.load(['linkPreview','user']) + const post = await Post.where({ id: p.id }).fetch() + await post.load(['linkPreview', 'user']) const { linkPreview } = post.relations return pickBy(x => x, { id: post.id, @@ -58,7 +65,7 @@ const prepareDigestData = async (searchId) => { const shouldSendData = (data, user, type) => { const postTypes = ['requests', 'offers', 'events', 'discussions', 'projects', 'resources'] const hasNewPosts = Object.keys(pick(postTypes, data)).some(s => postTypes.includes(s)) - const userSettingMatchesType = user.get('settings')['digest_frequency'] === type + const userSettingMatchesType = user.get('settings').digest_frequency === type return hasNewPosts && userSettingMatchesType } @@ -67,13 +74,13 @@ const sendDigest = async (searchId, type) => { const { lastPostId, user } = data if (shouldSendData(data, user, type)) return merge(await sendToUser(user, type, data), { lastPostId }) }) - .then(async (sent = {}) => { - const { lastPostId, success } = sent - if (success) { - const search = await SavedSearch.where({ id: searchId }).fetch() - return await search.updateLastPost(searchId, lastPostId) - } - }) + .then(async (sent = {}) => { + const { lastPostId, success } = sent + if (success) { + const search = await SavedSearch.where({ id: searchId }).fetch() + return await search.updateLastPost(searchId, lastPostId) + } + }) } export const sendAllDigests = async (type) => { diff --git a/lib/group/digest2/util.js b/lib/group/digest2/util.js index 0e4205e8e..9e182a568 100644 --- a/lib/group/digest2/util.js +++ b/lib/group/digest2/util.js @@ -1,23 +1,23 @@ -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import { includes } from 'lodash' import { get, pick, some } from 'lodash/fp' export const defaultTimezone = 'America/Los_Angeles' export const defaultTimeRange = type => { - const today = moment.tz(defaultTimezone).startOf('day').add(12, 'hours') + const today = DateTime.now().setZone(defaultTimezone).startOf('day').plus({ hours: 12 }) switch (type) { case 'daily': - return [today.clone().subtract(1, 'day'), today] + return [today.minus({ days: 1 }), today] case 'weekly': - return [today.clone().subtract(7, 'day'), today] + return [today.minus({ days: 7 }), today] } } export const isValidPostType = q => q.where(function () { this.whereNotIn('posts.type', ['welcome']) - .orWhere('posts.type', null) + .orWhere('posts.type', null) }) export const relatedUserColumns = (relationName = 'user') => ({ @@ -50,11 +50,13 @@ export const getPostsAndComments = (group, startTime, endTime) => q.where('posts.active', true) q.orderBy('id', 'asc') }) - .fetch({withRelated: [ - 'post', - relatedUserColumns(), - relatedUserColumns('post.user') - ]}) + .fetch({ + withRelated: [ + 'post', + relatedUserColumns(), + relatedUserColumns('post.user') + ] + }) .then(get('models')) }) @@ -67,7 +69,7 @@ export async function getRecipients (groupId, type) { const group = await Group.find(groupId) const recipients = await group.members().query(q => { q.whereRaw(`users.settings->>'digest_frequency' = '${type}'`) - q.whereRaw(`(group_memberships.settings->>'sendEmail')::boolean = true`) + q.whereRaw('(group_memberships.settings->>\'sendEmail\')::boolean = true') }).fetch().then(get('models')) return recipients diff --git a/package.json b/package.json index 6177fead1..2efcdec6a 100644 --- a/package.json +++ b/package.json @@ -116,11 +116,11 @@ "kue-ui": "^0.1.0", "link-preview-js": "^3.0.4", "lodash": "4.17.21", + "luxon": "^3.2.1", "md5": "^2.0.0", "mime": "^1.3.4", "mime-types": "^2.1.34", "minimist": "^1.1.0", - "moment-timezone": "^0.5.37", "newrelic": "^7.0.0", "node-fetch": "^2", "oidc-provider": "^7.11.3", diff --git a/test/unit/services/Search.test.js b/test/unit/services/Search.test.js index 0ed2e640f..5a6b5369c 100644 --- a/test/unit/services/Search.test.js +++ b/test/unit/services/Search.test.js @@ -1,4 +1,4 @@ -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import { expectEqualQuery } from '../../setup/helpers' import setup from '../../setup' @@ -6,11 +6,11 @@ describe('Search', function () { describe('.forPosts', function () { // TODO: fix this by reorganizing the search and filter code for posts to join groups_posts in the right place it.skip('produces the expected SQL for a complex query', function () { - var startTime = moment('2015-03-24 19:54:12-04:00') - var endTime = moment('2015-03-31 19:54:12-04:00') - var tz = moment.tz.guess() - var startTimeAsString = startTime.tz(tz).format('YYYY-MM-DD HH:mm:ss.SSS') - var endTimeAsString = endTime.tz(tz).format('YYYY-MM-DD HH:mm:ss.SSS') + const startTime = DateTime.fromISO('2015-03-24 19:54:12-04:00') + const endTime = DateTime.fromISO('2015-03-31 19:54:12-04:00') + const timeZone = DateTime.local().zoneName + const startTimeAsString = startTime.setZone(timeZone).toLocaleString({ ...DateTime.DATETIME_SHORT, timeZoneName: 'short' }) + const endTimeAsString = endTime.setZone(timeZone).toLocaleString({ ...DateTime.DATETIME_SHORT, timeZoneName: 'short' }) const search = Search.forPosts({ limit: 5, @@ -20,8 +20,8 @@ describe('Search', function () { follower: 37, term: 'milk toast', type: 'request', - start_time: startTime.toDate(), - end_time: endTime.toDate(), + start_time: startTime.toJSDate(), + end_time: endTime.toJSDate(), sort: 'posts.updated_at' }) diff --git a/test/unit/services/digest2.test.js b/test/unit/services/digest2.test.js index 313d25bdd..a12142f75 100644 --- a/test/unit/services/digest2.test.js +++ b/test/unit/services/digest2.test.js @@ -1,4 +1,4 @@ -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import formatData from '../../../lib/group/digest2/formatData' import personalizeData from '../../../lib/group/digest2/personalizeData' import { defaultTimezone, shouldSendData, getRecipients } from '../../../lib/group/digest2/util' @@ -35,7 +35,7 @@ const u4 = model({ avatar_url: 'http://cnn.com/man.png' }) -const group = model({slug: 'foo'}) +const group = model({ slug: 'foo' }) const linkPreview = model({ id: '1', @@ -56,7 +56,7 @@ describe('group digest v2', () => { post_id: 5, relations: { user: u3, - post: model({id: 5, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: {user: u4}}) + post: model({ id: 5, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: { user: u4 } }) } }), model({ @@ -65,7 +65,7 @@ describe('group digest v2', () => { post_id: 8, relations: { user: u3, - post: model({id: 8, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: {user: u4}}) + post: model({ id: 8, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: { user: u4 } }) } }), model({ @@ -74,7 +74,7 @@ describe('group digest v2', () => { post_id: 8, relations: { user: u3, - post: model({id: 8, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: {user: u4}}) + post: model({ id: 8, name: 'Old Post, New Comments', summary: () => 'Old Post, New Comments', details: () => {}, relations: { user: u4 } }) } }) @@ -133,16 +133,16 @@ describe('group digest v2', () => { relations: { user: u2, children: collection([ - model({name: 'I need things'}), - model({name: 'and love'}), - model({name: 'and more things'}) + model({ name: 'I need things' }), + model({ name: 'and love' }), + model({ name: 'and more things' }) ]) } }), model({ id: 78, name: '', - details: () => "A chat!", + details: () => 'A chat!', summary: () => 'A chat!', type: 'chat', relations: { @@ -168,7 +168,7 @@ describe('group digest v2', () => { id: 5, title: 'Do you have a dollar?', user: u1.attributes, - url: Frontend.Route.post({id: 5}, group), + url: Frontend.Route.post({ id: 5 }, group), comments: [ { id: 12, @@ -187,7 +187,7 @@ describe('group digest v2', () => { id: 6, title: 'I have cookies!', user: u2.attributes, - url: Frontend.Route.post({id: 6}, group), + url: Frontend.Route.post({ id: 6 }, group), comments: [] } ], @@ -197,7 +197,7 @@ describe('group digest v2', () => { id: 7, title: 'Kapow!', user: u2.attributes, - url: Frontend.Route.post({id: 7}, group), + url: Frontend.Route.post({ id: 7 }, group), comments: [], link_preview: omit(linkPreview.attributes, 'id') } @@ -209,7 +209,7 @@ describe('group digest v2', () => { location: 'Home', when: 'December 17, 1995 @ 6:30pm', user: u2.attributes, - url: Frontend.Route.post({id: 76}, group), + url: Frontend.Route.post({ id: 76 }, group), comments: [] } ], @@ -218,7 +218,7 @@ describe('group digest v2', () => { id: 77, title: 'A project with requests', user: u2.attributes, - url: Frontend.Route.post({id: 77}, group), + url: Frontend.Route.post({ id: 77 }, group), comments: [] } ], @@ -226,7 +226,7 @@ describe('group digest v2', () => { { id: 8, title: 'Old Post, New Comments', - url: Frontend.Route.post({id: 8}, group), + url: Frontend.Route.post({ id: 8 }, group), comments: [ { id: 13, @@ -266,10 +266,10 @@ describe('group digest v2', () => { model({ id: 1, name: 'Foo!', - summary: () => `Foo!`, - details: () => `

Edward West & ` + - `Julia Pope ` + - `#oakland

`, + summary: () => 'Foo!', + details: () => '

Edward West & ' + + 'Julia Pope ' + + '#oakland

', type: 'request', relations: { user: u1 @@ -291,7 +291,7 @@ describe('group digest v2', () => { `Julia Pope ` + `#oakland

`, user: u1.attributes, - url: Frontend.Route.post({id: 1}, group), + url: Frontend.Route.post({ id: 1 }, group), comments: [] } ], @@ -304,7 +304,7 @@ describe('group digest v2', () => { }) it('sets the no_new_activity key if there is no data', () => { - const data = {posts: [], comments: []} + const data = { posts: [], comments: [] } expect(formatData(group, data)).to.deep.equal({ topicsWithChats: [], @@ -321,10 +321,10 @@ describe('group digest v2', () => { }) describe('personalizeData', () => { - var user + let user before(() => { - user = factories.user({avatar_url: 'http://google.com/logo.png'}) + user = factories.user({ avatar_url: 'http://google.com/logo.png' }) return user.save() }) @@ -355,8 +355,8 @@ describe('group digest v2', () => { details: '

foo@bar.com and ' + `Person

`, comments: [ - {id: 3, user: user.pick('id', 'avatar_url'), text: 'Na'}, - {id: 4, user: u2.attributes, text: `Woa Bob`} + { id: 3, user: user.pick('id', 'avatar_url'), text: 'Na' }, + { id: 4, user: u2.attributes, text: `Woa Bob` } ], url: 'https://www.hylo.com/all/post/2' } @@ -381,12 +381,12 @@ describe('group digest v2', () => { title: 'Ya', user: u3.attributes, details: '

foo@bar.com and ' + - `Person

`, + `Person

`, reply_url: Email.postReplyAddress(2, user.id), url: 'https://www.hylo.com/all/post/2?' + ctParams, comments: [ - {id: 3, user: user.pick('id', 'avatar_url'), text: 'Na'}, - {id: 4, user: u2.attributes, text: `Woa Bob`} + { id: 3, user: user.pick('id', 'avatar_url'), text: 'Na' }, + { id: 4, user: u2.attributes, text: `Woa Bob` } ] } ], @@ -398,7 +398,7 @@ describe('group digest v2', () => { post_creation_action_url: Frontend.Route.emailPostForm(), reply_action_url: Frontend.Route.emailBatchCommentForm(), form_token: Email.formToken(77, user.id), - tracking_pixel_url: Analytics.pixelUrl('Digest', {userId: user.id, group: 'foo'}), + tracking_pixel_url: Analytics.pixelUrl('Digest', { userId: user.id, group: 'foo' }), subject: `Hi | ${u4.name}`, group_url: 'https://www.hylo.com/groups/foo?' + ctParams })) @@ -408,35 +408,35 @@ describe('group digest v2', () => { describe('shouldSendData', () => { it('is false if the data is empty', () => { - const data = {requests: [], offers: [], discussions: []} + const data = { requests: [], offers: [], discussions: [] } return shouldSendData(data).then(val => expect(val).to.be.false) }) it('is true if there is some data', () => { - const data = {discussions: [{id: 'foo'}]} + const data = { discussions: [{ id: 'foo' }] } return shouldSendData(data).then(val => expect(val).to.be.true) }) }) describe('sendAllDigests', () => { - var args, u1, u2, group, post + let args, u1, u2, group, post before(async () => { spyify(Email, 'sendSimpleEmail', function () { args = arguments }) - const six = moment.tz(defaultTimezone).startOf('day').add(6, 'hours') + const six = DateTime.now().setZone(defaultTimezone).startOf('day').plus({ hours: 6 }) u1 = await factories.user({ active: true, - settings: {digest_frequency: 'daily'}, + settings: { digest_frequency: 'daily' }, avatar_url: 'av1' }).save() - u2 = await factories.user({avatar_url: 'av2'}).save() + u2 = await factories.user({ avatar_url: 'av2' }).save() group = await factories.group({ avatar_url: 'foo' }).save() - post = await factories.post({created_at: six, user_id: u2.id, type: 'discussion'}).save() + post = await factories.post({ created_at: six, user_id: u2.id, type: 'discussion' }).save() await post.groups().attach(group.id) await group.addMembers([u1.id], { - settings: {sendEmail: true} + settings: { sendEmail: true } }) }) @@ -490,7 +490,7 @@ describe('group digest v2', () => { }) describe('sendDigest', () => { - var group + let group beforeEach(() => { group = factories.group() @@ -500,24 +500,24 @@ describe('group digest v2', () => { describe('when there is no data', () => { it('does not send -- feature disabled', () => { return sendDigest(group.id, 'daily') - .then(result => expect(result).to.equal(false)) + .then(result => expect(result).to.equal(false)) }) }) }) }) describe('getRecipients', () => { - var g, uIn1, uOut1, uOut2, uOut3, uOut4, uOut5, uIn2 + let g, uIn1, uOut1, uOut2, uOut3, uOut4, uOut5, uIn2 before(async () => { - const settings = {digest_frequency: 'daily'} - uIn1 = factories.user({settings}) - uOut1 = factories.user({active: false, settings}) // inactive user - uOut2 = factories.user({settings}) // inactive membership - uOut3 = factories.user({settings}) // send_email = false - uOut4 = factories.user({settings: {digest_frequency: 'weekly'}}) // digest_frequency = 'weekly' - uOut5 = factories.user({settings}) // not in the group - uIn2 = factories.user({settings}) + const settings = { digest_frequency: 'daily' } + uIn1 = factories.user({ settings }) + uOut1 = factories.user({ active: false, settings }) // inactive user + uOut2 = factories.user({ settings }) // inactive membership + uOut3 = factories.user({ settings }) // send_email = false + uOut4 = factories.user({ settings: { digest_frequency: 'weekly' } }) // digest_frequency = 'weekly' + uOut5 = factories.user({ settings }) // not in the group + uIn2 = factories.user({ settings }) g = factories.group() await Promise.join( uIn1.save(), @@ -531,19 +531,19 @@ describe('getRecipients', () => { ) await g.addMembers([uIn1, uOut1, uOut2, uOut4, uIn2], { - settings: {sendEmail: true} + settings: { sendEmail: true } }) - await g.addMembers([uOut3], {settings: {sendEmail: false}}) + await g.addMembers([uOut3], { settings: { sendEmail: false } }) await g.removeMembers([uOut2]) }) it('only returns active members with email turned on and the right digest type', () => { return getRecipients(g.id, 'daily') - .then(models => { - expect(models.length).to.equal(2) - expect(models.map(m => m.id).sort()) - .to.deep.equal([uIn1.id, uIn2.id].sort()) - }) + .then(models => { + expect(models.length).to.equal(2) + expect(models.map(m => m.id).sort()) + .to.deep.equal([uIn1.id, uIn2.id].sort()) + }) }) }) diff --git a/yarn.lock b/yarn.lock index 69af3bbbc..9236978ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8598,6 +8598,11 @@ lru-cache@~2.5.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.5.2.tgz#1fddad938aae1263ce138680be1b3f591c0ab41c" integrity sha512-wyqfj+623mgqv+bpjTdivSoC/LtY9oOrmKz2Cke0NZcgYW9Kce/qWjd9e5PDYf8wuiKfVeo8VnyOSSyeRiUsLw== +luxon@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.2.1.tgz#14f1af209188ad61212578ea7e3d518d18cee45f" + integrity sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg== + machine-as-action@^10.3.1: version "10.3.1" resolved "https://registry.yarnpkg.com/machine-as-action/-/machine-as-action-10.3.1.tgz#a699e7da529705675b6b892654b034378ea596dc" @@ -9100,18 +9105,6 @@ mock-require@^2.0.2: dependencies: caller-id "^0.1.0" -moment-timezone@^0.5.37: - version "0.5.40" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.40.tgz#c148f5149fd91dd3e29bf481abc8830ecba16b89" - integrity sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0": - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - moo@^0.5.0, moo@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"