@@ -268,18 +269,14 @@ const getProposal = (proposal_id) => {
});
};
-const getRfp = (rfp_id) => {
- return Near.asyncView(contract, "get_rfp", {
- rfp_id,
- });
-};
-
const FeedPage = () => {
+ const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;
+
State.init({
data: [],
author: "",
stage: "",
- sort: "id_desc",
+ sort: "",
category: "",
input: "",
loading: false,
@@ -289,76 +286,120 @@ const FeedPage = () => {
currentlyDisplaying: 0,
});
- const makeMoreItems = () => {
- State.update({ makeMoreLoader: true });
- fetchProposals(state.data.length);
- };
-
- function searchCacheApi() {
- let searchTerm = state.input;
- let searchInput = encodeURI(searchTerm);
- let searchUrl = `${cacheUrl}/proposals/search/${searchInput}`;
-
- return asyncFetch(searchUrl, {
- method: "GET",
- headers: {
- accept: "application/json",
- },
- }).catch((error) => {
- console.log("Error searching cache api", error);
+ const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${proposalFeedIndexerQueryName}_bool_exp = {}) {
+ ${proposalFeedIndexerQueryName}(
+ offset: $offset
+ limit: $limit
+ order_by: {proposal_id: desc}
+ where: $where
+ ) {
+ author_id
+ block_height
+ name
+ category
+ summary
+ editor_id
+ proposal_id
+ ts
+ timeline
+ views
+ labels
+ linked_rfp
+ }
+ ${proposalFeedIndexerQueryName}_aggregate(
+ order_by: {proposal_id: desc}
+ where: $where
+ ) {
+ aggregate {
+ count
+ }
+ }
+ }`;
+
+ const rfpQuery = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${rfpFeedIndexerQueryName}_bool_exp = {}) {
+ ${rfpFeedIndexerQueryName}(
+ offset: $offset
+ limit: $limit
+ order_by: {rfp_id: desc}
+ where: $where
+ ) {
+ name
+ rfp_id
+ }
+ }`;
+
+ function fetchGraphQL(operationsDoc, operationName, variables) {
+ return asyncFetch(QUERYAPI_ENDPOINT, {
+ method: "POST",
+ headers: { "x-hasura-role": indexerHasuraRole },
+ body: JSON.stringify({
+ query: operationsDoc,
+ variables: variables,
+ operationName: operationName,
+ }),
});
}
- function searchProposals() {
- if (state.loading) return;
- State.update({ loading: true });
+ function separateNumberAndText(str) {
+ const numberRegex = /\d+/;
- searchCacheApi().then((result) => {
- let body = result.body;
-
- const promises = body.records.map((proposal) => {
- if (isNumber(proposal.linked_rfp)) {
- getRfp(proposal.linked_rfp).then((rfp) => {
- return { ...proposal, rfpData: rfp };
- });
- } else {
- return Promise.resolve(proposal);
- }
- });
- Promise.all(promises).then((proposalsWithRfpData) => {
- State.update({ aggregatedCount: body.total_records });
- fetchBlockHeights(proposalsWithRfpData, 0);
- });
- });
+ if (numberRegex.test(str)) {
+ const number = str.match(numberRegex)[0];
+ const text = str.replace(numberRegex, "").trim();
+ return { number: parseInt(number), text };
+ } else {
+ return { number: null, text: str.trim() };
+ }
}
- function fetchCacheApi(variables) {
- let fetchUrl = `${cacheUrl}/proposals?order=${variables.order}&limit=${variables.limit}&offset=${variables.offset}`;
-
- if (variables.author_id) {
- fetchUrl += `&filters.author_id=${variables.author_id}`;
- }
- if (variables.stage) {
- fetchUrl += `&filters.stage=${variables.stage}`;
+ const buildWhereClause = () => {
+ let where = {};
+ if (state.author) {
+ where = { author_id: { _eq: state.author }, ...where };
}
- if (variables.category) {
+
+ if (state.category) {
if (isInfra || isEvents) {
- fetchUrl += `&filters.labels=${variables.category}`;
+ where = { labels: { _contains: state.category }, ...where };
} else {
- fetchUrl += `&filters.category=${variables.category}`;
+ where = { category: { _eq: state.category }, ...where };
}
}
- return asyncFetch(fetchUrl, {
- method: "GET",
- headers: {
- accept: "application/json",
- },
- }).catch((error) => {
- console.log("Error fetching cache api", error);
- });
- }
- function fetchProposals(offset) {
+ if (state.stage) {
+ // timeline is stored as jsonb
+ where = {
+ timeline: { _cast: { String: { _regex: `${state.stage}` } } },
+ ...where,
+ };
+ }
+ if (state.input) {
+ const { number, text } = separateNumberAndText(state.input);
+ if (number) {
+ where = { proposal_id: { _eq: number }, ...where };
+ }
+
+ if (text) {
+ where = {
+ _or: [
+ { name: { _iregex: `${text}` } },
+ { summary: { _iregex: `${text}` } },
+ { description: { _iregex: `${text}` } },
+ ],
+ ...where,
+ };
+ }
+ }
+
+ return where;
+ };
+
+ const makeMoreItems = () => {
+ State.update({ makeMoreLoader: true });
+ fetchProposals(state.data.length);
+ };
+
+ const fetchProposals = (offset) => {
if (!offset) {
offset = 0;
}
@@ -366,30 +407,36 @@ const FeedPage = () => {
State.update({ loading: true });
const FETCH_LIMIT = 10;
const variables = {
- order: state.sort,
limit: FETCH_LIMIT,
offset,
- category: state.category ? encodeURIComponent(state.category) : "",
- author_id: state.author ? encodeURIComponent(state.author) : "",
- stage: state.stage ? encodeURIComponent(state.stage) : "",
+ where: buildWhereClause(),
};
- fetchCacheApi(variables).then((result) => {
- const body = result.body;
- const promises = body.records.map((proposal) => {
- if (isNumber(proposal.linked_rfp)) {
- getRfp(proposal.linked_rfp).then((rfp) => {
- return { ...proposal, rfpData: rfp };
+ fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => {
+ if (result.status === 200) {
+ if (result.body.data) {
+ const data = result.body.data[proposalFeedIndexerQueryName];
+ const totalResult =
+ result.body.data[`${proposalFeedIndexerQueryName}_aggregate`];
+ const promises = data.map((item) => {
+ if (isNumber(item.linked_rfp)) {
+ return fetchGraphQL(rfpQuery, "GetLatestSnapshot", {
+ where: { rfp_id: { _eq: item.linked_rfp } },
+ }).then((result) => {
+ const rfpData = result.body.data?.[rfpFeedIndexerQueryName];
+ return { ...item, rfpData: rfpData[0] };
+ });
+ } else {
+ return Promise.resolve(item);
+ }
+ });
+ Promise.all(promises).then((res) => {
+ State.update({ aggregatedCount: totalResult.aggregate.count });
+ fetchBlockHeights(res, offset);
});
- } else {
- return Promise.resolve(proposal);
}
- });
- Promise.all(promises).then((proposalsWithRfpData) => {
- State.update({ aggregatedCount: body.total_records });
- fetchBlockHeights(proposalsWithRfpData, offset);
- });
+ }
});
- }
+ };
useEffect(() => {
State.update({ searchLoader: true });
@@ -401,45 +448,48 @@ const FeedPage = () => {
...new Set([...newItems, ...state.data].map((i) => JSON.stringify(i))),
].map((i) => JSON.parse(i));
// Sorting in the front end
- if (state.sort === "id_desc" || state.sort === "") {
+ if (state.sort === "proposal_id" || state.sort === "") {
items.sort((a, b) => b.proposal_id - a.proposal_id);
+ } else if (state.sort === "views") {
+ items.sort((a, b) => b.views - a.views);
}
return items;
};
const fetchBlockHeights = (data, offset) => {
- data = data.map((item, index) => ({
- ...item,
- timeline: JSON.parse(item.timeline),
- }));
- if (offset) {
- let newData = mergeItems(data);
- State.update({
- data: newData,
- currentlyDisplaying: newData.length,
- loading: false,
- searchLoader: false,
- makeMoreLoader: false,
- });
- } else {
- State.update({
- data,
- currentlyDisplaying: data.length,
- loading: false,
- searchLoader: false,
- makeMoreLoader: false,
- });
- }
+ let promises = data.map((item) => getProposal(item.proposal_id));
+ Promise.all(promises).then((blockHeights) => {
+ data = data.map((item, index) => ({
+ ...item,
+ timeline: JSON.parse(item.timeline),
+ social_db_post_block_height:
+ blockHeights[index].social_db_post_block_height,
+ }));
+ if (offset) {
+ let newData = mergeItems(data);
+ State.update({
+ data: newData,
+ currentlyDisplaying: newData.length,
+ loading: false,
+ searchLoader: false,
+ makeMoreLoader: false,
+ });
+ } else {
+ State.update({
+ data,
+ currentlyDisplaying: data.length,
+ loading: false,
+ searchLoader: false,
+ makeMoreLoader: false,
+ });
+ }
+ });
};
useEffect(() => {
const handler = setTimeout(() => {
- if (state.input) {
- searchProposals();
- } else {
- fetchProposals();
- }
+ fetchProposals();
}, 1000);
return () => {
diff --git a/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx b/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx
index 995a38a0a..53d6eba9e 100644
--- a/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx
+++ b/instances/devhub.near/widget/devhub/entity/proposal/LinkedProposalsDropdown.jsx
@@ -1,15 +1,24 @@
const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url");
href || (href = () => {});
-const instance = props.instance ?? "";
-
-const { cacheUrl, contract } = VM.require(`${instance}/widget/config.data`);
-
const linkedProposals = props.linkedProposals;
const onChange = props.onChange;
const [selectedProposals, setSelectedProposals] = useState(linkedProposals);
const [proposalsOptions, setProposalsOptions] = useState([]);
-const [textAfterHash, setTextAfterHash] = useState("");
+const [searchProposalId, setSearchProposalId] = useState("");
+const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;
+const queryName = "${REPL_PROPOSAL_FEED_INDEXER_QUERY_NAME}";
+const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) {
+${queryName}(
+ offset: $offset
+ limit: $limit
+ order_by: {proposal_id: desc}
+ where: $where
+) {
+ name
+ proposal_id
+}
+}`;
useEffect(() => {
if (JSON.stringify(linkedProposals) !== JSON.stringify(selectedProposals)) {
@@ -23,43 +32,73 @@ useEffect(() => {
}
}, [selectedProposals]);
-function searchCacheApi(input) {
- let searchInput = encodeURI(input);
- let searchUrl = `${cacheUrl}/proposals/search/${searchInput}`;
+function separateNumberAndText(str) {
+ const numberRegex = /\d+/;
- return asyncFetch(searchUrl, {
- method: "GET",
- headers: {
- accept: "application/json",
- },
- }).catch((error) => {
- console.log("Error searching cache api", error);
- });
+ if (numberRegex.test(str)) {
+ const number = str.match(numberRegex)[0];
+ const text = str.replace(numberRegex, "").trim();
+ return { number: parseInt(number), text };
+ } else {
+ return { number: null, text: str.trim() };
+ }
}
-function searchProposals(input) {
- if (state.loading) return;
- State.update({ loading: true });
+const buildWhereClause = () => {
+ let where = {};
+ const { number, text } = separateNumberAndText(searchProposalId);
- searchCacheApi(input).then((result) => {
- let proposalsData = result.body.records;
+ if (number) {
+ where = { proposal_id: { _eq: number }, ...where };
+ }
- const data = [];
- for (const prop of proposalsData) {
- data.push({
- label: "# " + prop.proposal_id + " : " + prop.name,
- value: prop.proposal_id,
- });
- }
- setProposalsOptions(data);
+ if (text) {
+ where = { name: { _ilike: `%${text}%` }, ...where };
+ }
+
+ return where;
+};
+
+function fetchGraphQL(operationsDoc, operationName, variables) {
+ return asyncFetch(QUERYAPI_ENDPOINT, {
+ method: "POST",
+ headers: { "x-hasura-role": "${REPL_INDEXER_HASURA_ROLE}" },
+ body: JSON.stringify({
+ query: operationsDoc,
+ variables: variables,
+ operationName: operationName,
+ }),
});
}
+const fetchProposals = () => {
+ const FETCH_LIMIT = 30;
+ const variables = {
+ limit: FETCH_LIMIT,
+ offset: 0,
+ where: buildWhereClause(),
+ };
+ fetchGraphQL(query, "GetLatestSnapshot", variables).then(async (result) => {
+ if (result.status === 200) {
+ if (result.body.data) {
+ const proposalsData = result.body.data[queryName];
+
+ const data = [];
+ for (const prop of proposalsData) {
+ data.push({
+ label: "# " + prop.proposal_id + " : " + prop.name,
+ value: prop.proposal_id,
+ });
+ }
+ setProposalsOptions(data);
+ }
+ }
+ });
+};
+
useEffect(() => {
- if (textAfterHash.trim()) {
- searchProposals(textAfterHash);
- }
-}, [textAfterHash]);
+ fetchProposals();
+}, [searchProposalId]);
return (
<>
@@ -69,7 +108,7 @@ return (
-
+
);
@@ -98,7 +137,7 @@ return (