Skip to content

Commit

Permalink
multi: Add proposal votes search
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgcramos authored Oct 5, 2022
1 parent bd2307f commit bd0fc49
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 23 deletions.
40 changes: 40 additions & 0 deletions plugins-structure/apps/politeia/cypress/e2e/pageDetails.cy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
mockTicketvoteResults,
mockTicketvoteSubmissions,
mockTicketvoteSummaries,
} from "@politeiagui/ticketvote/dev/mocks";
Expand Down Expand Up @@ -182,6 +183,34 @@ describe("Given an approved proposal details page", () => {
);
cy.findByTestId("status-change-reason").should("contain.text", reason);
});
describe("when searching for proposal votes", () => {
const ticket = "fakeTicketToken";
it("should allow votes search by ticket token", () => {
cy.mockResponse(
"/api/ticketvote/v1/results",
mockTicketvoteResults({ yes: 500, no: 50, result: { ticket } })
).as("results");
cy.visit("/record/fake001");
cy.findByTestId("proposal-search-votes-button").click();
cy.wait("@results");
cy.findByTestId("ticketvote-modal-ticket-search-input")
.type(ticket)
.type("{enter}");
cy.findByTestId("ticketvote-modal-ticket-search-table").should("exist");
});
it("should display loading indicator when loading proposal votes", () => {
cy.mockResponse(
"/api/ticketvote/v1/results",
mockTicketvoteResults({ yes: 500, no: 50, result: { ticket } }),
{ delay: 5000 }
).as("results");
cy.visit("/record/fake001");
cy.findByTestId("proposal-search-votes-button").click();
cy.findByTestId("ticketvote-modal-ticket-search-loading").should(
"be.visible"
);
});
});
});

describe("Given an abandoned proposal", () => {
Expand Down Expand Up @@ -366,4 +395,15 @@ describe("Given requests errors on Details page", () => {
"1658261424"
);
});
it("should display error when ticketvote results request fails", () => {
cy.mockResponse("/api/ticketvote/v1/results", errorMock, {
statusCode: 500,
}).as("results");
cy.visit("/record/fake001");
cy.findByTestId("proposal-search-votes-button").click();
cy.findByTestId("record-form-error-message").should(
"include.text",
"1658261424"
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import styles from "./styles.module.css";
import { ModalProposalDiff } from "./ModalProposalDiff";
import { ProposalsCompact } from "./ProposalsCompact";
import { PROPOSAL_STATUS_APPROVED } from "../../pi";
import { ModalTicketSearch } from "@politeiagui/ticketvote/ui";

const ProposalDetails = ({
record,
Expand Down Expand Up @@ -84,6 +85,12 @@ const ProposalDetails = ({
open(ModalImages, { images, activeIndex: index });
}

function handleOpenSearchVotesModal() {
open(ModalTicketSearch, {
token: proposalDetails.token,
});
}

const isAbandoned = proposalDetails.archived || proposalDetails.censored;

const currentStatusChange =
Expand Down Expand Up @@ -175,6 +182,11 @@ const ProposalDetails = ({
<ButtonIcon type="markdown" viewBox="0 0 208 128" />
</a>
<ButtonIcon type="link" onClick={handleShowRawMarkdown} />
<ButtonIcon
type="search"
onClick={handleOpenSearchVotesModal}
data-testid="proposal-search-votes-button"
/>
</div>
</>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
min-height: var(--font-size-xxlarge);
background-color: var(--dimmed-card-background);
min-width: 10rem;
margin-top: 1rem;
animation: fadeIn linear 1s alternate infinite;
-webkit-animation: fadeIn linear 1s alternate infinite;
-moz-animation: fadeIn linear 1s alternate infinite;
Expand All @@ -12,8 +13,10 @@

.footerButtons {
display: flex;
width: 6rem;
justify-content: space-between;
}

.footerButtons > * {
margin-left: 1rem;
}

.formButtons {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,30 @@ function SaveButton({

function Warning({ children }) {
return (
<Message kind="warning" className={styles.warning}>
<Message kind="warning" className={styles.message}>
{children}
</Message>
);
}

export function RecordForm({ initialValues, children, onSubmit }) {
function ErrorMessage({ error }) {
return (
error && (
<Message
kind="error"
data-testid="record-form-error-message"
className={styles.message}
>
{error.toString()}
</Message>
)
);
}

export function RecordForm({ initialValues, children, onSubmit, className }) {
const formProps = useForm({ defaultValues: initialValues });
return (
<Card className={styles.card}>
<Card className={classNames(styles.card, className)}>
<FormProvider {...formProps}>
<form
onSubmit={formProps.handleSubmit(onSubmit)}
Expand All @@ -152,6 +166,7 @@ export function RecordForm({ initialValues, children, onSubmit }) {
formProps,
CurrencyInput,
DatePickerInput,
ErrorMessage,
MarkdownInput,
SaveButton,
SelectInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
-moz-appearance: textfield;
}

.warning {
.message {
margin-top: 1rem;
margin-bottom: 3.3rem;
}
Expand Down
8 changes: 6 additions & 2 deletions plugins-structure/packages/core/src/dev/cypress/commands.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
Cypress.Commands.add(
"mockResponse",
(routeMatcher, mockFn, { headers = {}, statusCode = 200 } = {}) => {
(
routeMatcher,
mockFn,
{ headers = {}, statusCode = 200, ...replyParams } = {}
) => {
if (!routeMatcher) return;
return cy.intercept(routeMatcher, (req) => {
const params = req.body || {};
const body = mockFn(params);
req.reply({ body, headers, statusCode });
req.reply({ body, headers, statusCode, ...replyParams });
});
}
);
25 changes: 25 additions & 0 deletions plugins-structure/packages/ticketvote/src/dev/mocks/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getTokensArray } from "@politeiagui/core/dev/mocks";
import { getHumanReadableTicketvoteStatus } from "../../lib/utils";
import { faker } from "@faker-js/faker";

const bestblock = 420;

Expand Down Expand Up @@ -77,3 +78,27 @@ export function mockTicketvoteSummaries({
return { summaries };
};
}

export function mockTicketvoteResults({ yes = 10, no = 10, result = {} } = {}) {
return ({ token }) => {
const voteData = {
token,
ticket: faker.random.numeric(64),
address: faker.random.numeric(35),
signature: "",
receipt: "",
timestamp: Date.now() / 1000,
};
const yesVotes = Array(yes).fill({
...voteData,
...result,
votebit: "2",
});
const noVotes = Array(no).fill({
...voteData,
...result,
votebit: "1",
});
return { votes: [...yesVotes, ...noVotes] };
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const ticketvoteResultsSlice = createSlice({
})
.addCase(fetchTicketvoteResults.fulfilled, (state, action) => {
const { token } = action.meta.arg;
state.byToken[token] = action.payload;
state.byToken[token] = action.payload.votes;
state.status = "succeeded";
})
.addCase(fetchTicketvoteResults.rejected, (state, action) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import PropTypes from "prop-types";
import { Modal } from "pi-ui";
import { TicketSearch } from "./TicketSearch";

export function ModalTicketSearch({
placeholder,
onClose,
title,
show,
token,
}) {
return (
<Modal show={show} onClose={onClose} title={title}>
<TicketSearch placeholder={placeholder} token={token} />
</Modal>
);
}

ModalTicketSearch.propTypes = {
token: PropTypes.string.isRequired,
placeholder: PropTypes.string,
onClose: PropTypes.func,
title: PropTypes.node,
show: PropTypes.bool,
};

ModalTicketSearch.defaultProps = {
title: "Search Ticket Vote",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { Table } from "pi-ui";
import { RecordForm } from "@politeiagui/common-ui";
import styles from "./styles.module.css";
import { ticketvoteResults } from "../../ticketvote/results";

export function TicketSearch({ placeholder, token }) {
const dispatch = useDispatch();

const [resultsFound, setResultsFound] = useState();

const results = useSelector((state) =>
ticketvoteResults.selectByToken(state, token)
);

const resultsStatus = useSelector(ticketvoteResults.selectStatus);
const error = useSelector(ticketvoteResults.selectError);

function handleSearchTicket({ ticketToken }) {
const tickets = results
.filter((r) => r.ticket === ticketToken)
.map((ticket) => ({
Ticket: ticket.ticket,
Option: ticket.votebit === "2" ? "Yes" : "No",
}));
setResultsFound(tickets);
}

useEffect(() => {
dispatch(ticketvoteResults.fetch({ token }));
}, [dispatch, token]);

const isLoading = resultsStatus === "loading";

return (
<div>
{isLoading && (
<div
className={styles.loading}
data-testid="ticketvote-modal-ticket-search-loading"
/>
)}
<RecordForm onSubmit={handleSearchTicket} className={styles.searchForm}>
{({ TextInput, ErrorMessage }) => (
<>
{error && <ErrorMessage error={error} />}
<TextInput
name="ticketToken"
placeholder={isLoading ? "Loading..." : placeholder}
disabled={isLoading || error}
data-testid="ticketvote-modal-ticket-search-input"
/>
</>
)}
</RecordForm>
{resultsFound && (
<div data-testid="ticketvote-modal-ticket-search-table">
<Table headers={["Ticket", "Option"]} data={resultsFound} />
</div>
)}
</div>
);
}

TicketSearch.propTypes = {
token: PropTypes.string.isRequired,
placeholder: PropTypes.string,
};

TicketSearch.defaultProps = {
placeholder: "Search by ticket token",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ModalTicketSearch";
export * from "./TicketSearch";
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.searchForm {
padding: 0 !important;
}

.loading {
width: 50%;
height: 0.3rem;
background-color: var(--color-primary);
top: 0;
left: 0;
position: fixed;
animation: slide 2s linear infinite;
overflow: hidden;
}

@keyframes slide {
0% {
left: 0;
width: 0%;
}
25% {
left: 0%;
width: 33%;
}
75% {
left: 67%;
width: 33%;
}
100% {
left: 100%;
width: 0%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export const TicketvoteRecordVoteStatusBar = ({ ticketvoteSummary }) => {
isVoteActive={true}
quorumVotes={quorum}
votesReceived={votesReceived}
onSearchVotes={() => {
console.log("SEARCHING");
}}
/>
}
/>
Expand Down
Loading

0 comments on commit bd0fc49

Please sign in to comment.