Skip to content

Commit 7058711

Browse files
committed
feat: integrate dataset schemas into datasets preview and details, adding schema queries and TypeBadge component
1 parent d4a91f7 commit 7058711

File tree

11 files changed

+404
-83
lines changed

11 files changed

+404
-83
lines changed

src/modules/datasets/DatasetsPreviewTable.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { createPlaceholderDataFnForQueryKey } from '@/utils/createPlaceholderDat
1212
import { ErrorAlert } from '../ErrorAlert';
1313
import { datasetsQuery } from './datasetsQuery';
1414
import { columns } from './datasetsTable/columns';
15+
import { useDatasetsSchemas } from './hooks/useDatasetsSchemas';
1516

1617
export function DatasetsPreviewTable({ className }: { className?: string }) {
1718
const { chainId } = useUserStore();
@@ -29,10 +30,20 @@ export function DatasetsPreviewTable({ className }: { className?: string }) {
2930
placeholderData: createPlaceholderDataFnForQueryKey(queryKey),
3031
});
3132

33+
const datasetsArray = datasets.data?.datasets ?? [];
34+
35+
const datasetAddresses = datasetsArray.map((dataset) => dataset.address);
36+
const { schemasMap, isLoading: isSchemasLoading } = useDatasetsSchemas(
37+
datasetAddresses,
38+
chainId!
39+
);
40+
3241
const formattedData =
3342
datasets.data?.datasets.map((dataset) => ({
3443
...dataset,
3544
destination: `/dataset/${dataset.address}`,
45+
schema: schemasMap.get(dataset.address) || [],
46+
isSchemasLoading,
3647
})) ?? [];
3748

