Implementing grouping messages by date
This commit is contained in:
parent
dea56c0008
commit
b0fefdbb98
4 changed files with 232 additions and 115 deletions
|
@ -1,14 +1,16 @@
|
||||||
```jsx
|
```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 tokens = ['foo', 'bar', 'baz', 'qux', 'quux'];
|
||||||
const fileExtensions = ['docx', 'pdf', 'txt', 'mp3', 'wmv', 'tiff'];
|
const fileExtensions = ['docx', 'pdf', 'txt', 'mp3', 'wmv', 'tiff'];
|
||||||
const createRandomMessage = (props) => {
|
const createRandomMessage = ({startTime, timeWindow} = {}) => (props) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const fileName =
|
const fileName =
|
||||||
`${_.sample(tokens)}${_.sample(tokens)}.${_.sample(fileExtensions)}`;
|
`${_.sample(tokens)}${_.sample(tokens)}.${_.sample(fileExtensions)}`;
|
||||||
return {
|
return {
|
||||||
id: _.random(now).toString(),
|
id: _.random(now).toString(),
|
||||||
received_at: _.random(now - YEAR_MS, now),
|
received_at: _.random(startTime, startTime + timeWindow),
|
||||||
attachments: [{
|
attachments: [{
|
||||||
data: null,
|
data: null,
|
||||||
fileName,
|
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 startTime = Date.now();
|
||||||
const messages = _.sortBy(
|
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
|
message => -message.received_at
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { map } from 'lodash';
|
|
||||||
|
|
||||||
import { AttachmentListSection } from './AttachmentListSection';
|
import { AttachmentListSection } from './AttachmentListSection';
|
||||||
import { groupMessagesByDate } from './groupMessagesByDate';
|
import { groupMessagesByDate } from './groupMessagesByDate';
|
||||||
|
@ -123,23 +122,21 @@ export class MediaGallery extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const groups = groupMessagesByDate(now, messages);
|
const sections = groupMessagesByDate(now, messages);
|
||||||
return map(groups, (annotations) => {
|
return sections.map(section => {
|
||||||
const first = annotations[0];
|
const first = section.messages[0];
|
||||||
const date = moment(first.message.received_at);
|
const date = moment(first.received_at);
|
||||||
|
const header =
|
||||||
const header = first.label === 'yearMonth'
|
section.type === 'yearMonth'
|
||||||
? date.format(MONTH_FORMAT)
|
? date.format(MONTH_FORMAT)
|
||||||
: i18n(first.label);
|
: i18n(section.type);
|
||||||
const groupMessages = map(annotations, 'message');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttachmentListSection
|
<AttachmentListSection
|
||||||
key={header}
|
key={header}
|
||||||
header={header}
|
header={header}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
type={type}
|
type={type}
|
||||||
messages={groupMessages}
|
messages={section.messages}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,11 +5,25 @@ import moment from 'moment';
|
||||||
import { compact, groupBy, sortBy } from 'lodash';
|
import { compact, groupBy, sortBy } from 'lodash';
|
||||||
|
|
||||||
import { Message } from './propTypes/Message';
|
import { Message } from './propTypes/Message';
|
||||||
|
// import { missingCaseError } from '../../../missingCaseError';
|
||||||
|
|
||||||
|
type StaticSectionType = 'today' | 'yesterday' | 'thisWeek' | 'thisMonth';
|
||||||
|
type YearMonthSectionType = 'yearMonth';
|
||||||
|
|
||||||
|
interface GenericSection<T> {
|
||||||
|
type: T;
|
||||||
|
messages: Array<Message>;
|
||||||
|
}
|
||||||
|
type StaticSection = GenericSection<StaticSectionType>;
|
||||||
|
type YearMonthSection = GenericSection<YearMonthSectionType> & {
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
};
|
||||||
|
export type Section = StaticSection | YearMonthSection;
|
||||||
export const groupMessagesByDate = (
|
export const groupMessagesByDate = (
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
messages: Array<Message>
|
messages: Array<Message>
|
||||||
): any => {
|
): Array<Section> => {
|
||||||
const referenceDateTime = moment.utc(timestamp);
|
const referenceDateTime = moment.utc(timestamp);
|
||||||
const today = moment(referenceDateTime).startOf('day');
|
const today = moment(referenceDateTime).startOf('day');
|
||||||
const yesterday = moment(referenceDateTime)
|
const yesterday = moment(referenceDateTime)
|
||||||
|
@ -18,42 +32,132 @@ export const groupMessagesByDate = (
|
||||||
const thisWeek = moment(referenceDateTime).startOf('isoWeek');
|
const thisWeek = moment(referenceDateTime).startOf('isoWeek');
|
||||||
const thisMonth = moment(referenceDateTime).startOf('month');
|
const thisMonth = moment(referenceDateTime).startOf('month');
|
||||||
|
|
||||||
const sorted = sortBy(messages, message => -message.received_at);
|
const sortedMessages = sortBy(messages, message => -message.received_at);
|
||||||
const annotations = sorted.map(message => {
|
const messagesWithSection = sortedMessages.map(
|
||||||
const date = moment.utc(message.received_at);
|
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)) {
|
const toSection = (
|
||||||
return {
|
messagesWithSection: Array<MessageWithSection> | undefined
|
||||||
order: 0,
|
): Section | null => {
|
||||||
label: 'today',
|
if (!messagesWithSection || messagesWithSection.length === 0) {
|
||||||
message,
|
return null;
|
||||||
};
|
}
|
||||||
} else if (date.isAfter(yesterday)) {
|
|
||||||
return {
|
|
||||||
order: 1,
|
|
||||||
label: 'yesterday',
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
} else if (date.isAfter(thisWeek)) {
|
|
||||||
return {
|
|
||||||
order: 2,
|
|
||||||
label: 'thisWeek',
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
} else if (date.isAfter(thisMonth)) {
|
|
||||||
return {
|
|
||||||
order: 3,
|
|
||||||
label: 'thisMonth',
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
||||||
|
type: firstMessageWithSection.type,
|
||||||
|
messages: messages,
|
||||||
|
};
|
||||||
|
case 'yearMonth':
|
||||||
|
return {
|
||||||
|
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<T> = {
|
||||||
|
order: number;
|
||||||
|
type: T;
|
||||||
|
message: Message;
|
||||||
|
};
|
||||||
|
type MessageWithStaticSection = GenericMessageWithSection<StaticSectionType>;
|
||||||
|
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 {
|
return {
|
||||||
order: date.year() * 100 + date.month(),
|
order: 0,
|
||||||
label: 'yearMonth',
|
type: 'today',
|
||||||
message,
|
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: 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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,14 @@
|
||||||
* @prettier
|
* @prettier
|
||||||
*/
|
*/
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import { groupMessagesByDate } from '../../../components/conversation/media-gallery/groupMessagesByDate';
|
import { assert } from 'chai';
|
||||||
|
import { shuffle } from 'lodash';
|
||||||
|
|
||||||
|
import {
|
||||||
|
groupMessagesByDate,
|
||||||
|
Section,
|
||||||
|
} from '../../../components/conversation/media-gallery/groupMessagesByDate';
|
||||||
import { Message } from '../../../components/conversation/media-gallery/propTypes/Message';
|
import { Message } from '../../../components/conversation/media-gallery/propTypes/Message';
|
||||||
|
|
||||||
const toMessage = (date: Date): Message => ({
|
const toMessage = (date: Date): Message => ({
|
||||||
|
@ -16,7 +21,7 @@ const toMessage = (date: Date): Message => ({
|
||||||
describe('groupMessagesByDate', () => {
|
describe('groupMessagesByDate', () => {
|
||||||
it('should group messages', () => {
|
it('should group messages', () => {
|
||||||
const referenceTime = new Date('2018-04-12T18:00Z').getTime(); // Thu
|
const referenceTime = new Date('2018-04-12T18:00Z').getTime(); // Thu
|
||||||
const input: Array<Message> = [
|
const input: Array<Message> = shuffle([
|
||||||
// Today
|
// Today
|
||||||
toMessage(new Date('2018-04-12T12:00Z')), // Thu
|
toMessage(new Date('2018-04-12T12:00Z')), // Thu
|
||||||
toMessage(new Date('2018-04-12T00:01Z')), // Thu
|
toMessage(new Date('2018-04-12T00:01Z')), // Thu
|
||||||
|
@ -32,110 +37,94 @@ describe('groupMessagesByDate', () => {
|
||||||
// February 2011
|
// February 2011
|
||||||
toMessage(new Date('2011-02-28T23:59Z')),
|
toMessage(new Date('2011-02-28T23:59Z')),
|
||||||
toMessage(new Date('2011-02-01T10:00Z')),
|
toMessage(new Date('2011-02-01T10:00Z')),
|
||||||
];
|
]);
|
||||||
|
|
||||||
const expected = {
|
const expected: Array<Section> = [
|
||||||
today: [
|
{
|
||||||
{
|
type: 'today',
|
||||||
order: 0,
|
messages: [
|
||||||
label: 'today',
|
{
|
||||||
message: {
|
|
||||||
id: 'Thu, 12 Apr 2018 12:00:00 GMT',
|
id: 'Thu, 12 Apr 2018 12:00:00 GMT',
|
||||||
received_at: 1523534400000,
|
received_at: 1523534400000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
order: 0,
|
|
||||||
label: 'today',
|
|
||||||
message: {
|
|
||||||
id: 'Thu, 12 Apr 2018 00:01:00 GMT',
|
id: 'Thu, 12 Apr 2018 00:01:00 GMT',
|
||||||
received_at: 1523491260000,
|
received_at: 1523491260000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
yesterday: [
|
{
|
||||||
{
|
type: 'yesterday',
|
||||||
order: 1,
|
messages: [
|
||||||
label: 'yesterday',
|
{
|
||||||
message: {
|
|
||||||
id: 'Wed, 11 Apr 2018 23:59:00 GMT',
|
id: 'Wed, 11 Apr 2018 23:59:00 GMT',
|
||||||
received_at: 1523491140000,
|
received_at: 1523491140000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
thisWeek: [
|
{
|
||||||
{
|
type: 'thisWeek',
|
||||||
order: 2,
|
messages: [
|
||||||
label: 'thisWeek',
|
{
|
||||||
message: {
|
|
||||||
id: 'Mon, 09 Apr 2018 00:01:00 GMT',
|
id: 'Mon, 09 Apr 2018 00:01:00 GMT',
|
||||||
received_at: 1523232060000,
|
received_at: 1523232060000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
thisMonth: [
|
{
|
||||||
{
|
type: 'thisMonth',
|
||||||
order: 3,
|
messages: [
|
||||||
label: 'thisMonth',
|
{
|
||||||
message: {
|
|
||||||
id: 'Sun, 08 Apr 2018 23:59:00 GMT',
|
id: 'Sun, 08 Apr 2018 23:59:00 GMT',
|
||||||
received_at: 1523231940000,
|
received_at: 1523231940000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
order: 3,
|
|
||||||
label: 'thisMonth',
|
|
||||||
message: {
|
|
||||||
id: 'Sun, 01 Apr 2018 00:01:00 GMT',
|
id: 'Sun, 01 Apr 2018 00:01:00 GMT',
|
||||||
received_at: 1522540860000,
|
received_at: 1522540860000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
yearMonth: [
|
{
|
||||||
{
|
type: 'yearMonth',
|
||||||
order: 201802,
|
year: 2018,
|
||||||
label: 'yearMonth',
|
month: 2,
|
||||||
message: {
|
messages: [
|
||||||
|
{
|
||||||
id: 'Sat, 31 Mar 2018 23:59:00 GMT',
|
id: 'Sat, 31 Mar 2018 23:59:00 GMT',
|
||||||
received_at: 1522540740000,
|
received_at: 1522540740000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
order: 201802,
|
|
||||||
label: 'yearMonth',
|
|
||||||
message: {
|
|
||||||
id: 'Thu, 01 Mar 2018 14:00:00 GMT',
|
id: 'Thu, 01 Mar 2018 14:00:00 GMT',
|
||||||
received_at: 1519912800000,
|
received_at: 1519912800000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
order: 201101,
|
{
|
||||||
label: 'yearMonth',
|
type: 'yearMonth',
|
||||||
message: {
|
year: 2011,
|
||||||
|
month: 1,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
id: 'Mon, 28 Feb 2011 23:59:00 GMT',
|
id: 'Mon, 28 Feb 2011 23:59:00 GMT',
|
||||||
received_at: 1298937540000,
|
received_at: 1298937540000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
|
||||||
order: 201101,
|
|
||||||
label: 'yearMonth',
|
|
||||||
message: {
|
|
||||||
id: 'Tue, 01 Feb 2011 10:00:00 GMT',
|
id: 'Tue, 01 Feb 2011 10:00:00 GMT',
|
||||||
received_at: 1296554400000,
|
received_at: 1296554400000,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
};
|
];
|
||||||
|
|
||||||
const actual = groupMessagesByDate(referenceTime, input);
|
const actual = groupMessagesByDate(referenceTime, input);
|
||||||
assert.deepEqual(actual, expected);
|
assert.deepEqual(actual, expected);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue