Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: normalize redux state with short tokens #2365

Merged
merged 11 commits into from
May 26, 2021
6 changes: 4 additions & 2 deletions src/components/Proposal/Proposal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import ThumbnailGrid from "src/components/Files";
import VersionPicker from "src/components/VersionPicker";
import useModalContext from "src/hooks/utils/useModalContext";
import { useRouter } from "src/components/Router";
import { isEmpty, getKeyByValue } from "src/helpers";
import { shortRecordToken, isEmpty, getKeyByValue } from "src/helpers";

/**
* replaceImgDigestWithPayload uses a regex to parse images
Expand Down Expand Up @@ -232,7 +232,9 @@ const Proposal = React.memo(function Proposal({
* */
edit={
isEditable ? (
<Edit url={`/record/${proposalToken}/edit`} />
<Edit
url={`/record/${shortRecordToken(proposalToken)}/edit`}
/>
) : showEditIcon ? (
<Tooltip
placement={mobile ? "left" : "right"}
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProposalForm/ProposalForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import DraftSaver from "./DraftSaver";
import { useProposalForm } from "./hooks";
import usePolicy from "src/hooks/api/usePolicy";
import {
shortRecordToken,
replaceBlobsByDigestsAndGetFiles,
replaceImgDigestByBlob
} from "src/helpers";
Expand Down Expand Up @@ -340,7 +341,7 @@ const ProposalFormWrapper = ({
PROPOSAL_STATE_VETTED
)) || [[], null];
const [proposals, summaries] = rfpWithVoteSummaries;
const proposal = proposals[rfpLink];
const proposal = proposals[shortRecordToken(rfpLink)];
const voteSummary = summaries && summaries[rfpLink];
const isInvalidToken = !proposal || !voteSummary;
if (isInvalidToken) {
Expand All @@ -367,7 +368,7 @@ const ProposalFormWrapper = ({
setSubmitting(false);
setSubmitSuccess(true);
// Navigate to record page.
history.push(`/record/${proposalToken.substring(0, 7)}`);
history.push(`/record/${shortRecordToken(proposalToken)}`);
resetForm();
} catch (e) {
setSubmitting(false);
Expand Down
3 changes: 2 additions & 1 deletion src/components/ProposalsList/ProposalsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import { Text, classNames, useTheme, getThemeProperty } from "pi-ui";
import styles from "./ProposalsList.module.css";
import { shortRecordToken } from "src/helpers";
import ProposalItem from "./ProposalItem";
import LoadingPlaceholders from "src/components/LoadingPlaceholders";
import ContentLoader from "react-content-loader";
Expand Down Expand Up @@ -41,7 +42,7 @@ const ProposalsList = ({ data: { proposals, voteSummaries } }) => {
proposal={proposal}
voteSummary={
voteSummaries[
proposals[index]?.censorshiprecord.token.substring(0, 7)
shortRecordToken(proposals[index]?.censorshiprecord.token)
]
}
/>
Expand Down
7 changes: 5 additions & 2 deletions src/components/RecordsView/RecordsView.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, useMemo, useEffect } from "react";
import { Tabs, Tab } from "pi-ui";
import LazyList from "src/components/LazyList";
import orderBy from "lodash/fp/orderBy";
import { getRecordsByTabOption } from "./helpers";
import HelpMessage from "src/components/HelpMessage";
import { useConfig } from "src/containers/Config";
import { shortRecordToken } from "src/helpers";
import { NOJS_ROUTE_PREFIX, PROPOSAL_STATUS_CENSORED } from "src/constants";

const LoadingPlaceholders = ({ numberOfItems, placeholder }) => {
Expand All @@ -16,7 +18,8 @@ const LoadingPlaceholders = ({ numberOfItems, placeholder }) => {
};

const getFilteredRecordsAndToken = (records, tokens, tab, filterCensored) => {
const filteredTokens = tokens[tab];
const filteredTokens = tokens[tab].map((token) => shortRecordToken(token));
const sortByNewestFirst = orderBy(["timestamp"], ["desc"]);
let filteredRecords =
(records &&
filteredTokens &&
Expand All @@ -27,7 +30,7 @@ const getFilteredRecordsAndToken = (records, tokens, tab, filterCensored) => {
({ status }) => status !== PROPOSAL_STATUS_CENSORED
);
}
return [filteredRecords, filteredTokens];
return [sortByNewestFirst(filteredRecords), filteredTokens];
};

const getDefaultEmptyMessage = () => "No records available";
Expand Down
3 changes: 2 additions & 1 deletion src/containers/Invoice/Detail/InvoiceDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState, memo } from "react";
import { Spinner, Text, H4, Table, Link as UiLink, classNames } from "pi-ui";
import Link from "src/components/Link";
import { shortRecordToken } from "src/helpers";
import { useInvoices } from "./hooks";
import { formatCentsToUSD } from "src/utils";
import useAdminInvoices from "src/hooks/api/useAdminInvoices";
Expand All @@ -16,7 +17,7 @@ const printInvoiceInfo = ({
Date: `${month}/${year}`,
Invoice: (
<Link to={`/invoices/${token}`} className={styles.invoiceLinkWrapper}>
<Text color="primary">{token.substring(0, 7)}</Text>
<Text color="primary">{shortRecordToken(token)}</Text>
</Link>
),
"Total (USD)": formatCentsToUSD(total)
Expand Down
13 changes: 7 additions & 6 deletions src/containers/Proposal/Detail/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getTokensForProposalsPagination
} from "../helpers";
import { getDetailsFile } from "./helpers";
import { shortRecordToken } from "src/helpers";
import { PROPOSAL_STATE_VETTED } from "src/constants";
import useFetchMachine from "src/hooks/utils/useFetchMachine";
import isEmpty from "lodash/fp/isEmpty";
Expand All @@ -23,11 +24,11 @@ const getUnfetchedVoteSummaries = (proposal, voteSummaries) => {
const rfpLinks = getProposalRfpLinksTokens(proposal);
const proposalToken = getProposalToken(proposal);
const tokens = concat(rfpLinks || [])(proposalToken);
// compare tokens by substring
// compare tokens by short form
return tokens.filter(
(t) =>
!keys(voteSummaries).some(
(vs) => vs.substring(0, 7) === t.substring(0, 7)
(vs) => shortRecordToken(vs) === shortRecordToken(t)
)
);
};
Expand All @@ -42,7 +43,7 @@ const getProposalRfpLinksTokens = (proposal) => {
};

export function useProposal(token, threadParentID) {
const tokenShort = token.substring(0, 7);
const tokenShort = shortRecordToken(token);
const onFetchProposalDetails = useAction(act.onFetchProposalDetails);
const onFetchProposalsBatch = useAction(act.onFetchProposalsBatch);
const onFetchProposalsVoteSummary = useAction(
Expand All @@ -51,8 +52,8 @@ export function useProposal(token, threadParentID) {
const onFetchVotesDetails = useAction(act.onFetchVotesDetails);
const onFetchProposalVoteResults = useAction(act.onFetchProposalVoteResults);
const proposalSelector = useMemo(
() => sel.makeGetProposalByToken(token),
[token]
() => sel.makeGetProposalByToken(tokenShort),
[tokenShort]
);
const proposal = useSelector(proposalSelector);
const proposals = useSelector(sel.proposalsByToken);
Expand All @@ -71,7 +72,7 @@ export function useProposal(token, threadParentID) {
proposals: values(pick(proposals, rfpLinks)),
voteSummaries: pick(
voteSummaries,
rfpLinks.map((l) => l.substring(0, 7))
rfpLinks.map((l) => shortRecordToken(l))
)
};

Expand Down
7 changes: 5 additions & 2 deletions src/containers/Proposal/Edit/Edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ const EditProposal = ({ match }) => {
const { userid } = currentUser || {};
const { isPaid } = usePaywall();
const [, identityError] = useIdentity();
const hasDetails =
proposal?.files.filter((f) => f.name === "index.md").length > 0;

const initialValues = proposal
? {
token: tokenFromUrl,
token: proposal.censorshiprecord.token,
name: proposal.name,
type:
proposal && proposal.linkby
Expand Down Expand Up @@ -61,7 +64,7 @@ const EditProposal = ({ match }) => {
)}
{!!identityError && <IdentityMessageError />}
</Or>
{!loading && !!proposal ? (
{!loading && !!proposal && hasDetails ? (
<ProposalForm
initialValues={initialValues}
onSubmit={onEditProposal}
Expand Down
6 changes: 3 additions & 3 deletions src/containers/Proposal/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
INELIGIBLE,
PROPOSAL_PAGE_SIZE
} from "../../constants";
import { getTextFromIndexMd } from "src/helpers";
import { getTextFromIndexMd, shortRecordToken } from "src/helpers";
import set from "lodash/fp/set";
import values from "lodash/fp/values";
import pick from "lodash/pick";
Expand Down Expand Up @@ -282,7 +282,7 @@ export const getProposalToken = (proposal) =>
*/
export const getProposalUrl = (token, isJsEnabled) =>
isJsEnabled
? `/record/${token.substring(0, 7)}`
? `/record/${shortRecordToken(token)}`
: `${NOJS_ROUTE_PREFIX}/record/${token}`;

/**
Expand All @@ -293,7 +293,7 @@ export const getProposalUrl = (token, isJsEnabled) =>
*/
export const getCommentsUrl = (token, isJsEnabled) =>
isJsEnabled
? `/record/${token.substring(0, 7)}?scrollToComments=true`
? `/record/${shortRecordToken(token)}?scrollToComments=true`
: `${NOJS_ROUTE_PREFIX}/record/${token}?scrollToComments=true`;

/**
Expand Down
8 changes: 4 additions & 4 deletions src/containers/Proposal/hooks/useProposalURLs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getProposalUrl, getCommentsUrl, getAuthorUrl } from "../helpers";
import { ARCHIVE_URL } from "src/constants";
import * as sel from "src/selectors";
import { useSelector } from "src/redux";
import { shortRecordToken } from "src/helpers";
import { PROPOSAL_STATE_VETTED } from "src/constants";

export default function useProposalURLs(
Expand All @@ -18,14 +19,13 @@ export default function useProposalURLs(
const isLegacy = legacyProposals.includes(proposalToken);
const proposalURL = !isLegacy
? getProposalUrl(proposalToken, javascriptEnabled, isLegacy)
: `${ARCHIVE_URL}proposals/${proposalToken.substring(0, 7)}`;
: `${ARCHIVE_URL}proposals/${shortRecordToken(proposalToken)}`;
const commentsURL = useMemo(
() =>
!isLegacy
? getCommentsUrl(proposalToken, javascriptEnabled)
: `${ARCHIVE_URL}proposals/${proposalToken.substring(
0,
7
: `${ARCHIVE_URL}proposals/${shortRecordToken(
proposalToken
)}?scrollToComments=true`,
[isLegacy, javascriptEnabled, proposalToken]
);
Expand Down
6 changes: 5 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ export const digest = (payload) => sha3_256(payload);
export const utoa = (str) => window.btoa(unescape(encodeURIComponent(str)));
export const atou = (str) => decodeURIComponent(escape(window.atob(str)));

// shortToken receives a full lengthed record token and parses to its short
// form
export const shortRecordToken = (token) => token.substring(0, 7);

// parseReceivedProposalsMap iterates over BE returned proposals map[token] => proposal, parses the
// metadata file & the proposal statuses
export const parseReceivedProposalsMap = (proposals) => {
const parsedProps = {};
for (const [token, prop] of Object.entries(proposals)) {
parsedProps[token] = parseRawProposal(prop);
parsedProps[shortRecordToken(token)] = parseRawProposal(prop);
}
return parsedProps;
};
Expand Down
16 changes: 13 additions & 3 deletions src/hooks/api/useLegacyVettedProposals.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PROPOSAL_VOTING_REJECTED,
PROPOSAL_VOTING_INELIGIBLE
} from "src/constants";
import { shortRecordToken } from "src/helpers";
import legacyProposalsInfo from "src/legacyproposals.json";
import tokenInventory from "src/legacytokeninventory.json";

Expand Down Expand Up @@ -32,16 +33,25 @@ export default function useLegacyVettedProposals(shouldReturn = false, status) {
useEffect(() => {
// shouldReturn is a boolean to control when the proposals are done fetching so we can return the legacy props.
if (shouldReturn) {
const proposalsTokensList = tokenInventory[mapStatusToString[status]];
const proposalsTokensList =
tokenInventory[mapStatusToString[status]] &&
tokenInventory[mapStatusToString[status]].map((token) =>
shortRecordToken(token)
);
// filter propsals by tab and transform from Array to Object where the key is the proposal token and the value is the proposal info
const finalList = newLegacyProposalsInfo
.filter(
(p) =>
proposalsTokensList &&
proposalsTokensList.includes(p.censorshiprecord.token)
proposalsTokensList.includes(
shortRecordToken(p.censorshiprecord.token)
)
)
.reduce(
(acc, cur) => ({ ...acc, [cur.censorshiprecord.token]: cur }),
(acc, cur) => ({
...acc,
[shortRecordToken(cur.censorshiprecord.token)]: cur
}),
{}
);
setLegacyProposals(finalList);
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/api/useProposalsBatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import isUndefined from "lodash/fp/isUndefined";
import keys from "lodash/fp/keys";
import difference from "lodash/fp/difference";
import { INVENTORY_PAGE_SIZE, PROPOSAL_PAGE_SIZE } from "src/constants";
import { shortRecordToken } from "src/helpers";
import {
getRfpLinkedProposals,
getProposalStatusLabel,
Expand All @@ -45,7 +46,7 @@ const getRfpSubmissions = (proposals) =>
)(proposals);

const getUnfetchedTokens = (proposals, tokens) =>
difference(tokens)(keys(proposals));
difference(tokens.map((token) => shortRecordToken(token)))(keys(proposals));

const getCurrentPage = (tokens) => {
return tokens ? Math.floor(+tokens.length / INVENTORY_PAGE_SIZE) : 0;
Expand Down
Loading