Skip to content

Commit

Permalink
simplify label fetch greatly (#265)
Browse files Browse the repository at this point in the history
this will also fix the crash we're seeing when our current dev branch
queries against the prod api without a return added for the all the
labelers
  • Loading branch information
thieflord06 authored Jan 14, 2025
2 parents de56711 + 46701fc commit ef603e5
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 126 deletions.
111 changes: 49 additions & 62 deletions src/api/labled.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// @ts-check

import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
import { fetchClearskyApi, unwrapShortDID } from './core';


import { useQuery } from '@tanstack/react-query';
import { fetchClearskyApi, unwrapShortDID, publicAtClient } from './core';

/**
*
* @param {string|undefined} fullDid
* @param {string[]} lablerDids
* @param {AbortSignal} signal
*/
export async function getLabeled(fullDid, lablerDids) {
let queryUrl = `https://api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${fullDid}`;
if(!fullDid){
export async function getLabeled(fullDid, lablerDids, signal) {
if (!fullDid) {
return [];
}
const data = await fetch(queryUrl,{
headers: {
'atproto-accept-labelers': lablerDids.join(',')
},
}).then((r) =>
r.json()
const req = await publicAtClient.getProfile(
{ actor: fullDid },
{
headers: {
'atproto-accept-labelers': lablerDids.join(','),
},
signal,
}
);
return data.labels || [];

return req.data.labels || [];
}

/**
Expand All @@ -38,73 +38,60 @@ export async function getLabeled(fullDid, lablerDids) {
* Fetches labelers from the Clearsky API.
* @returns {Promise<string[]>} A promise that resolves to an array of dids of labelers.
*/
async function getLabelers(){
const data = await fetchClearskyApi('v1', 'get-labelers/dids')
async function getLabelers() {
const data = await fetchClearskyApi('v1', 'get-labelers/dids');
return data.data;
}

export function useLabelers() {
return useQuery({
queryKey: ['v1','get-labelers','dids'],
queryKey: ['v1', 'get-labelers', 'dids'],
queryFn: () => getLabelers(),
});
}

// Can only query 18 labelers at a time.
const BATCH_SIZE = 17;

/**
* Get sets of 18 dids at a time from the lablerDids array to query.
* @param {string} fullDid
* @param {string[]|undefined} lablerDids
* @param {AbortSignal} signal
* @returns
*/
function* getDidSlices(fullDid, lablerDids, signal) {
if (!lablerDids) {
return [];
}
let page = 1;
while (true) {
const start = (page - 1) * BATCH_SIZE;
if (start >= lablerDids.length) {
return;
}
yield getLabeled(
fullDid,
lablerDids.slice(start, start + BATCH_SIZE),
signal
);
page += 1;
}
}

/**
* Get all labels applied to an actor by a list of labelers.
*
* @param {string|undefined} did
* @param {string[]|undefined} lablerDids
* @returns
*/
export function useLabeled(did,lablerDids){
export function useLabeled(did, lablerDids) {
const fullDid = unwrapShortDID(did);
/**
* Get 18 dids from the lablerDids array to query.
*
* @param {number} page
* @returns
*/
function getDidSlice(page){
if(!lablerDids){
return [];
}
if( 1 === page ){
return lablerDids.slice(0,BATCH_SIZE);
}

const start = (page - 1) * BATCH_SIZE;
return lablerDids.slice(start,start + BATCH_SIZE);
}
const {
fetchNextPage,
hasNextPage,
isLoading,
isFetchingNextPage,
data,
} = useInfiniteQuery({
enabled: !!fullDid && lablerDids && lablerDids.length > 0,
return useQuery({
enabled: !!fullDid && !!lablerDids?.length,
queryKey: ['labeled', fullDid, lablerDids],
queryFn: ({ pageParam = 1 }) => getLabeled(fullDid || '', getDidSlice(pageParam)),
getNextPageParam: (_lastPage, allPages) => {
//@ts-ignore Will not run if lablerDids is undefined
if(allPages.length >= lablerDids.length / BATCH_SIZE){
return undefined;
}
return allPages.length + 1;
},
initialPageParam: 1,
queryFn: ({ signal }) =>
Promise.all(getDidSlices(fullDid || '', lablerDids, signal)),
});

useEffect(() => {
if(lablerDids&& hasNextPage && !isFetchingNextPage){
fetchNextPage();
}
}, [lablerDids,hasNextPage,isFetchingNextPage,fetchNextPage]);

return {data,isLoading}

}
127 changes: 63 additions & 64 deletions src/detail-panels/labeled/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { useMemo } from 'react';


import { useLabeled, useLabelers } from '../../api/labled';
import { AccountShortEntry } from '../../common-components/account-short-entry';
import { FormatTimestamp } from '../../common-components/format-timestamp';
Expand All @@ -11,108 +10,108 @@ import './labeled.css';

/**
* @param {{
* cts: string,
* val: string,
* src: string,
* cts: string,
* val: string,
* src: string,
* }} _
*/
function LabeledListItem({ cts,val,src }){
function LabeledListItem({ cts, val, src }) {
const value = useMemo(() => {
let v = val;
//https://docs.bsky.app/docs/advanced-guides/moderation#global-label-values
if(val.startsWith('!')){
if (val.startsWith('!')) {
switch (val) {
case '!hide':
return 'Hidden'
return 'Hidden';
case '!warn':
return 'Warning'
return 'Warning';
case '!no-unauthenticated':
return 'Private Account'
return 'Private Account';
default:
//This should never happen.
v = val.slice(1,2).toUpperCase() + val.slice(2);
v = val.slice(1, 2).toUpperCase() + val.slice(2);
break; //intenitionally not returning yet.
}
}

//has a - in it?
if(v.includes('-')){
if (v.includes('-')) {
//split, uppercase first letter of each word, join with space
v = v.split('-').map((s) => s.slice(0, 1).toUpperCase() + s.slice(1)).join(' ');
}else{
v = v
.split('-')
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
.join(' ');
} else {
v = v.slice(0, 1).toUpperCase() + v.slice(1);
}
return v;
},[val]);
}, [val]);
return (
<>
<li className={'labeled'}>
<div className='row'>
<AccountShortEntry
className='labeler-owner'
withDisplayName
account={src}
/>
<FormatTimestamp
timestamp={cts}
noTooltip
className='labeled-date'
<li className={'labeled'}>
<div className="row">
<AccountShortEntry
className="labeler-owner"
withDisplayName
account={src}
/>
</div>
<div>
<span className='label-name'>
{value}
</span>
</div>
</li>
<FormatTimestamp timestamp={cts} noTooltip className="labeled-date" />
</div>
<div>
<span className="label-name">{value}</span>
</div>
</li>
</>
);
}

/**
* @param {{
* labels: { cts: string, val: string,uri:string,src:string }[]
* }} _
*
* @returns {JSX.Element}
*/
function LabeledList({labels}) {
* labels: { cts: string, val: string,uri:string,src:string }[]
* }} _
*
* @returns {JSX.Element}
*/
function LabeledList({ labels }) {
return (
<ul className='labeled-view'>
{labels.map(({src,cts,val}) => (
<ul className="labeled-view">
{labels.map(({ src, cts, val }) => (
<LabeledListItem
key={`${src}-${val}-${cts}`}
src={src}
cts={cts}
val={val}
key={`${src}-${val}-${cts}`}
src={src}
cts={cts}
val={val}
/>
))}
</ul>
);
}

/** @typedef {import('@tanstack/react-query').InfiniteData<{ blocklist: (BlockedByRecord | { did: string; blocked_date: string })[]; count?: number }>} InfBlockData */

/**
* @this {never}
*/
export default function LabeledPanel({}) {
const accountQuery = useAccountResolver();
const did = accountQuery.data?.shortDID;
const {data:labelers,isLoading:isLoadingLabelers} = useLabelers();
const { data: labelers, isLoading: isLoadingLabelers } = useLabelers();

const {data:labels,isLoading} = useLabeled(did,labelers);
const { data: labels, isLoading } = useLabeled(did, labelers);
//Reduce pages to array of labels
const flatLabels = useMemo(() => {
if(!labels){
const flatLabels = useMemo(() => {
if (!labels) {
return [];
}
if(labels.pages){
return labels.pages.filter(Boolean).flat()
//unique label.val
.filter((label, i, arr) => arr.findIndex(l => l.val === label.val) === i);
if (labels) {
return (
labels
.flat()
//unique label.val
.filter(
(label, i, arr) => arr.findIndex((l) => l.val === label.val) === i
)
);
}
},[labels]);
}, [labels]);

return (
<div
Expand All @@ -123,17 +122,17 @@ export default function LabeledPanel({}) {
minHeight: '100%',
}}
>
<h3 className='labeled-header'>
Labeled {flatLabels?.length} Times
</h3>
<h3 className="labeled-header">Labeled {flatLabels?.length} Times</h3>

{isLoadingLabelers || isLoading ? <div>Loading...</div>:(
{isLoadingLabelers || isLoading ? (
<div>Loading...</div>
) : (
<>
{flatLabels && flatLabels.length ? (
<LabeledList labels={flatLabels} />
) : (
<div className='labeled-view no-labels'>No labels</div>
)}
{flatLabels && flatLabels.length ? (
<LabeledList labels={flatLabels} />
) : (
<div className="labeled-view no-labels">No labels</div>
)}
</>
)}
</div>
Expand Down

0 comments on commit ef603e5

Please sign in to comment.