Merge branch 'polls-normalization' into 'develop'

Improve Poll normalization, store as Immutable.Record

Closes #857

See merge request soapbox-pub/soapbox-fe!1089
next-old
Alex Gleason 2022-03-10 21:49:52 +00:00
commit 84a364a7d2
5 zmienionych plików z 281 dodań i 26 usunięć

Wyświetl plik

@ -0,0 +1,201 @@
{
"account": {
"acct": "alex",
"avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
"bot": false,
"created_at": "2020-01-08T01:25:43.000Z",
"display_name": "Alex Gleason",
"emojis": [],
"fields": [
{
"name": "Website",
"value": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
},
{
"name": "Soapbox",
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"followers_count": 2465,
"following_count": 1581,
"fqn": "alex@gleasonator.com",
"header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
"id": "9v5bmRalQvjOy0ECcC",
"last_status_at": "2022-03-10T18:19:50",
"locked": false,
"note": "I create Fediverse software that empowers people online.<br/><br/>I&#39;m vegan btw<br/><br/>Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"accepts_chat_messages": true,
"also_known_as": [
"https://mitra.social/users/alex"
],
"ap_id": "https://gleasonator.com/users/alex",
"background_image": null,
"birthday": "1993-07-03",
"favicon": "https://gleasonator.com/favicon.png",
"hide_favorites": true,
"hide_followers": false,
"hide_followers_count": false,
"hide_follows": false,
"hide_follows_count": false,
"is_admin": true,
"is_confirmed": true,
"is_moderator": false,
"is_suggested": true,
"relationship": {},
"skip_thread_containment": false,
"tags": []
},
"source": {
"fields": [
{
"name": "Website",
"value": "https://alexgleason.me"
},
{
"name": "Soapbox",
"value": "https://soapbox.pub"
},
{
"name": "Email",
"value": "alex@alexgleason.me"
},
{
"name": "Gender identity",
"value": "Soyboy"
},
{
"name": "Donate (PayPal)",
"value": "https://paypal.me/gleasonator"
},
{
"name": "$BTC",
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
},
{
"name": "$ETH",
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
},
{
"name": "$DOGE",
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
},
{
"name": "$XMR",
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
}
],
"note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
"pleroma": {
"actor_type": "Person",
"discoverable": false
},
"sensitive": false
},
"statuses_count": 23648,
"url": "https://gleasonator.com/users/alex",
"username": "alex"
},
"application": null,
"bookmarked": false,
"card": null,
"content": "<p>What is tolerance?</p>",
"created_at": "2020-03-23T19:33:06.000Z",
"emojis": [],
"favourited": false,
"favourites_count": 49,
"id": "103874034847713213",
"in_reply_to_account_id": null,
"in_reply_to_id": null,
"language": null,
"media_attachments": [],
"mentions": [],
"muted": false,
"pinned": true,
"pleroma": {
"content": {
"text/plain": "What is tolerance?"
},
"conversation_id": "3023268",
"direct_conversation_id": null,
"emoji_reactions": [
{
"count": 3,
"me": false,
"name": "❤️"
}
],
"expires_at": null,
"in_reply_to_account_acct": null,
"local": true,
"parent_visible": false,
"pinned_at": "2021-11-23T01:38:44.000Z",
"quote": null,
"quote_url": null,
"quote_visible": false,
"spoiler_text": {
"text/plain": ""
},
"thread_muted": false
},
"poll": {
"emojis": [],
"expired": true,
"expires_at": "2020-03-24T19:33:06.000Z",
"id": "4930",
"multiple": false,
"options": [
{
"title": "Banning, censoring, and deplatforming anyone you disagree with",
"votes_count": 2
},
{
"title": "Promoting free speech, even for people and ideas you dislike",
"votes_count": 36
}
],
"voters_count": 2,
"votes_count": 38
},
"reblog": null,
"reblogged": false,
"reblogs_count": 27,
"replies_count": 15,
"sensitive": false,
"spoiler_text": "",
"tags": [],
"text": null,
"uri": "https://gleasonator.com/users/alex/statuses/103874034847713213",
"url": "https://gleasonator.com/notice/103874034847713213",
"visibility": "public"
}

Wyświetl plik

@ -1,4 +1,4 @@
import { fromJS } from 'immutable';
import { Record as ImmutableRecord, fromJS } from 'immutable';
import { normalizeStatus } from '../status';
@ -7,12 +7,13 @@ describe('normalizeStatus', () => {
const status = fromJS({});
const result = normalizeStatus(status);
expect(result.get('emojis')).toEqual(fromJS([]));
expect(result.get('favourites_count')).toBe(0);
expect(result.get('mentions')).toEqual(fromJS([]));
expect(result.get('reblog')).toBe(null);
expect(result.get('uri')).toBe('');
expect(result.get('visibility')).toBe('public');
expect(ImmutableRecord.isRecord(result)).toBe(true);
expect(result.emojis).toEqual(fromJS([]));
expect(result.favourites_count).toBe(0);
expect(result.mentions).toEqual(fromJS([]));
expect(result.reblog).toBe(null);
expect(result.uri).toBe('');
expect(result.visibility).toBe('public');
});
it('fixes the order of mentions', () => {
@ -88,22 +89,22 @@ describe('normalizeStatus', () => {
const result = normalizeStatus(status);
expect(result.get('media_attachments')).toEqual(expected);
expect(result.media_attachments).toEqual(expected);
});
it('leaves Pleroma attachments alone', () => {
const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-attachments.json'));
const result = normalizeStatus(status);
expect(status.get('media_attachments')).toEqual(result.get('media_attachments'));
expect(status.get('media_attachments')).toEqual(result.media_attachments);
});
it('normalizes Pleroma quote post', () => {
const status = fromJS(require('soapbox/__fixtures__/pleroma-quote-post.json'));
const result = normalizeStatus(status);
expect(result.get('quote')).toEqual(status.getIn(['pleroma', 'quote']));
expect(result.getIn(['pleroma', 'quote'])).toBe(undefined);
expect(result.quote).toEqual(status.getIn(['pleroma', 'quote']));
expect(result.pleroma.get('quote')).toBe(undefined);
});
it('normalizes GoToSocial status', () => {
@ -119,7 +120,7 @@ describe('normalizeStatus', () => {
quote: null,
};
expect(result.toJS()).toMatchObject(missing);
expect(result).toMatchObject(missing);
});
it('normalizes Friendica status', () => {
@ -132,7 +133,7 @@ describe('normalizeStatus', () => {
quote: null,
};
expect(result.toJS()).toMatchObject(missing);
expect(result).toMatchObject(missing);
});
it('normalizes poll and poll options', () => {
@ -146,9 +147,22 @@ describe('normalizeStatus', () => {
multiple: false,
voters_count: 0,
votes_count: 0,
own_votes: [],
voted: false,
};
expect(result.get('poll').toJS()).toMatchObject(expected);
expect(result.getIn(['poll', 'expires_at']) instanceof Date).toBe(true);
expect(ImmutableRecord.isRecord(result.poll)).toBe(true);
expect(ImmutableRecord.isRecord(result.poll.options.get(0))).toBe(true);
expect(result.poll.toJS()).toMatchObject(expected);
expect(result.poll.expires_at instanceof Date).toBe(true);
});
it('normalizes a Pleroma logged-out poll', () => {
const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll.json'));
const result = normalizeStatus(status);
// Adds logged-in fields
expect(result.poll.voted).toBe(false);
expect(result.poll.own_votes).toEqual(fromJS([]));
});
});

Wyświetl plik

@ -47,16 +47,22 @@ const StatusRecord = ImmutableRecord({
spoilerHtml: '',
});
const basePollOption = ImmutableMap({ title: '', votes_count: 0 });
const PollOptionRecord = ImmutableRecord({
title: '',
votes_count: 0,
});
const basePoll = ImmutableMap({
// https://docs.joinmastodon.org/entities/poll/
const PollRecord = ImmutableRecord({
emojis: ImmutableList(),
expired: false,
expires_at: new Date(Date.now() + 1000 * (60 * 5)), // 5 minutes
expires_at: new Date(),
multiple: false,
options: ImmutableList(),
voters_count: 0,
votes_count: 0,
own_votes: ImmutableList(),
voted: false,
});
// Ensure attachments have required fields
@ -100,23 +106,19 @@ const normalizeMentions = (status: ImmutableMap<string, any>) => {
});
};
// Normalize poll option
const normalizePollOption = (option: ImmutableMap<string, any>) => {
return option.mergeWith(mergeDefined, basePollOption);
};
// Normalize poll
const normalizePoll = (status: ImmutableMap<string, any>) => {
if (status.hasIn(['poll', 'options'])) {
return status.update('poll', ImmutableMap(), poll => {
return poll.mergeWith(mergeDefined, basePoll).update('options', (options: ImmutableList<ImmutableMap<string, any>>) => {
return options.map(normalizePollOption);
return PollRecord(poll).update('options', (options: ImmutableList<ImmutableMap<string, any>>) => {
return options.map(PollOptionRecord);
});
});
} else {
return status.set('poll', null);
}
};
// Fix order of mentions
const fixMentionsOrder = (status: ImmutableMap<string, any>) => {
const mentions = status.get('mentions', ImmutableList());

Wyświetl plik

@ -1,9 +1,35 @@
import { Map as ImmutableMap } from 'immutable';
import { POLLS_IMPORT } from 'soapbox/actions/importer';
import reducer from '../polls';
describe('polls reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual(ImmutableMap());
});
describe('POLLS_IMPORT', () => {
it('normalizes the poll', () => {
const polls = [{ id: '3', options: [{ title: 'Apples' }] }];
const action = { type: POLLS_IMPORT, polls };
const result = reducer(undefined, action);
const expected = {
'3': {
options: [{ title: 'Apples', votes_count: 0 }],
emojis: [],
expired: false,
multiple: false,
voters_count: 0,
votes_count: 0,
own_votes: [],
voted: false,
},
};
expect(result.toJS()).toMatchObject(expected);
});
});
});

Wyświetl plik

@ -1,8 +1,20 @@
import { Map as ImmutableMap, fromJS } from 'immutable';
import { POLLS_IMPORT } from 'soapbox/actions/importer';
import { normalizeStatus } from 'soapbox/normalizers/status';
const importPolls = (state, polls) => state.withMutations(map => polls.forEach(poll => map.set(poll.id, fromJS(poll))));
// HOTFIX: Convert the poll into a fake status to normalize it...
// TODO: get rid of POLLS_IMPORT and use STATUS_IMPORT here.
const normalizePoll = poll => {
const status = fromJS({ poll });
return normalizeStatus(status).poll;
};
const importPolls = (state, polls) => {
return state.withMutations(map => {
return polls.forEach(poll => map.set(poll.id, normalizePoll(poll)));
});
};
const initialState = ImmutableMap();