Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(explorer): reorder columns. RequestKey is the relevant first column when showing transactions. Moved sender to the end #2754

Merged
merged 3 commits into from
Dec 20, 2024
Merged
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
8 changes: 8 additions & 0 deletions .changeset/soft-tomatoes-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@kadena/explorer': patch
---

Reordering columns to present the first column as the one to link to

Remove toggling close of truncated content. Will always open so users can no
select and copy
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,7 @@ export const BlockTransactions: FC<IProps> = ({ hash }) => {
loaderVariant: 'icon',
},
{
label: 'Sender',
key: 'cmd.meta.sender',
variant: 'code',
width: '25%',
render: FormatLinkWrapper({ url: '/account/:value' }),
},
{
label: 'RequestKey',
label: 'Request Key',
key: 'hash',
variant: 'code',
width: '25%',
Expand All @@ -114,6 +107,13 @@ export const BlockTransactions: FC<IProps> = ({ hash }) => {
width: '40%',
render: CompactTableFormatters.FormatJsonParse(),
},
{
label: 'Sender',
key: 'cmd.meta.sender',
variant: 'code',
width: '25%',
render: FormatLinkWrapper({ url: '/account/:value' }),
},
]}
data={innerData.node.transactions.edges.map(
(edge) => edge.node as Transaction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CopyButton } from '@/components/CopyButton/CopyButton';
import { Stack, Text } from '@kadena/kode-ui';
import classNames from 'classnames';
import type { FC } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useState } from 'react';
import {
descriptionDetailsClass,
descriptionDetailsExpandedClass,
Expand All @@ -14,57 +14,12 @@ interface IProps {
field: IDataRenderComponentField;
}

const storageKey = 'expandedfields';

export const ExpandTruncatedField: FC<IProps> = ({ field }) => {
const [isExpanded, setIsExpanded] = useState(false);

const toggleExpand = (): void => {
let storage: string[] = JSON.parse(
localStorage.getItem(storageKey) ?? '[]',
);

const key = field.key ? field.key : field.id ?? '';

if (isExpanded) {
storage = storage.filter((v) => v !== key);
} else {
storage.push(key);
}

localStorage.setItem(storageKey, JSON.stringify(storage));

window.dispatchEvent(new Event(storageKey));
setIsExpanded((v) => !isExpanded);
};

const checkStorage = () => {
const storage: string[] = JSON.parse(
localStorage.getItem(storageKey) ?? '[]',
);

setIsExpanded(storage.includes(field.key));
};
const storageListener = useCallback((event: StorageEvent | Event) => {
if (event.type !== storageKey && 'key' in event && event.key !== storageKey)
return;

checkStorage();
}, []);

useEffect(() => {
checkStorage();
window.addEventListener(storageKey, storageListener);
window.addEventListener('storage', storageListener);
return () => {
window.removeEventListener(storageKey, storageListener);
window.removeEventListener('storage', storageListener);
};
}, [storageListener]);

return (
<Stack
onClick={toggleExpand}
onClick={() => setIsExpanded(true)}
as="dd"
gap="xs"
className={classNames(descriptionDetailsClass, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,35 @@
import { Heading, Stack } from '@kadena/kode-ui';
import { Heading, Text } from '@kadena/kode-ui';
import type { FC } from 'react';
import React from 'react';

interface NoSearchResultsProps {
interface INoSearchResultsProps {
type?: 'requestKey' | 'accountName' | 'blockhash';
value?: string;
}

export const NoSearchResults: FC<NoSearchResultsProps> = ({ type, value }) => {
export const NoSearchResults: FC<INoSearchResultsProps> = ({ type, value }) => {
// TODO: add buttons to navigate to other networks testnet/mainnet
// Use url from router to query other networks to see if the search result is
// there

switch (true) {
case type === 'requestKey' && value !== undefined:
return (
<Stack justifyContent="center" width="100%">
<Heading as="h3">No search results for request key: {value}</Heading>
</Stack>
<Heading as="h3">No search results for request key: {value}</Heading>
);

case type === 'accountName' && value !== undefined:
return (
<Stack justifyContent="center" flexDirection={'column'} width="100%">
<>
<Heading as="h3">No search results for account: {value}</Heading>
<Heading as="h4">Please check the account name and try again</Heading>
</Stack>
<Text>Please check the network and account name and try again</Text>
</>
);

case type === 'blockhash' && value !== undefined:
return (
<Stack justifyContent="center" width="100%">
<Heading as="h3">No search results for block: {value}</Heading>
</Stack>
);
return <Heading as="h3">No search results for block: {value}</Heading>;

default:
return (
<Stack justifyContent="center" width="100%">
<Heading as="h3">No search results</Heading>
</Stack>
);
return <Heading as="h3">No search results</Heading>;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ const Height: React.FC = () => {
key={`chain${chainData.chainId}`}
>
{chainData.data.edges.length === 0 ? (
<NoSearchResults />
<LayoutBody>
<NoSearchResults />
</LayoutBody>
) : (
<CompactTable
isLoading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const Transaction: React.FC = () => {
}, [loading, data, error, setIsLoading]);

return (
<Layout>
<Layout layout="full">
{innerData && innerData.transaction ? (
<>
<LayoutHeader>
Expand Down Expand Up @@ -120,7 +120,12 @@ const Transaction: React.FC = () => {
</>
) : (
!Array.isArray(router.query.requestKey) && (
<NoSearchResults type="requestKey" value={router.query.requestKey} />
<LayoutBody>
<NoSearchResults
type="requestKey"
value={router.query.requestKey}
/>
</LayoutBody>
)
)}
</Layout>
Expand Down
79 changes: 79 additions & 0 deletions packages/apps/explorer/src/utils/__test__/condenseString.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { condenseStrings } from '../condenseStrings';

describe('condenseString', () => {
const singleWord =
'thisisaverylongstringthatshouldbecondensedthisisaverylongstringthatshouldbecondensed';
const multiWord =
'thisisaverylongstringthatshouldbecond ensedthisisaverylongstringthatshouldbecondensed thisIsAshorterString';

describe('with default options', () => {
it('condenses a single string', () => {
expect(condenseStrings(singleWord)).toBe('thisi…ensed');
});

it('condenses a string with multiple words', () => {
expect(condenseStrings(multiWord)).toBe(
'thisi…econd ensed…ensed thisIsAshorterString',
);
});
});
describe('with custom options', () => {
it('minLength 10', () => {
expect(condenseStrings(singleWord, { minLength: 10 })).toBe(
'thisi…ensed',
);
expect(condenseStrings(multiWord, { minLength: 10 })).toBe(
'thisi…econd ensed…ensed thisI…tring',
);
});

it('replacement "..."', () => {
expect(condenseStrings(singleWord, { replacement: '...' })).toBe(
'thisi...ensed',
);
expect(condenseStrings(multiWord, { replacement: '...' })).toBe(
'thisi...econd ensed...ensed thisIsAshorterString',
);
});

it('startLength 10', () => {
expect(condenseStrings(singleWord, { startLength: 10 })).toBe(
'thisisaver…ensed',
);
expect(condenseStrings(multiWord, { startLength: 10 })).toBe(
'thisisaver…econd ensedthisi…ensed thisIsAshorterString',
);
});

it('endLength 10', () => {
expect(condenseStrings(singleWord, { endLength: 10 })).toBe(
'thisi…econdensed',
);
expect(condenseStrings(multiWord, { endLength: 10 })).toBe(
'thisi…ouldbecond ensed…econdensed thisIsAshorterString',
);
});

it('all options', () => {
expect(
condenseStrings(singleWord, {
minLength: 40,
replacement: '*.*',
startLength: 5,
endLength: 20,
}),
).toBe('thisi*.*hatshouldbecondensed');

expect(
condenseStrings(multiWord, {
minLength: 40,
replacement: '*.*',
startLength: 5,
endLength: 20,
}),
).toBe(
'thisisaverylongstringthatshouldbecond ensed*.*hatshouldbecondensed thisIsAshorterString',
);
});
});
});
43 changes: 43 additions & 0 deletions packages/apps/explorer/src/utils/condenseStrings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Shortens long sections in a string by keeping the first 5 and last 5 characters.
sstraatemans marked this conversation as resolved.
Show resolved Hide resolved
* @param str - string to shorten
* @returns {[string, string]} - shortened string and original string
*/
export function condenseStrings(
str: string,
options?: {
minLength?: number;
replacement?: string;
startLength?: number;
endLength?: number;
},
): string {
// set default options and override with provided options
let config;
if (options) {
config = {
...{ minLength: 22, replacement: '…', startLength: 5, endLength: 5 },
...options,
};
} else {
config = {
minLength: 22,
replacement: '…',
startLength: 5,
endLength: 5,
};
}

// Regex explanation:
// Split on sequences of characters that are NOT letters, digits, '_' or '-'.
const parts = str.split(/([^\w\d]+)/u);

return parts
.map((part) => {
if (/^[\w\d]+$/u.test(part) && part.length > config.minLength) {
return `${part.slice(0, config.startLength)}${config.replacement}${part.slice(-config.endLength)}`;
}
return part;
})
.join('');
}
Loading