3849
return (

src/modules/datasets/dataset/buildDatasetDetails.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { DatasetQuery } from '@/graphql/graphql';
1+
import { DatasetSchemaQuery } from '@/graphql/dataprotector/graphql';
2+
import { DatasetQuery } from '@/graphql/poco/graphql';
23
import CopyButton from '@/components/CopyButton';
34
import SmartLinkGroup from '@/components/SmartLinkGroup';
45
import TransferEvent from '@/modules/events/TransferEvent';
@@ -7,15 +8,23 @@ import {
78
formatDateCompact,
89
formatElapsedTime,
910
} from '@/utils/formatElapsedTime';
11+
import TypeBadge from './schema/TypeBadge';
1012

1113
export function buildDatasetDetails({
1214
dataset,
15+
schema,
16+
isSchemaLoading,
1317
}: {
1418
dataset: DatasetQuery['dataset'];
19+
schema?: NonNullable<
20+
NonNullable<DatasetSchemaQuery['protectedData']>['schema']
21+
>;
22+
isSchemaLoading: boolean;
1523
}) {
1624
if (!dataset) {
1725
return {};
1826
}
27+
1928
const firstTransfer =
2029
Array.isArray(dataset?.transfers) && dataset?.transfers[0];
2130
const firstTimestamp = firstTransfer?.transaction?.timestamp;
@@ -38,6 +47,17 @@ export function buildDatasetDetails({
3847
<SmartLinkGroup type={'address'} addressOrId={dataset.owner.address} />
3948
),
4049
}),
50+
...(schema && {
51+
Type: (
52+
<TypeBadge
53+
isLoading={isSchemaLoading}
54+
schemaPaths={schema}
55+
maxVisible={Infinity}
56+
direction="horizontal"
57+
overflowHidden={false}
58+
/>
59+
),
60+
}),
4161
...(firstTimestamp && {
4262
Created: (
4363
<p>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { DatasetSchemaQuery } from '@/graphql/dataprotector/graphql';
2+
import { cn } from '@/lib/utils';
3+
import React from 'react';
4+
import { pluralize } from '@/utils/pluralize';
5+
6+
interface TypeBadgeProps {
7+
schemaPaths?: NonNullable<
8+
NonNullable<DatasetSchemaQuery['protectedData']>['schema']
9+
>;
10+
isLoading?: boolean;
11+
maxVisible?: number;
12+
direction?: 'vertical' | 'horizontal';
13+
className?: string;
14+
overflowHidden?: boolean;
15+
}
16+
17+
const borderTypeColor = [
18+
{ keywords: ['string'], color: 'border-yellow-500 text-yellow-500' },
19+
{ keywords: ['video'], color: 'border-orange-300 text-orange-300' },
20+
{ keywords: ['bool'], color: 'border-blue-200 text-blue-200' },
21+
{ keywords: ['application'], color: 'border-blue-400 text-blue-400' },
22+
{ keywords: ['audio'], color: 'border-[#A0B1FE] text-[#A0B1FE]' },
23+
{ keywords: ['f64'], color: 'border-green-200 text-green-200' },
24+
{ keywords: ['i128'], color: 'border-purple-200 text-purple-200' },
25+
{ keywords: ['image'], color: 'border-[#F05FC5] text-[#F05FC5]' },
26+
{ keywords: ['number'], color: 'border-[#F693B8] text-[#F693B8]' },
27+
{ keywords: ['boolean'], color: 'border-purple-100 text-purple-100' },
28+
];
29+
30+
const TypeBadge: React.FC<TypeBadgeProps> = ({
31+
schemaPaths,
32+
isLoading,
33+
maxVisible = 2,
34+
direction = 'vertical',
35+
className,
36+
overflowHidden = true,
37+
}) => {
38+
if (isLoading && schemaPaths && schemaPaths.length === 0) {
39+
return (
40+
<span className="border-muted-foreground text-muted-foreground rounded-full border px-4 py-2 text-xs">
41+
Loading...
42+
</span>
43+
);
44+
}
45+
if (!schemaPaths || schemaPaths.length === 0) {
46+
return <span className="text-muted-foreground">No type</span>;
47+
}
48+
const visibleItems = schemaPaths.slice(0, maxVisible);
49+
const hiddenCount = schemaPaths.length - visibleItems.length;
50+
51+
return (
52+
<div
53+
className={cn(
54+
`flex`,
55+
direction === 'vertical'
56+
? 'flex-col gap-1'
57+
: 'flex-row flex-wrap gap-4',
58+
className
59+
)}
60+
>
61+
{visibleItems.map((schema, index) => {
62+
const borderColor = borderTypeColor.find((color) =>
63+
color.keywords.some((keyword) =>
64+
schema.type?.toLowerCase().includes(keyword)
65+
)
66+
)?.color;
67+
return (
68+
<span
69+
key={index}
70+
className={cn(
71+
'inline-flex w-fit rounded-full border px-4 py-2 text-xs',
72+
borderColor,
73+
overflowHidden && 'max-w-36'
74+
)}
75+
title={
76+
overflowHidden ? `${schema.path}: ${schema.type}` : undefined
77+
}
78+
>
79+
<span
80+
className={cn(
81+
'inline-block',
82+
overflowHidden &&
83+
'max-w-18 truncate overflow-hidden text-ellipsis'
84+
)}
85+
>
86+
{schema.path}
87+
</span>
88+
<span
89+
className={cn(
90+
'inline-block min-w-0 flex-1',
91+
overflowHidden && 'truncate overflow-hidden text-ellipsis'
92+
)}
93+
>
94+
: {schema.type}
95+
</span>
96+
</span>
97+
);
98+
})}
99+
{hiddenCount > 0 && (
100+
<span className="w-fit text-xs text-gray-500 hover:bg-gray-100">
101+
+{pluralize(hiddenCount, 'other')}
102+
</span>
103+
)}
104+
</div>
105+
);
106+
};
107+
108+
export default TypeBadge;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { graphql } from '@/graphql/dataprotector/gql';
2+
3+
export const datasetSchemaQuery = graphql(`
4+
query DatasetSchema($datasetAddress: ID!) {
5+
protectedData(id: $datasetAddress) {
6+
schema {
7+
path
8+
type
9+
}
10+
}
11+
}
12+
`);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { graphql } from '@/graphql/dataprotector/gql';
2+
3+
export const datasetsSchemaQuery = graphql(`
4+
query datasetsSchema($datasetIds: [Bytes!]!) {
5+
protectedDatas(where: { id_in: $datasetIds }) {
6+
id
7+
schema {
8+
path
9+
type
10+
}
11+
}
12+
}
13+
`);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { graphql } from '@/graphql/dataprotector/gql';
2+
3+
// Query pour la recherche par schéma avec pagination optimisée et filtrage côté serveur
4+
export const schemaSearchPaginatedQuery = graphql(`
5+
query SchemaSearchPaginated(
6+
$length: Int = 20
7+
$skip: Int = 0
8+
$nextSkip: Int = 20
9+
$nextNextSkip: Int = 40
10+
$requiredSchema: [String!]
11+
) {
12+
protectedDatas(
13+
where: { transactionHash_not: "0x", schema_contains: $requiredSchema }
14+
skip: $skip
15+
first: $length
16+
orderBy: creationTimestamp
17+
orderDirection: desc
18+
) {
19+
id
20+
name
21+
creationTimestamp
22+
owner {
23+
id
24+
}
25+
schema {
26+
path
27+
type
28+
}
29+
}
30+
protectedDatasHasNext: protectedDatas(
31+
where: { transactionHash_not: "0x", schema_contains: $requiredSchema }
32+
first: 1
33+
skip: $nextSkip
34+
orderBy: creationTimestamp
35+
orderDirection: desc
36+
) {
37+
id
38+
}
39+
protectedDatasHasNextNext: protectedDatas(
40+
where: { transactionHash_not: "0x", schema_contains: $requiredSchema }
41+
first: 1
42+
skip: $nextNextSkip
43+
orderBy: creationTimestamp
44+
orderDirection: desc
45+
) {
46+
id
47+
}
48+
}
49+
`);

src/modules/datasets/datasetsTable/TypeBadge.tsx

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/modules/datasets/datasetsTable/columns.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { ColumnDef } from '@tanstack/react-table';
33
import CopyButton from '@/components/CopyButton';
44
import { formatElapsedTime } from '@/utils/formatElapsedTime';
55
import { truncateAddress } from '@/utils/truncateAddress';
6-
import TypeBadge from './TypeBadge';
7-
8-
type Dataset = DatasetsQuery['datasets'][number];
6+
import TypeBadge from '../dataset/schema/TypeBadge';
97

8+
type Dataset = DatasetsQuery['datasets'][number] & {
9+
schema?: Array<{ path?: string | null; type?: string | null }>;
10+
isSchemasLoading?: boolean;
11+
};
1012
export const columns: ColumnDef<Dataset>[] = [
1113
{
1214
accessorKey: 'datasetAddress',
@@ -42,17 +44,14 @@ export const columns: ColumnDef<Dataset>[] = [
4244
},
4345
},
4446
{
45-
accessorKey: 'datasetName',
47+
accessorKey: 'datasetSchema',
4648
header: 'Type',
4749
cell: ({ row }) => {
48-
// const datasetType = row.original.type;
49-
const datasetType = [
50-
{ name: 'Email', type: 'string' },
51-
{ name: 'Photo', type: 'image' },
52-
{ name: 'Agent IA', type: 'bool' },
53-
{ name: 'Credit score', type: 'f64' },
54-
]; // Temporary until TheGraph supports dataset type
55-
return <TypeBadge datasetType={datasetType} />;
50+
const datasetSchema = row.original.schema;
51+
const isSchemasLoading = row.original.isSchemasLoading;
52+
return (
53+
<TypeBadge isLoading={isSchemasLoading} schemaPaths={datasetSchema} />
54+
);
5655
},
5756
},
5857
{

0 commit comments

Comments
 (0)