Skip to content

Commit 7eea17f

Browse files
feat: add type and structure on datasets (#67)
* feat: add dataprotector subgraph integration and update poco queries * fix: format dataprotector subgraph URL for Bellecour chain * fix: correct casing in dataprotector codegen configuration * fix: update import paths for GraphQL queries to use poco directory * feat(graphql): add execute function for dataprotector and poco subgraphs * fix: remove unused parameter from dataset type cell renderer * refactor: remove TypeBadge component and its usage in columns definition * fix: update import paths for GraphQL queries to use poco directory * fix: update dataprotector subgraph URL and refine document patterns in codegen configurations * feat: integrate dataset schemas into datasets preview and details, adding schema queries and TypeBadge component * feat: add InteractiveJsonViewer component for enhanced schema visualization and update dependencies Co-authored-by: Robin Le Caignec <[email protected]> * fix: update TypeBadge component styles for improved visual consistency * feat: add SchemaSearch component for filtering datasets by field types and integrate it into DatasetsRoute * refactor: move typeGroups definition outside of SchemaSearch function for improved readability * feat: add dataset schema type groups and integrate into SchemaSearch component * feat: add useEffect to automatically open schema search when filters are present * feat: enhance SchemaSearch component with new input and button styles, and add Plus icon for better UX * fix: correct color for 'string' keyword in borderTypeColor definition * feat: add 'Clear all' button to remove all filters and disable autoCapitalize on input field * feat: integrate tooltip functionality into TypeBadge component in datasets table * feat: add onSchemaSearch prop to TypeBadge and integrate schema search functionality in datasets * feat: implement clear all filters functionality in DatasetsRoute and pass handler to SchemaSearch component * feat: update TypeBadge and DatasetsRoute to use SchemaFilter type for schema search functionality * fix: update tooltip color for better visibility in dark mode * refactor: simplify pagination logic and enhance getDisplayData function for schema search * refactor: reorder fields in schemaSearchPaginatedQuery for improved clarity * refactor: streamline dataset formatting and simplify data retrieval logic in DatasetsRoute * refactor: rename execute function import for clarity in dataset address route * feat: implement useSchemaSearch hook for improved dataset navigation and filtering * refactor: remove unused fields from datasetsQuery and update schema key in useDatasetsSchemas hook * refactor: update TypeBadgeProps to use SchemaFilter type for schemaPaths and onSchemaSearch * refactor: update dataset formatting to improve clarity and consistency in data handling * refactor: remove console.log statements for cleaner code in AddressContributionTable and formatDataset functions * refactor: reorder legacy common types in dataset schema type groups for clarity * refactor: test adjust class names for DatasetsPreviewTable and WorkerpoolsPreviewTable for improved layout consistency * refactor: adjust DatasetsPreviewTable class for consistent max-height styling * refactor: adjust DatasetsPreviewTable and WorkerpoolsPreviewTable class names for improved layout consistency * refactor: enhance search button layout and add icon in TypeBadge component * refactor: update SchemaSearch button to enhance layout and add descriptive text * refactor: update loading state styling in TypeBadge component for improved layout
1 parent 93bce61 commit 7eea17f

23 files changed

+1107
-51
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@tanstack/react-router-devtools": "^1.130.13",
4040
"@tanstack/react-table": "^8.21.3",
4141
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
42+
"@uiw/react-json-view": "^2.0.0-alpha.37",
4243
"big.js": "^7.0.1",
4344
"buffer": "^6.0.3",
4445
"class-variance-authority": "^0.7.1",

src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const buttonVariants = cva(
2727
xs: 'h-6 rounded-full px-2 text-xs has-[>svg]:px-1.5 gap-1',
2828
sm: 'h-8 rounded-full gap-1.5 px-3 has-[>svg]:px-2.5',
2929
default: 'h-9 px-4 py-2 has-[>svg]:px-3 rounded-full',
30-
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
30+
lg: 'h-11 rounded-full px-6 has-[>svg]:px-4 text-md',
3131
icon: 'size-9 rounded-md',
3232
},
3333
},

