From b0fefdbb98674ab564806545f4d2411e9e6a8fe1 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Fri, 13 Apr 2018 16:27:06 -0400 Subject: [PATCH] Implementing grouping messages by date --- .../media-gallery/MediaGallery.md | 35 +++- .../media-gallery/MediaGallery.tsx | 21 +-- .../media-gallery/groupMessagesByDate.ts | 162 ++++++++++++++---- .../media-gallery/groupMessagesByDate.ts | 119 ++++++------- 4 files changed, 227 insertions(+), 110 deletions(-) diff --git a/ts/components/conversation/media-gallery/MediaGallery.md b/ts/components/conversation/media-gallery/MediaGallery.md index cf862db7f..16159c16e 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.md +++ b/ts/components/conversation/media-gallery/MediaGallery.md @@ -1,14 +1,16 @@ ```jsx -const YEAR_MS = 1 * 12 * 30 * 24 * 60 * 60 * 1000; +const DAY_MS = 24 * 60 * 60 * 1000; +const MONTH_MS = 30 * DAY_MS; +const YEAR_MS = 12 * MONTH_MS; const tokens = ['foo', 'bar', 'baz', 'qux', 'quux']; const fileExtensions = ['docx', 'pdf', 'txt', 'mp3', 'wmv', 'tiff']; -const createRandomMessage = (props) => { +const createRandomMessage = ({startTime, timeWindow} = {}) => (props) => { const now = Date.now(); const fileName = `${_.sample(tokens)}${_.sample(tokens)}.${_.sample(fileExtensions)}`; return { id: _.random(now).toString(), - received_at: _.random(now - YEAR_MS, now), + received_at: _.random(startTime, startTime + timeWindow), attachments: [{ data: null, fileName, @@ -21,9 +23,34 @@ const createRandomMessage = (props) => { }; }; +const createRandomMessages = ({startTime, timeWindow}) => + _.range(_.random(5, 10)).map(createRandomMessage({startTime, timeWindow})); + + const startTime = Date.now(); const messages = _.sortBy( - _.range(25).map(createRandomMessage), + [ + ...createRandomMessages({ + startTime, + timeWindow: DAY_MS, + }), + ...createRandomMessages({ + startTime: startTime - DAY_MS, + timeWindow: DAY_MS, + }), + ...createRandomMessages({ + startTime: startTime - 3 * DAY_MS, + timeWindow: 3 * DAY_MS, + }), + ...createRandomMessages({ + startTime: startTime - 30 * DAY_MS, + timeWindow: 15 * DAY_MS, + }), + ...createRandomMessages({ + startTime: startTime - 365 * DAY_MS, + timeWindow: 300 * DAY_MS, + }), + ], message => -message.received_at ); diff --git a/ts/components/conversation/media-gallery/MediaGallery.tsx b/ts/components/conversation/media-gallery/MediaGallery.tsx index f5cf64b70..c12cf19a9 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.tsx +++ b/ts/components/conversation/media-gallery/MediaGallery.tsx @@ -4,7 +4,6 @@ import React from 'react'; import moment from 'moment'; -import { map } from 'lodash'; import { AttachmentListSection } from './AttachmentListSection'; import { groupMessagesByDate } from './groupMessagesByDate'; @@ -123,23 +122,21 @@ export class MediaGallery extends React.Component { } const now = Date.now(); - const groups = groupMessagesByDate(now, messages); - return map(groups, (annotations) => { - const first = annotations[0]; - const date = moment(first.message.received_at); - - const header = first.label === 'yearMonth' - ? date.format(MONTH_FORMAT) - : i18n(first.label); - const groupMessages = map(annotations, 'message'); - + const sections = groupMessagesByDate(now, messages); + return sections.map(section => { + const first = section.messages[0]; + const date = moment(first.received_at); + const header = + section.type === 'yearMonth' + ? date.format(MONTH_FORMAT) + : i18n(section.type); return ( ); }); diff --git a/ts/components/conversation/media-gallery/groupMessagesByDate.ts b/ts/components/conversation/media-gallery/groupMessagesByDate.ts index c1c9fd1c0..845c9e37e 100644 --- a/ts/components/conversation/media-gallery/groupMessagesByDate.ts +++ b/ts/components/conversation/media-gallery/groupMessagesByDate.ts @@ -5,11 +5,25 @@ import moment from 'moment'; import { compact, groupBy, sortBy } from 'lodash'; import { Message } from './propTypes/Message'; +// import { missingCaseError } from '../../../missingCaseError'; +type StaticSectionType = 'today' | 'yesterday' | 'thisWeek' | 'thisMonth'; +type YearMonthSectionType = 'yearMonth'; + +interface GenericSection { + type: T; + messages: Array; +} +type StaticSection = GenericSection; +type YearMonthSection = GenericSection & { + year: number; + month: number; +}; +export type Section = StaticSection | YearMonthSection; export const groupMessagesByDate = ( timestamp: number, messages: Array -): any => { +): Array
=> { const referenceDateTime = moment.utc(timestamp); const today = moment(referenceDateTime).startOf('day'); const yesterday = moment(referenceDateTime) @@ -18,42 +32,132 @@ export const groupMessagesByDate = ( const thisWeek = moment(referenceDateTime).startOf('isoWeek'); const thisMonth = moment(referenceDateTime).startOf('month'); - const sorted = sortBy(messages, message => -message.received_at); - const annotations = sorted.map(message => { - const date = moment.utc(message.received_at); + const sortedMessages = sortBy(messages, message => -message.received_at); + const messagesWithSection = sortedMessages.map( + withSection({ + today, + yesterday, + thisWeek, + thisMonth, + }) + ); + const groupedMessages = groupBy(messagesWithSection, 'type'); + const yearMonthMessages = Object.values( + groupBy(groupedMessages.yearMonth, 'order') + ).reverse(); + return compact([ + toSection(groupedMessages.today), + toSection(groupedMessages.yesterday), + toSection(groupedMessages.thisWeek), + toSection(groupedMessages.thisMonth), + ...yearMonthMessages.map(group => toSection(group)), + ]); +}; - if (date.isAfter(today)) { - return { - order: 0, - label: 'today', - message, - }; - } else if (date.isAfter(yesterday)) { - return { - order: 1, - label: 'yesterday', - message, - }; - } else if (date.isAfter(thisWeek)) { +const toSection = ( + messagesWithSection: Array | undefined +): Section | null => { + if (!messagesWithSection || messagesWithSection.length === 0) { + return null; + } + + const firstMessageWithSection: MessageWithSection = messagesWithSection[0]; + if (!firstMessageWithSection) { + return null; + } + + const messages = messagesWithSection.map( + messageWithSection => messageWithSection.message + ); + switch (firstMessageWithSection.type) { + case 'today': + case 'yesterday': + case 'thisWeek': + case 'thisMonth': return { - order: 2, - label: 'thisWeek', - message, + type: firstMessageWithSection.type, + messages: messages, }; - } else if (date.isAfter(thisMonth)) { + case 'yearMonth': return { - order: 3, - label: 'thisMonth', - message, + type: firstMessageWithSection.type, + year: firstMessageWithSection.year, + month: firstMessageWithSection.month, + messages, }; - } + default: + // NOTE: Investigate why we get the following error: + // error TS2345: Argument of type 'any' is not assignable to parameter + // of type 'never'. + // return missingCaseError(firstMessageWithSection.type); + return null; + } +}; + +type GenericMessageWithSection = { + order: number; + type: T; + message: Message; +}; +type MessageWithStaticSection = GenericMessageWithSection; +type MessageWithYearMonthSection = GenericMessageWithSection< + YearMonthSectionType +> & { + year: number; + month: number; +}; +type MessageWithSection = + | MessageWithStaticSection + | MessageWithYearMonthSection; +const withSection = ({ + today, + yesterday, + thisWeek, + thisMonth, +}: { + today: moment.Moment; + yesterday: moment.Moment; + thisWeek: moment.Moment; + thisMonth: moment.Moment; +}) => (message: Message): MessageWithSection => { + const messageReceivedDate = moment.utc(message.received_at); + if (messageReceivedDate.isAfter(today)) { + return { + order: 0, + type: 'today', + message, + }; + } + if (messageReceivedDate.isAfter(yesterday)) { + return { + order: 1, + type: 'yesterday', + message, + }; + } + if (messageReceivedDate.isAfter(thisWeek)) { + return { + order: 2, + type: 'thisWeek', + message, + }; + } + if (messageReceivedDate.isAfter(thisMonth)) { return { - order: date.year() * 100 + date.month(), - label: 'yearMonth', + order: 3, + type: 'thisMonth', message, }; - }); + } - return groupBy(annotations, 'label'); + const month: number = messageReceivedDate.month(); + const year: number = messageReceivedDate.year(); + return { + order: year * 100 + month, + type: 'yearMonth', + month, + year, + message, + }; }; diff --git a/ts/test/components/media-gallery/groupMessagesByDate.ts b/ts/test/components/media-gallery/groupMessagesByDate.ts index b74218686..050e91512 100644 --- a/ts/test/components/media-gallery/groupMessagesByDate.ts +++ b/ts/test/components/media-gallery/groupMessagesByDate.ts @@ -2,9 +2,14 @@ * @prettier */ import 'mocha'; + import { assert } from 'chai'; +import { shuffle } from 'lodash'; -import { groupMessagesByDate } from '../../../components/conversation/media-gallery/groupMessagesByDate'; +import { + groupMessagesByDate, + Section, +} from '../../../components/conversation/media-gallery/groupMessagesByDate'; import { Message } from '../../../components/conversation/media-gallery/propTypes/Message'; const toMessage = (date: Date): Message => ({ @@ -16,7 +21,7 @@ const toMessage = (date: Date): Message => ({ describe('groupMessagesByDate', () => { it('should group messages', () => { const referenceTime = new Date('2018-04-12T18:00Z').getTime(); // Thu - const input: Array = [ + const input: Array = shuffle([ // Today toMessage(new Date('2018-04-12T12:00Z')), // Thu toMessage(new Date('2018-04-12T00:01Z')), // Thu @@ -32,110 +37,94 @@ describe('groupMessagesByDate', () => { // February 2011 toMessage(new Date('2011-02-28T23:59Z')), toMessage(new Date('2011-02-01T10:00Z')), - ]; + ]); - const expected = { - today: [ - { - order: 0, - label: 'today', - message: { + const expected: Array
= [ + { + type: 'today', + messages: [ + { id: 'Thu, 12 Apr 2018 12:00:00 GMT', received_at: 1523534400000, attachments: [], }, - }, - { - order: 0, - label: 'today', - message: { + { id: 'Thu, 12 Apr 2018 00:01:00 GMT', received_at: 1523491260000, attachments: [], }, - }, - ], - yesterday: [ - { - order: 1, - label: 'yesterday', - message: { + ], + }, + { + type: 'yesterday', + messages: [ + { id: 'Wed, 11 Apr 2018 23:59:00 GMT', received_at: 1523491140000, attachments: [], }, - }, - ], - thisWeek: [ - { - order: 2, - label: 'thisWeek', - message: { + ], + }, + { + type: 'thisWeek', + messages: [ + { id: 'Mon, 09 Apr 2018 00:01:00 GMT', received_at: 1523232060000, attachments: [], }, - }, - ], - thisMonth: [ - { - order: 3, - label: 'thisMonth', - message: { + ], + }, + { + type: 'thisMonth', + messages: [ + { id: 'Sun, 08 Apr 2018 23:59:00 GMT', received_at: 1523231940000, attachments: [], }, - }, - { - order: 3, - label: 'thisMonth', - message: { + { id: 'Sun, 01 Apr 2018 00:01:00 GMT', received_at: 1522540860000, attachments: [], }, - }, - ], - yearMonth: [ - { - order: 201802, - label: 'yearMonth', - message: { + ], + }, + { + type: 'yearMonth', + year: 2018, + month: 2, + messages: [ + { id: 'Sat, 31 Mar 2018 23:59:00 GMT', received_at: 1522540740000, attachments: [], }, - }, - { - order: 201802, - label: 'yearMonth', - message: { + { id: 'Thu, 01 Mar 2018 14:00:00 GMT', received_at: 1519912800000, attachments: [], }, - }, - { - order: 201101, - label: 'yearMonth', - message: { + ], + }, + { + type: 'yearMonth', + year: 2011, + month: 1, + messages: [ + { id: 'Mon, 28 Feb 2011 23:59:00 GMT', received_at: 1298937540000, attachments: [], }, - }, - { - order: 201101, - label: 'yearMonth', - message: { + { id: 'Tue, 01 Feb 2011 10:00:00 GMT', received_at: 1296554400000, attachments: [], }, - }, - ], - }; + ], + }, + ]; const actual = groupMessagesByDate(referenceTime, input); assert.deepEqual(actual, expected);