Add pagination (#1073)

* Got tuple comparison for timeline pagination query working

* Include limit parameter in get timeline rpc

* Got view more squeaks frontend button working

* Got basic pagination working

* Include squeak time in display rpc struct

* Got pagination working

* Fix sql query to break ties manually in comparison

* Update frontend build
This commit is contained in:
Jonathan Zernik 2021-08-27 05:31:37 -07:00 committed by GitHub
parent 623aada9a0
commit 0a8d30d5fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 150 additions and 25 deletions

View file

@ -55,6 +55,7 @@ import {
goToSqueakAddressPage,
} from "../../navigation/navigation"
const SQUEAKS_PER_PAGE = 10;
export default function TimelinePage() {
var classes = useStyles();
@ -66,7 +67,7 @@ export default function TimelinePage() {
const history = useHistory();
const getSqueaks = () => {
getTimelineSqueakDisplaysRequest(setSqueaks);
getTimelineSqueakDisplaysRequest(SQUEAKS_PER_PAGE, null, null, null, setSqueaks);
};
const getNetwork = () => {
getNetworkRequest(setNetwork);
@ -114,6 +115,35 @@ export default function TimelinePage() {
network={network}
setSqueaksFn={setSqueaks}
></SqueakList>
{(squeaks.length > 0) && ViewMoreSqueaksButton()}
</>
)
}
function ViewMoreSqueaksButton() {
return (
<>
<Grid item xs={12}>
<div className={classes.root}>
<Button
variant="contained"
onClick={() => {
const latestSqueak = squeaks.slice(-1).pop();
const latestSqueakHeight = (latestSqueak ? latestSqueak.getBlockHeight() : null);
const latestSqueakTime = (latestSqueak ? latestSqueak.getSqueakTime() : null);
const latestSqueakHash = (latestSqueak ? latestSqueak.getSqueakHash() : null);
console.log(latestSqueakHeight);
console.log(latestSqueakTime);
console.log(latestSqueakHash);
getTimelineSqueakDisplaysRequest(SQUEAKS_PER_PAGE, latestSqueakHeight, latestSqueakTime, latestSqueakHash, (resp) => {
// TODO: nothing maybe
console.log(resp);
setSqueaks(squeaks.concat(resp));
});
}}>View more squeaks
</Button>
</div>
</Grid>
</>
)
}

View file

@ -155,8 +155,12 @@ export function getUserRequest(handleResponse) {
});
}
export function getTimelineSqueakDisplaysRequest(handleResponse) {
export function getTimelineSqueakDisplaysRequest(limit, blockHeight, squeakTime, squeakHash, handleResponse) {
var request = new GetTimelineSqueakDisplaysRequest()
request.setLimit(limit);
request.setBlockHeight(blockHeight);
request.setSqueakTime(squeakTime);
request.setSqueakHash(squeakHash);
client.getTimelineSqueakDisplays(request, {}, (err, response) => {
handleResponse(response.getSqueakDisplayEntriesList());
});

View file

@ -372,7 +372,9 @@ def test_get_following_squeaks(
# Get all squeak displays for the known address
get_timeline_squeak_display_response = admin_stub.GetTimelineSqueakDisplays(
squeak_admin_pb2.GetTimelineSqueakDisplaysRequest()
squeak_admin_pb2.GetTimelineSqueakDisplaysRequest(
limit=100,
)
)
assert len(get_timeline_squeak_display_response.squeak_display_entries) >= 1
for (

View file

@ -485,20 +485,34 @@ message SqueakDisplayEntry {
/// Block time
int64 block_time = 8;
/// Squeak time
int64 squeak_time = 9;
/// The author name
string author_address = 9;
string author_address = 10;
/// Is author address known
bool is_author_known = 10;
bool is_author_known = 11;
/// The author name
SqueakProfile author = 11;
SqueakProfile author = 12;
/// Liked time
int64 liked_time_s = 12;
int64 liked_time_s = 13;
}
message GetTimelineSqueakDisplaysRequest {
/// Limit number of results
int32 limit = 1;
/// Block height
int32 block_height = 2;
/// Squeak time
int64 squeak_time = 3;
/// Hash of the squeak.
string squeak_hash = 4;
}
message GetTimelineSqueakDisplaysReply {

View file

@ -39,6 +39,7 @@ def squeak_entry_to_message(squeak_entry: SqueakEntry) -> squeak_admin_pb2.Squea
block_height=squeak_entry.block_height,
block_hash=squeak_entry.block_hash.hex(),
block_time=squeak_entry.block_time,
squeak_time=squeak_entry.squeak_time,
is_reply=is_reply,
reply_to=reply_to, # type: ignore
author_address=squeak_entry.address,

View file

@ -283,9 +283,30 @@ class SqueakAdminServerHandler(object):
)
def handle_get_timeline_squeak_display_entries(self, request):
logger.info("Handle get timeline squeak display entries.")
limit = request.limit
block_height = request.block_height
squeak_time = request.squeak_time
squeak_hash_str = request.squeak_hash
squeak_hash = bytes.fromhex(
squeak_hash_str) if squeak_hash_str else None
logger.info("""Handle get timeline squeak display entries with
limit: {}
block_height: {}
squeak_time: {}
squeak_hash: {}
""".format(
limit,
block_height,
squeak_time,
squeak_hash,
))
squeak_entries = (
self.squeak_controller.get_timeline_squeak_entries()
self.squeak_controller.get_timeline_squeak_entries(
limit,
block_height,
squeak_time,
squeak_hash,
)
)
logger.info(
"Got number of timeline squeak entries: {}".format(
@ -649,7 +670,7 @@ class SqueakAdminServerHandler(object):
squeak_display_msgs = [
squeak_entry_to_message(entry) for entry in squeak_entries
]
return squeak_admin_pb2.GetTimelineSqueakDisplaysReply(
return squeak_admin_pb2.GetLikedSqueakDisplaysReply(
squeak_display_entries=squeak_display_msgs
)

View file

@ -1,14 +1,14 @@
{
"files": {
"main.js": "/static/js/main.bfb223c5.chunk.js",
"main.js.map": "/static/js/main.bfb223c5.chunk.js.map",
"main.js": "/static/js/main.10fd6ba6.chunk.js",
"main.js.map": "/static/js/main.10fd6ba6.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.9f0ba400.js",
"runtime-main.js.map": "/static/js/runtime-main.9f0ba400.js.map",
"static/css/2.ea4ba2f0.chunk.css": "/static/css/2.ea4ba2f0.chunk.css",
"static/js/2.ec475cb5.chunk.js": "/static/js/2.ec475cb5.chunk.js",
"static/js/2.ec475cb5.chunk.js.map": "/static/js/2.ec475cb5.chunk.js.map",
"index.html": "/index.html",
"precache-manifest.d2a8205cf40a06153e5874961a1e8606.js": "/precache-manifest.d2a8205cf40a06153e5874961a1e8606.js",
"precache-manifest.0f8fa25748364c540ce6365137ec0feb.js": "/precache-manifest.0f8fa25748364c540ce6365137ec0feb.js",
"service-worker.js": "/service-worker.js",
"static/css/2.ea4ba2f0.chunk.css.map": "/static/css/2.ea4ba2f0.chunk.css.map",
"static/js/2.ec475cb5.chunk.js.LICENSE.txt": "/static/js/2.ec475cb5.chunk.js.LICENSE.txt",
@ -20,6 +20,6 @@
"static/js/runtime-main.9f0ba400.js",
"static/css/2.ea4ba2f0.chunk.css",
"static/js/2.ec475cb5.chunk.js",
"static/js/main.bfb223c5.chunk.js"
"static/js/main.10fd6ba6.chunk.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeak Node</title><meta name="description" content="Squeak Node is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.ec475cb5.chunk.js"></script><script src="/static/js/main.bfb223c5.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Squeak Node</title><meta name="description" content="Squeak Node is a frontend for accessing a squeak node"><meta name="keywords" content="squeak, bitcoin, lightning"><meta name="author" content="Flatlogic LLC."><link href="/static/css/2.ea4ba2f0.chunk.css" rel="stylesheet"></head><body style="font-family:Roboto,sans-serif"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,l=r[0],a=r[1],i=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var a=t[l];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var l=this["webpackJsonpsqueak-node-frontend"]=this["webpackJsonpsqueak-node-frontend"]||[],a=l.push.bind(l);l.push=r,l=l.slice();for(var i=0;i<l.length;i++)r(l[i]);var p=a;t()}([])</script><script src="/static/js/2.ec475cb5.chunk.js"></script><script src="/static/js/main.10fd6ba6.chunk.js"></script></body></html>

View file

@ -1,6 +1,6 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "99ccc6cb2113f0a4951619caa302e43b",
"revision": "b5011907348412eb78b65d9d21258ae6",
"url": "/index.html"
},
{
@ -16,8 +16,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "/static/js/2.ec475cb5.chunk.js.LICENSE.txt"
},
{
"revision": "d7ae9f2d5721cc3f45f3",
"url": "/static/js/main.bfb223c5.chunk.js"
"revision": "16b19bbda111a5670399",
"url": "/static/js/main.10fd6ba6.chunk.js"
},
{
"revision": "cc9816de5a8639d377ea",

View file

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"/precache-manifest.d2a8205cf40a06153e5874961a1e8606.js"
"/precache-manifest.0f8fa25748364c540ce6365137ec0feb.js"
);
self.addEventListener('message', (event) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,7 @@ class SqueakEntry(NamedTuple):
block_height: int
block_hash: bytes
block_time: int
squeak_time: int
reply_to: Optional[bytes]
is_unlocked: bool
squeak_profile: Optional[SqueakProfile]

View file

@ -11,6 +11,8 @@ import sqlalchemy
from bitcoin.core import CBlockHeader
from sqlalchemy import func
from sqlalchemy import literal
from sqlalchemy.sql import and_
from sqlalchemy.sql import or_
from sqlalchemy.sql import select
from squeak.core import CSqueak
@ -31,6 +33,10 @@ from squeaknode.db.migrations import run_migrations
from squeaknode.db.models import Models
MAX_INT = 999999999
MAX_HASH = b'\xff' * 32
logger = logging.getLogger(__name__)
@ -212,8 +218,28 @@ class SqueakDb:
return None
return self._parse_squeak_entry(row)
def get_timeline_squeak_entries(self) -> List[SqueakEntry]:
def get_timeline_squeak_entries(
self,
limit: int,
block_height: int = MAX_INT,
squeak_time: int = MAX_INT,
squeak_hash: bytes = MAX_HASH,
) -> List[SqueakEntry]:
""" Get all followed squeaks. """
block_height = block_height or MAX_INT
squeak_time = squeak_time or MAX_INT
squeak_hash = squeak_hash or MAX_HASH
logger.info("""Timeline db query with
limit: {}
block_height: {}
squeak_time: {}
squeak_hash: {}
""".format(
limit,
block_height,
squeak_time,
squeak_hash.hex(),
))
s = (
select([self.squeaks, self.profiles])
.select_from(
@ -222,10 +248,24 @@ class SqueakDb:
self.profiles.c.address == self.squeaks.c.author_address,
)
)
.where(or_(
self.squeaks.c.n_block_height < block_height,
and_(
self.squeaks.c.n_block_height == block_height,
self.squeaks.c.n_time < squeak_time,
),
and_(
self.squeaks.c.n_block_height == block_height,
self.squeaks.c.n_time == squeak_time,
self.squeaks.c.hash < squeak_hash,
),
))
.order_by(
self.squeaks.c.n_block_height.desc(),
self.squeaks.c.n_time.desc(),
self.squeaks.c.hash.desc(),
)
.limit(limit)
)
with self.get_connection() as connection:
result = connection.execute(s)
@ -1103,6 +1143,7 @@ class SqueakDb:
block_height=row["n_block_height"],
block_hash=(row["hash_block"]),
block_time=row["block_time"],
squeak_time=row["n_time"],
reply_to=reply_to,
is_unlocked=is_locked,
liked_time=liked_time_s,

View file

@ -373,8 +373,19 @@ class SqueakController:
def get_squeak_entry(self, squeak_hash: bytes) -> Optional[SqueakEntry]:
return self.squeak_db.get_squeak_entry(squeak_hash)
def get_timeline_squeak_entries(self) -> List[SqueakEntry]:
return self.squeak_db.get_timeline_squeak_entries()
def get_timeline_squeak_entries(
self,
limit: int,
block_height: int,
squeak_time: int,
squeak_hash: bytes,
) -> List[SqueakEntry]:
return self.squeak_db.get_timeline_squeak_entries(
limit,
block_height,
squeak_time,
squeak_hash,
)
def get_liked_squeak_entries(self) -> List[SqueakEntry]:
return self.squeak_db.get_liked_squeak_entries()