src/config.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,44 @@ export const SUPPORTED_CHAINS = [
6464
isExperimental: true,
6565
},
6666
];
67+
68+
export const datasetSchemaTypeGroups = [
69+
{
70+
label: 'Common types',
71+
items: [
72+
{ value: 'string' },
73+
{ value: 'f64' },
74+
{ value: 'i128' },
75+
{ value: 'bool' },
76+
],
77+
},
78+
{
79+
label: 'Popular File Types',
80+
items: [
81+
{ value: 'application/pdf' },
82+
{ value: 'image/jpeg' },
83+
{ value: 'image/png' },
84+
{ value: 'image/gif' },
85+
{ value: 'video/mp4' },
86+
],
87+
},
88+
{
89+
label: 'Other File Types',
90+
items: [
91+
{ value: 'application/octet-stream' },
92+
{ value: 'application/xml' },
93+
{ value: 'application/zip' },
94+
{ value: 'image/bmp' },
95+
{ value: 'image/webp' },
96+
{ value: 'video/mpeg' },
97+
{ value: 'video/x-msvideo' },
98+
{ value: 'audio/midi' },
99+
{ value: 'audio/mpeg' },
100+
{ value: 'audio/x-wav' },
101+
],
102+
},
103+
{
104+
label: 'Legacy Common Types (deprecated)',
105+
items: [{ value: 'number' }, { value: 'boolean' }],
106+
},
107+
];

src/hooks/useSchemaSearch.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useNavigate, useSearch } from '@tanstack/react-router';
2+
import {
3+
encodeSchemaFilters,
4+
SchemaFilter,
5+
} from '@/modules/datasets/schemaFilters';
6+
import useUserStore from '@/stores/useUser.store';
7+
import { getChainFromId } from '@/utils/chain.utils';
8+
9+
type SchemaField = { path?: string | null; type?: string | null };
10+
11+
export function useSchemaSearch() {
12+
const navigate = useNavigate();
13+
const search = useSearch({ strict: false });
14+
const { chainId } = useUserStore();
15+
16+
const navigateToDatasets = (schemaFields: SchemaField[]) => {
17+
const schemaFilters: SchemaFilter[] = schemaFields
18+
.filter(
19+
(field): field is { path: string; type: string } =>
20+
field.path != null && field.type != null
21+
)
22+
.map((field) => ({ path: field.path, type: field.type }));
23+
24+
const encoded = encodeSchemaFilters(schemaFilters);
25+
navigate({
26+
to: `/${getChainFromId(chainId)?.slug}/datasets`,
27+
search: { ...search, schema: encoded },
28+
replace: false,
29+
resetScroll: true,
30+
});
31+
};
32+
33+
const updateCurrentPage = (
34+
schemaFilters: SchemaFilter[],
35+
setCurrentPage?: (page: number) => void
36+
) => {
37+
navigate({
38+
search: { ...search, schema: encodeSchemaFilters(schemaFilters) },
39+
replace: false,
40+
resetScroll: true,
41+
});
42+
setCurrentPage?.(0);
43+
};
44+
45+
return {
46+
navigateToDatasets,
47+
updateCurrentPage,
48+
};
49+
}

src/index.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
--popover: var(--color-grey-800);
208208
--popover-foreground: var(--color-grey-50);
209209

210-
--tooltip: var(--color-grey-800);
210+
--tooltip: var(--color-grey-600);
211211
--tooltip-foreground: var(--color-grey-50);
212212
--tooltip-border: var(--color-grey-700);
213213

@@ -279,4 +279,8 @@
279279
*::-webkit-scrollbar-track {
280280
@apply bg-grey-800 rounded-full;
281281
}
282+
283+
*::-webkit-scrollbar-corner {
284+
@apply bg-transparent;
285+
}
282286
}

src/modules/addresses/address/workers/beneficiaryDeals/addressContributionTable.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ export function AddressContributionTable({
8787
addressAddress,
8888
currentPage: currentPage - 1,
8989
});
90-
console.log('AddressContributionTable', contribution);
9190

9291
return (
9392
<div className="space-y-6">

src/modules/datasets/DatasetsPreviewTable.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ import { ChainLink } from '@/components/ChainLink';
77
import { DataTable } from '@/components/DataTable';
88
import DatasetIcon from '@/components/icons/DatasetIcon';
99
import { Button } from '@/components/ui/button';
10+
import { useSchemaSearch } from '@/hooks/useSchemaSearch';
1011
import useUserStore from '@/stores/useUser.store';
1112
import { createPlaceholderDataFnForQueryKey } from '@/utils/createPlaceholderDataFnForQueryKey';
1213
import { ErrorAlert } from '../ErrorAlert';
1314
import { datasetsQuery } from './datasetsQuery';
14-
import { columns } from './datasetsTable/columns';
15+
import { createColumns } from './datasetsTable/columns';
16+
import { useDatasetsSchemas } from './hooks/useDatasetsSchemas';
1517

1618
export function DatasetsPreviewTable({ className }: { className?: string }) {
1719
const { chainId } = useUserStore();
20+
const { navigateToDatasets } = useSchemaSearch();
1821

1922
const queryKey = [chainId, 'datasets_preview'];
2023
const datasets = useQuery({
@@ -29,10 +32,22 @@ export function DatasetsPreviewTable({ className }: { className?: string }) {
2932
placeholderData: createPlaceholderDataFnForQueryKey(queryKey),
3033
});
3134

35+
const datasetsArray = datasets.data?.datasets ?? [];
36+
37+
const datasetAddresses = datasetsArray.map((dataset) => dataset.address);
38+
const { schemasMap, isLoading: isSchemasLoading } = useDatasetsSchemas(
39+
datasetAddresses,
40+
chainId!
41+
);
42+
43+
const columns = createColumns(navigateToDatasets);
44+
3245
const formattedData =
3346
datasets.data?.datasets.map((dataset) => ({
3447
...dataset,
3548
destination: `/dataset/${dataset.address}`,
49+
schema: schemasMap.get(dataset.address) || [],
50+
isSchemasLoading,
3651
})) ?? [];
3752

3853
return (
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { datasetSchemaTypeGroups } from '@/config';
2+
import { cn } from '@/lib/utils';
3+
import { SelectLabel } from '@radix-ui/react-select';
4+
import { ChevronDown, Plus, SlidersHorizontal, X } from 'lucide-react';
5+
import { useState } from 'react';
6+
import { Button } from '@/components/ui/button';
7+
import { Input } from '@/components/ui/input';
8+
import {
9+
Select,
10+
SelectContent,
11+
SelectGroup,
12+
SelectItem,
13+
SelectTrigger,
14+
SelectValue,
15+
} from '@/components/ui/select';
16+
import { borderTypeColor } from './borderTypeColor';
17+
import { SchemaFilter } from './schemaFilters';
18+
19+
export interface SchemaSearchProps {
20+
filters: SchemaFilter[];
21+
onAddFilter: (filter: SchemaFilter) => void;
22+
onRemoveFilter: (index: number) => void;
23+
onClearAllFilters?: () => void;
24+
isOpen: boolean;
25+
setIsOpen: (open: boolean) => void;
26+
}
27+
28+
export function SchemaSearch({
29+
filters,
30+
onAddFilter,
31+
onRemoveFilter,
32+
onClearAllFilters,
33+
isOpen,
34+
setIsOpen,
35+
}: SchemaSearchProps) {
36+
const [inputPathValue, setInputPathValue] = useState('');
37+
const [selectedType, setSelectedType] = useState<string>('');
38+
39+
const handleAdd = () => {
40+
if (!inputPathValue.trim() || !selectedType) return;
41+
onAddFilter({ path: inputPathValue.trim(), type: selectedType });
42+
setInputPathValue('');
43+
setSelectedType('');
44+
};
45+
46+
return (
47+
<div className="rounded-2xl border border-[#303038]">
48+
<button
49+
className={cn('flex w-full items-center gap-2 px-10 py-6 duration-300')}
50+
onClick={() => setIsOpen(!isOpen)}
51+
>
52+
<SlidersHorizontal size={16} />
53+
<p className="flex-1 text-left font-bold">
54+
Schema Search{' '}
55+
<span className="text-muted-foreground font-normal">
56+
Add field types to filter datasets by their schema structure.
57+
Filters are applied automatically.
58+
</span>
59+
</p>
60+
<ChevronDown
61+
className={cn(
62+
'ml-auto transition-transform',
63+
isOpen && '-rotate-180'
64+
)}
65+
/>
66+
</button>
67+
<div
68+
className={cn(
69+
'grid transition-all duration-300',
70+
isOpen
71+
? 'translate-y-0 grid-rows-[1fr]'
72+
: 'translate-y-2 grid-rows-[0fr]'
73+
)}
74+
>
75+
<div className={cn('text-grey-200 grid overflow-hidden px-6 md:px-10')}>
76+
<hr className="border-secondary" />
77+
<div
78+
className={cn(
79+
'grid transition-all duration-300',
80+
filters.length > 0
81+
? 'mt-6 translate-y-0 grid-rows-[1fr]'
82+
: 'translate-y-2 grid-rows-[0fr]'
83+
)}
84+
>
85+
<div className="flex flex-wrap items-center gap-2.5 overflow-hidden">
86+
Filter by field types:{' '}
87+
{filters.map((schema, index) => {
88+
const borderColor = borderTypeColor.find((color) =>
89+
color.keywords.some((keyword) =>
90+
schema.type?.toLowerCase().includes(keyword)
91+
)
92+
)?.color;
93+
return (
94+
<span
95+
key={index}
96+
className={cn(
97+
'inline-flex w-fit items-center rounded-full border px-4 py-2 text-xs',
98+
borderColor
99+
)}
100+
>
101+
<span className={cn('inline-block')}>{schema.path}</span>
102+
<span className={cn('inline-block')}>: {schema.type}</span>
103+
<button onClick={() => onRemoveFilter(index)}>
104+
<X className="ml-1 text-white" size={12} />
105+
</button>
106+
</span>
107+
);
108+
})}
109+
{filters.length > 0 && (
110+
<button
111+
className="text-xs text-white"
112+
type="button"
113+
onClick={onClearAllFilters}
114+
>
115+
Clear all
116+
</button>
117+
)}
118+
</div>
119+
</div>
120+
<div className="mt-6 mb-6 flex translate-y-1 flex-col items-center gap-4 sm:flex-row">
121+
<Input
122+
type="text"
123+
id="schema-path"
124+
autoComplete="off"
125+
autoCapitalize="off"
126+
value={inputPathValue}
127+
onChange={(e) => setInputPathValue(e.target.value)}
128+
className={cn(
129+
'bg-muted border-secondary col-span-2 w-full rounded-2xl px-4 py-6 text-sm text-white'
130+
)}
131+
placeholder="Field path (e.g email, telegram_chatId, nested)"
132+
/>
133+
134+
<Select value={selectedType} onValueChange={setSelectedType}>
135+
<SelectTrigger className="bg-muted border-secondary w-full rounded-2xl px-4 py-6 text-sm text-white sm:max-w-1/3">
136+
<SelectValue placeholder="Select type" />
137+
</SelectTrigger>
138+
<SelectContent className="bg-muted border-secondary overflow-visible p-6">
139+
{datasetSchemaTypeGroups.map((group) => (
140+
<SelectGroup
141+
key={group.label}
142+
className="overflow-visible not-first:mt-2"
143+
>
144+
<SelectLabel className="text-grey-300 text-sm">
145+
{group.label}
146+
</SelectLabel>
147+
{group.items.map((type) => (
148+
<SelectItem
149+
key={type.value}
150+
className="text-base font-bold data-[state=checked]:text-yellow-400"
151+
value={type.value}
152+
>
153+
{type.value}
154+
</SelectItem>
155+
))}
156+
</SelectGroup>
157+
))}
158+
</SelectContent>
159+
</Select>
160+
<Button
161+
size="lg"
162+
onClick={handleAdd}
163+
disabled={!inputPathValue.trim() || !selectedType}
164+
>
165+
Add filter
166+
<Plus size={20} />
167+
</Button>
168+
</div>
169+
</div>
170+
</div>
171+
</div>
172+
);
173+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const borderTypeColor = [
2+
{ keywords: ['string'], color: 'border-yellow-200 text-yellow-200' },
3+
{ keywords: ['video'], color: 'border-orange-300 text-orange-300' },
4+
{ keywords: ['bool'], color: 'border-blue-200 text-blue-200' },
5+
{ keywords: ['application'], color: 'border-blue-400 text-blue-400' },
6+
{ keywords: ['audio'], color: 'border-[#A0B1FE] text-[#A0B1FE]' },
7+
{ keywords: ['f64'], color: 'border-green-200 text-green-200' },
8+
{ keywords: ['i128'], color: 'border-purple-200 text-purple-200' },
9+
{ keywords: ['image'], color: 'border-[#F05FC5] text-[#F05FC5]' },
10+
{ keywords: ['number'], color: 'border-[#F693B8] text-[#F693B8]' },
11+
{ keywords: ['boolean'], color: 'border-purple-100 text-purple-100' },
12+
];

0 commit comments

Comments
 (0)