Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ import { artemisService } from '../artemis-service';

export type MessageProps = {
currentMessage: Message,
back?: Function
back?: Function,
onNext?: () => void,
onPrevious?: () => void,
hasNext?: boolean,
hasPrevious?: boolean,
currentIndex?: number,
totalCount?: number
}

export type Message = {
Expand Down Expand Up @@ -52,7 +58,7 @@ export type Message = {

export const MessageView: React.FunctionComponent<MessageProps> = props => {

const [currentMessage] = useState(props.currentMessage);
const currentMessage = props.currentMessage;
const [messageBody, setMessageBody] = useState("");
const [messageTextMode, setMessageTextMode] = useState("");

Expand Down Expand Up @@ -126,7 +132,9 @@ export const MessageView: React.FunctionComponent<MessageProps> = props => {

return (
<>
<Title headingLevel="h4">Message ID: {currentMessage.messageID}</Title>
<Title headingLevel="h4">
Message ID: {currentMessage.messageID} ({(props.currentIndex ?? 0) + 1}/{props.totalCount})
</Title>
<Title headingLevel="h4">Displaying Body as : {messageTextMode}</Title>
<TextArea id="body" autoResize isDisabled value={messageBody}></TextArea>
<Title headingLevel="h4">Headers</Title>
Expand Down Expand Up @@ -229,6 +237,12 @@ export const MessageView: React.FunctionComponent<MessageProps> = props => {
<><Button id='message-view-queues-button' onClick={() => { if (props.back) { props.back(0); } } }>Queues</Button>
<Button id='message-view-browse-button' onClick={() => { if (props.back) { props.back(1); } }}>Browse</Button></>
}
{(props.onPrevious || props.onNext) &&
<>
<Button id='message-view-previous-button' isDisabled={!props.hasPrevious} onClick={() => { if (props.onPrevious) { props.onPrevious(); } }}>Previous</Button>
<Button id='message-view-next-button' isDisabled={!props.hasNext} onClick={() => { if (props.onNext) { props.onNext(); } }}>Next</Button>
</>
}
</>
)
}
Expand All @@ -237,7 +251,7 @@ function getProps(properties: any, type:string): React.ReactNode {
if(properties) {
return Object.keys(properties).map((key, index) => {
return (
<Tr id={key}>
<Tr id={key} key={key}>
<Td id={key + "key"}>{key}</Td>
<Td id={key + "val"}>{"" + properties[key]}</Td>
<Td id={key + "type"}>{type}</Td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext, useEffect, useState } from 'react'
import React, { useContext, useEffect, useImperativeHandle, useRef, useState, forwardRef } from 'react'
import { Column } from '../table/ArtemisTable';
import { artemisService } from '../artemis-service';
import { Toolbar, ToolbarContent, ToolbarItem, Text, SearchInput, Button, PaginationVariant, Pagination, DataList, DataListCell, DataListCheck, DataListItem, DataListItemCells, DataListItemRow, Modal, TextContent, Icon, ModalVariant } from '@patternfly/react-core';
Expand All @@ -34,17 +34,60 @@ export type MessageProps = {
address: string,
queue: string,
routingType: string,
selectMessage?: Function,
selectMessage?: (message: any, globalIndex: number, totalCount: number) => void,
onTotalCountChange?: (count: number) => void,
back?: Function
}

export type MessagesTableHandle = {
fetchMessageAt: (globalIndex: number) => Promise<{ message: Message | null; totalCount: number }>;
}

export const MessagesTable = forwardRef<MessagesTableHandle, MessageProps>((props, ref) => {
useImperativeHandle(ref, () => ({ fetchMessageAt }));

const fetchMessageAt = async (globalIndex: number): Promise<{ message: Message | null; totalCount: number }> => {
const targetPage = Math.floor(globalIndex / perPage) + 1;
const indexInPage = globalIndex % perPage;

if (targetPage === page) {
return { message: rowsRef.current[indexInPage] ?? null, totalCount: resultsSizeRef.current };
}

const cached = pageCacheRef.current.get(targetPage);
if (cached) {
return { message: cached[indexInPage] ?? null, totalCount: resultsSizeRef.current };
}

export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
try {
const brokerObjectname = await artemisService.getBrokerObjectName();
const queueMBean: string = createQueueObjectName(brokerObjectname, props.address, props.routingType, props.queue);
const response: any = await artemisService.getMessages(queueMBean, targetPage, perPage, filter);
const data = response?.data ?? [];
const totalCount = response?.count ?? resultsSizeRef.current;

if (totalCount !== resultsSizeRef.current) {
// count shifted (e.g. messages deleted elsewhere) — cached pages are no longer trustworthy
pageCacheRef.current.clear();
resultsSizeRef.current = totalCount;
setresultsSize(totalCount);
props.onTotalCountChange?.(totalCount);
} else {
pageCacheRef.current.set(targetPage, data);
}

return { message: (data[indexInPage] as Message) ?? null, totalCount };
} catch (error) {
eventService.notify({ type: 'warning', message: jolokiaService.errorMessage(error) });
return { message: null, totalCount: resultsSizeRef.current };
}
};

const messageView = (row: any) => {
if (props.selectMessage) { props.selectMessage(row); }
const currentRows = rowsRef.current;
const indexInPage = currentRows.findIndex((r: any) => r.messageID === row.messageID);
const globalIndex = (page - 1) * perPage + indexInPage;
if (props.selectMessage) { props.selectMessage(row, globalIndex, resultsSizeRef.current); }
}

const allColumns: Column[] = [
Expand All @@ -64,7 +107,7 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
const [filter, setFilter] = useState("");
const [inputValue, setInputValue] = useState('');
const [page, setPage] = useState(1);
const [rows, setRows] = useState([])
const [rows, setRows] = useState<any[]>([])
const [perPage, setPerPage] = useState(10);
const [columns, setColumns] = useState(allColumns);
const [columnsLoaded, setColumnsLoaded] = useState(false);
Expand All @@ -77,33 +120,48 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
const [showCopyMessagesModal, setShowCopyMessagesModal] = useState(false);
const [showRetryMessagesModal, setShowRetryMessagesModal] = useState(false);
const [showResendModal, setShowResendModal] = useState(false);
const [ resendMessage, setResendMessage] = useState<Message | undefined>();
const [resendMessage, setResendMessage] = useState<Message | undefined>();

const { brokerNode } = useContext(ArtemisContext);

const rowsRef = useRef<any[]>([]);
const resultsSizeRef = useRef<number>(0);
const pageCacheRef = useRef<Map<number, any[]>>(new Map());

useEffect(() => {
rowsRef.current = rows;
}, [rows]);
useEffect(() => {
resultsSizeRef.current = resultsSize;
}, [resultsSize]);
useEffect(() => {
pageCacheRef.current.clear();
}, [props.address, props.routingType, props.queue, perPage, filter]);

useEffect(() => {
const listMessages = async (): Promise<any> => {
const brokerObjectname = await artemisService.getBrokerObjectName();
const queueMBean: string = createQueueObjectName(brokerObjectname, props.address, props.routingType, props.queue);
const response = await artemisService.getMessages(queueMBean, page, perPage, filter);
return response;
}
const listData = async () => {
listMessages().then((data) => {
setRows(data.data);
setresultsSize(data.count);
props.onTotalCountChange?.(data.count);
}).catch((error) => {
eventService.notify({type: 'warning', message: jolokiaService.errorMessage(error) })
})
}
const listMessages = async (): Promise<any> => {
const brokerObjectname = await artemisService.getBrokerObjectName();
const queueMBean: string = createQueueObjectName(brokerObjectname, props.address, props.routingType, props.queue);
const response = await artemisService.getMessages(queueMBean, page, perPage, filter);
return response;
}

setPerPage(artemisPreferencesService.loadTablePageSize(columnStorage.messages));
if (!columnsLoaded) {
const updatedColumns: Column[] = artemisPreferencesService.loadColumnPreferences(columnStorage.messages, allColumns);
setColumns(updatedColumns);
setColumnsLoaded(true);
}
listData();

}, [props.address, props.routingType, props.queue, page, perPage, filter, selectedMessages])

const handleSetPage = (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => {
Expand Down Expand Up @@ -131,6 +189,8 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
};

const getRowActions = (row: any): IAction[] => {
const indexInPage = rows.findIndex((r: any) => r.messageID === row.messageID);
const globalIndex = (page - 1) * perPage + indexInPage;
return [
{
title: 'Delete',
Expand All @@ -144,7 +204,7 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
title: 'View',
id: 'message-dropdown-view',
onClick: () => {
if (props.selectMessage) { props.selectMessage(row); }
if (props.selectMessage) { props.selectMessage(row, globalIndex, resultsSizeRef.current); }
}
},
{
Expand Down Expand Up @@ -392,6 +452,7 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
<Thead>
<Tr >
<Th
aria-label="Select all"
select={{
onSelect: (_event, isSelecting) => selectAllMessages(isSelecting),
isSelected: areAllMessagesSelected
Expand Down Expand Up @@ -627,4 +688,4 @@ export const MessagesTable: React.FunctionComponent<MessageProps> = props => {
</Modal>
</React.Fragment>
);
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { Navigate } from '../views/ArtemisTabView';
import { QueuesTable } from './QueuesTable';
import { MessagesTable } from '../messages/MessagesTable';
import { MessagesTable, MessagesTableHandle } from '../messages/MessagesTable';
import { Filter } from '../table/ArtemisTable';
import { Button, Modal, ModalVariant, TextContent, Title, Text, Icon, TextVariants, TextList, TextListItem, TextListItemVariants, TextListVariants } from '@patternfly/react-core';
import { Message, MessageView } from '../messages/MessageView';
Expand All @@ -42,10 +42,6 @@ export type Queue = {
routingType: string
}





export const QueuesView: React.FunctionComponent<Navigate> = navigate => {
const initialMessage: Message = {
messageID: '',
Expand All @@ -62,10 +58,19 @@ export const QueuesView: React.FunctionComponent<Navigate> = navigate => {
userID: ''
};

const [ activeTabKey, setActiveTabKey ] = useState<string | number>(0);
const [ currentQueue, setCurrentQueue ] = useState<Queue>({name: "", address: "", routingType: "ANYCAST"});
const [ currentMessage, setCurrentMessage ] = useState<Message>(initialMessage);
const [ showHelpModal, setShowHelpModal ] = useState(false);
const [activeTabKey, setActiveTabKey] = useState<string | number>(0);
const [currentQueue, setCurrentQueue] = useState<Queue>({name: "", address: "", routingType: "ANYCAST"});
const [currentMessage, setCurrentMessage] = useState<Message>(initialMessage);
const [showHelpModal, setShowHelpModal] = useState(false);

const [currentIndex, setCurrentIndex] = useState(0);
const [totalMessages, setTotalMessages] = useState(0);
const messagesTableRef = useRef<MessagesTableHandle>(null);

const handleTotalCountChange = (count: number) => {
setTotalMessages(count);
setCurrentIndex(prev => (prev >= count ? Math.max(0, count - 1) : prev));
};

const selectQueue = (queue: string, address: string, routingType: string) => {
setCurrentQueue({
Expand All @@ -76,30 +81,84 @@ export const QueuesView: React.FunctionComponent<Navigate> = navigate => {
setActiveTabKey(1);
}

const selectMessage = (message: Message) => {
const selectMessage = (message: Message, globalIndex: number, totalCount: number) => {
setCurrentMessage(message);
setCurrentIndex(globalIndex);
setTotalMessages(totalCount);
setActiveTabKey(2);
}

const nextMessage = async () => {
const newIndex = currentIndex + 1;
if (!messagesTableRef.current || newIndex >= totalMessages) return;
const result = await messagesTableRef.current.fetchMessageAt(newIndex);
setTotalMessages(result.totalCount);
if (result.message) {
setCurrentIndex(newIndex);
setCurrentMessage(result.message);
} else {
// that message is gone (deleted) — try the new last valid index instead
const clamped = Math.min(newIndex, result.totalCount - 1);
if (clamped >= 0 && clamped !== currentIndex) {
const retry = await messagesTableRef.current.fetchMessageAt(clamped);
if (retry.message) {
setCurrentIndex(clamped);
setCurrentMessage(retry.message);
}
setTotalMessages(retry.totalCount);
}
}
}

const previousMessage = async () => {
const newIndex = currentIndex - 1;
if (!messagesTableRef.current || newIndex < 0) return;
const result = await messagesTableRef.current.fetchMessageAt(newIndex);
setTotalMessages(result.totalCount);
if (result.message) {
setCurrentIndex(newIndex);
setCurrentMessage(result.message);
} else {
const clamped = Math.max(0, Math.min(newIndex, result.totalCount - 1));
if (clamped !== currentIndex) {
const retry = await messagesTableRef.current.fetchMessageAt(clamped);
if (retry.message) {
setCurrentIndex(clamped);
setCurrentMessage(retry.message);
}
setTotalMessages(retry.totalCount);
}
}
}

const back = (tab: number) => {
setActiveTabKey(tab);
setActiveTabKey(tab);
}

return (
<div>
{activeTabKey === 0 &&
<QueuesTable search={navigate.search} filter={navigate.filter} selectQueue={selectQueue}/>
}
{activeTabKey === 1 &&
<>
<Title headingLevel='h2'>Browsing {currentQueue.name + ' '} <Icon status="info"><HelpIcon onClick={() => setShowHelpModal(true)}/></Icon></Title>
<MessagesTable address={currentQueue.address} queue={currentQueue.name} routingType={currentQueue.routingType} selectMessage={selectMessage} back={back}/>
</>
{activeTabKey !== 0 &&
<div style={{ display: activeTabKey === 1 ? 'block' : 'none' }}>
<Title headingLevel='h2'>Browsing {currentQueue.name + ' '} <Icon status="info"><HelpIcon onClick={() => setShowHelpModal(true)}/></Icon></Title>
<MessagesTable ref={messagesTableRef} address={currentQueue.address} queue={currentQueue.name} routingType={currentQueue.routingType} selectMessage={selectMessage} onTotalCountChange={handleTotalCountChange} back={back}/>
</div>
}
{activeTabKey === 2 &&
<>
<Title headingLevel='h2'>Viewing Message on {currentQueue.name}</Title>
<MessageView currentMessage={currentMessage} back={back}/>
<MessageView
currentMessage={currentMessage}
back={back}
onNext={nextMessage}
onPrevious={previousMessage}
hasNext={currentIndex < totalMessages - 1}
hasPrevious={currentIndex > 0}
currentIndex={currentIndex}
totalCount={totalMessages}
/>
</>
}
<Modal aria-label='copy-message-modal'
Expand Down