Implement message stub query for latest messages

pull/1/head
J M Rossy 2 years ago
parent 486e65a6aa
commit e551e01bc0
  1. 10
      src/features/search/MessageDetails.tsx
  2. 50
      src/features/search/MessageSearch.tsx
  3. 4
      src/features/search/MessageSummary.tsx
  4. 84
      src/features/search/query.ts
  5. 19
      src/features/search/types.ts
  6. 8
      src/types.ts

@ -18,7 +18,7 @@ import { getHumanReadableTimeString } from '../../utils/time';
import { useInterval } from '../../utils/timeout';
import { PLACEHOLDER_MESSAGES } from './placeholderMessages';
import { parseResultData } from './query';
import { parseMessageQueryResult } from './query';
import { MessagesQueryResult } from './types';
const AUTO_REFRESH_DELAY = 10000;
@ -26,11 +26,10 @@ const AUTO_REFRESH_DELAY = 10000;
export function MessageDetails({ messageId }: { messageId: string }) {
const [result, reexecuteQuery] = useQuery<MessagesQueryResult>({
query: messageDetailsQuery,
variables: { messageId: parseInt(messageId) },
variables: { messageId },
});
const { data, fetching, error } = result;
const messages = useMemo(() => parseResultData(data), [data]);
const messages = useMemo(() => parseMessageQueryResult(data), [data]);
const isMessageFound = messages.length > 0;
const shouldBlur = !isMessageFound || fetching;
@ -385,8 +384,7 @@ query MessageDetails ($messageId: bigint!){
processable
}
}
}
`;
}`;
const helpText = {
origin:

@ -19,11 +19,11 @@ import { sanitizeString } from '../../utils/string';
import { useInterval } from '../../utils/timeout';
import { MessageSummary } from './MessageSummary';
import { parseResultData } from './query';
import { MessagesQueryResult } from './types';
import { parseMessageStubResult } from './query';
import { MessagesStubQueryResult } from './types';
import { isValidSearchQuery } from './utils';
const AUTO_REFRESH_DELAY = 5000;
const AUTO_REFRESH_DELAY = 10000;
export function MessageSearch() {
// Search text input
@ -33,6 +33,9 @@ export function MessageSearch() {
setSearchInput(value);
};
const debouncedSearchInput = useDebounce(searchInput, 750);
const hasInput = !!debouncedSearchInput;
const sanitizedInput = sanitizeString(debouncedSearchInput);
const isValidInput = hasInput ? isValidSearchQuery(sanitizedInput) : true;
// Filter state and handlers
const chainOptions = useMemo(getChainOptionList, []);
@ -46,18 +49,15 @@ export function MessageSearch() {
};
// GraphQL query and results
const hasInput = !!debouncedSearchInput;
const sanitizedInput = sanitizeString(debouncedSearchInput);
const isValidInput = hasInput ? isValidSearchQuery(sanitizedInput) : true;
const query = hasInput ? searchMessagesQuery : latestMessagesQuery;
const variables = hasInput ? { search: sanitizedInput } : undefined;
const [result, reexecuteQuery] = useQuery<MessagesQueryResult>({
const [result, reexecuteQuery] = useQuery<MessagesStubQueryResult>({
query,
variables,
pause: !isValidInput,
});
const { data, fetching, error } = result;
const messageList = useMemo(() => parseResultData(data), [data]);
const messageList = useMemo(() => parseMessageStubResult(data), [data]);
const hasError = !!error;
const reExecutor = useCallback(() => {
if (query && isValidInput) {
@ -216,22 +216,32 @@ function getChainOptionList(): Array<{ value: string; display: string }> {
];
}
// TODO automatic typings
const latestMessagesQuery = `
query latestMessages {
messages(order_by: {origintimesent: desc}, limit: 8) {
query MessageDetails {
message(order_by: {timestamp: desc}, limit: 8) {
destination
id
origin
recipient
sender
timestamp
delivered_message {
id
tx_id
inbox_address
}
message_states {
block_height
block_timestamp
error_msg
estimated_gas_cost
id
status
sender
recipient
body
originchainid
origintimesent
destinationchainid
destinationtimesent
processable
}
}`;
}
}`;
// TODO
const searchMessagesQuery = `
query searchMessages ($search: String!) {
messages (search: $search, order_by: {origintimesent: desc}, limit: 8) {

@ -1,11 +1,11 @@
import Link from 'next/link';
import { ChainToChain } from '../../components/icons/ChainToChain';
import { Message, MessageStatus } from '../../types';
import { MessageStatus, MessageStub } from '../../types';
import { shortenAddress } from '../../utils/addresses';
import { getHumanReadableTimeString } from '../../utils/time';
export function MessageSummary({ message }: { message: Message }) {
export function MessageSummary({ message }: { message: MessageStub }) {
const {
id,
status,

@ -1,38 +1,67 @@
import { DomainToChain } from '../../consts/domains';
import { Message, MessageStatus, PartialTransactionReceipt } from '../../types';
import {
Message,
MessageStatus,
MessageStub,
PartialTransactionReceipt,
} from '../../types';
import { logger } from '../../utils/logger';
import { MessageEntry, MessagesQueryResult, TransactionEntry } from './types';
import {
MessageEntry,
MessageStubEntry,
MessagesQueryResult,
MessagesStubQueryResult,
TransactionEntry,
} from './types';
export function parseResultData(
export function parseMessageStubResult(
data: MessagesStubQueryResult | undefined,
): MessageStub[] {
if (!data?.message?.length) return [];
return data.message
.map(parseMessageStub)
.filter((m): m is MessageStub => !!m);
}
export function parseMessageQueryResult(
data: MessagesQueryResult | undefined,
): Message[] {
if (!data?.message?.length) return [];
return data.message.map(parseMessage).filter((m): m is Message => !!m);
}
function parseMessage(m: MessageEntry): Message | null {
function parseMessageStub(m: MessageStubEntry): MessageStub | null {
try {
const { delivered_message, message_states } = m;
let status = MessageStatus.Pending;
let destinationTransaction: PartialTransactionReceipt | undefined =
undefined;
if (delivered_message) {
status = MessageStatus.Delivered;
destinationTransaction = parseTransaction(delivered_message.transaction);
} else if (message_states.length > 0) {
const latestState = message_states.at(-1);
if (latestState && latestState.processable) {
status = MessageStatus.Failing;
}
}
const status = getMessageStatus(m);
return {
id: m.id,
status,
sender: decodeBinaryHex(m.sender),
recipient: decodeBinaryHex(m.recipient),
originChainId: DomainToChain[m.origin],
destinationChainId: DomainToChain[m.destination],
timestamp: parseTimestampString(m.timestamp),
};
} catch (error) {
logger.error('Error parsing message', error);
return null;
}
}
function parseMessage(m: MessageEntry): Message | null {
try {
const status = getMessageStatus(m);
const destinationTransaction =
status === MessageStatus.Delivered && m.delivered_message?.transaction
? parseTransaction(m.delivered_message.transaction)
: undefined;
return {
id: m.id,
status,
sender: decodeBinaryHex(m.sender),
recipient: decodeBinaryHex(m.recipient),
body: m.msg_body ?? '',
body: decodeBinaryHex(m.msg_body ?? ''),
originChainId: DomainToChain[m.origin],
destinationChainId: DomainToChain[m.destination],
timestamp: parseTimestampString(m.timestamp),
@ -59,11 +88,20 @@ function parseTimestampString(t: string) {
return new Date(t).getTime();
}
// TODO use text in db instead of bytea
// TODO remove when db uses text
function decodeBinaryHex(b: string) {
return btoa(b);
// const byteArray = b.substring(3).map(c => parseInt(c))
// return Array.from(byteArray, (byte)=>
// ('0' + (byte & 0xFF).toString(16)).slice(-2)
// ).join('')
}
function getMessageStatus(m: MessageEntry | MessageStubEntry) {
const { delivered_message, message_states } = m;
if (delivered_message) {
return MessageStatus.Delivered;
} else if (message_states.length > 0) {
const latestState = message_states.at(-1);
if (latestState && latestState.processable) {
return MessageStatus.Failing;
}
}
return MessageStatus.Pending;
}

@ -22,10 +22,13 @@ export interface TransactionEntry {
block: BlockEntry;
}
export interface DeliveredMessageEntry {
export interface DeliveredMessageStubEntry {
id: number;
inbox_address: string; // binary e.g. \\x123
tx_id: number;
}
export interface DeliveredMessageEntry extends DeliveredMessageStubEntry {
transaction: TransactionEntry;
}
@ -38,21 +41,31 @@ export interface MessageStateEntry {
processable: boolean;
}
export interface MessageEntry {
export interface MessageStubEntry {
id: number;
destination: number;
origin: number;
recipient: string; // binary e.g. \\x123
sender: string; // binary e.g. \\x123
timestamp: string; // e.g. "2022-08-28T17:30:15"
transaction: TransactionEntry; // origin transaction
delivered_message: DeliveredMessageStubEntry | null | undefined;
message_states: MessageStateEntry[];
}
export interface MessageEntry extends MessageStubEntry {
outbox_address: string; // binary e.g. \\x123
msg_body: string | null | undefined; // binary e.g. \\x123
timestamp: string; // e.g. "2022-08-28T17:30:15"
origin_tx_id: number;
transaction: TransactionEntry; // origin transaction
delivered_message: DeliveredMessageEntry | null | undefined;
message_states: MessageStateEntry[];
}
export interface MessagesStubQueryResult {
message: MessageStubEntry[];
}
export interface MessagesQueryResult {
message: MessageEntry[];
}

@ -14,16 +14,18 @@ export enum MessageStatus {
Failing = 'failing',
}
// Partially modeled after SDK AbacusMessage class
export interface Message {
export interface MessageStub {
id: number;
status: MessageStatus;
sender: Address;
recipient: Address;
body: string;
originChainId: number;
destinationChainId: number;
timestamp: number; // Note, equivalent to timestamp in originTx
}
export interface Message extends MessageStub {
body: string;
originTransaction: PartialTransactionReceipt;
destinationTransaction?: PartialTransactionReceipt;
}

Loading…
Cancel
Save