• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cartesi / rollups-explorer / 10138892735

29 Jul 2024 05:56AM UTC coverage: 92.455% (-0.9%) from 93.346%
10138892735

Pull #213

github

brunomenezes
feat: Integrate decoding spec + input-details-view. Improve error handling for Content component.
Pull Request #213: Feature/165 add decode specification v1

897 of 1067 branches covered (84.07%)

Branch coverage included in aggregate %.

1137 of 1303 new or added lines in 21 files covered. (87.26%)

2 existing lines in 1 file now uncovered.

10168 of 10901 relevant lines covered (93.28%)

48.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.65
/apps/web/src/components/inputs/inputDetailsView.tsx
1
"use client";
1✔
2
import { InputDetails } from "@cartesi/rollups-explorer-ui";
1✔
3
import { Box, Group, Select, Stack, Text } from "@mantine/core";
1✔
4
import { find, omit, pathOr } from "ramda";
1✔
5
import { isNilOrEmpty, isNotNilOrEmpty } from "ramda-adjunct";
1✔
6
import { FC, useEffect, useState } from "react";
1✔
7
import { useQuery } from "urql";
1✔
8
import { Address, Hex } from "viem";
1✔
9
import { InputItemFragment } from "../../graphql/explorer/operations";
1✔
10
import {
1✔
11
    InputDetailsDocument,
1✔
12
    InputDetailsQuery,
1✔
13
    InputDetailsQueryVariables,
1✔
14
} from "../../graphql/rollups/operations";
1✔
15
import { Voucher } from "../../graphql/rollups/types";
1✔
16
import { useConnectionConfig } from "../../providers/connectionConfig/hooks";
1✔
17
import { NewSpecificationButton } from "../specification/components/NewSpecificationButton";
1✔
18
import { findSpecificationFor } from "../specification/conditionals";
1✔
19
import { Envelope, decodePayload } from "../specification/decoder";
1✔
20
import { useSpecification } from "../specification/hooks/useSpecification";
1✔
21
import { useSystemSpecifications } from "../specification/hooks/useSystemSpecifications";
1✔
22
import { Specification } from "../specification/types";
1✔
23
import { stringifyContent } from "../specification/utils";
1✔
24
import VoucherExecution from "./voucherExecution";
1✔
25

1✔
26
interface ApplicationInputDataProps {
1✔
27
    input: InputItemFragment;
1✔
28
}
1✔
29

1✔
30
type InputTypes = "vouchers" | "reports" | "notices";
1✔
31

1✔
32
const payloadOrString = pathOr("", ["edges", 0, "node", "payload"]);
1✔
33

1✔
34
const updateForNextPage = (
1✔
35
    name: InputTypes,
3✔
36
    obj: InputDetailsQueryVariables,
3✔
37
) => {
3✔
38
    switch (name) {
3✔
39
        case "vouchers":
3✔
40
            return omit(["lastVouchers", "vouchersPrevPage"], obj);
1✔
41
        case "notices":
3✔
42
            return omit(["lastNotices", "noticesPrevPage"], obj);
1✔
43
        case "reports":
3✔
44
            return omit(["lastReports", "reportsPrevPage"], obj);
1✔
45
        default:
3!
UNCOV
46
            throw new Error(`${name} not supported.`);
×
47
    }
3✔
48
};
3✔
49

1✔
50
const updateForPrevPage = (
1✔
51
    name: InputTypes,
3✔
52
    obj: InputDetailsQueryVariables,
3✔
53
) => {
3✔
54
    switch (name) {
3✔
55
        case "vouchers":
3✔
56
            return omit(["firstVouchers", "vouchersNextPage"], obj);
1✔
57
        case "notices":
3✔
58
            return omit(["firstNotices", "noticesNextPage"], obj);
1✔
59
        case "reports":
3✔
60
            return omit(["firstReports", "reportsNextPage"], obj);
1✔
61
        default:
3!
UNCOV
62
            throw new Error(`${name} not supported.`);
×
63
    }
3✔
64
};
3✔
65

1✔
66
const getStringifiedDecodedValueOrPayload = (
1✔
67
    envelope: Envelope | undefined,
33✔
68
    originalPayload: string,
33✔
69
) => {
33✔
70
    if (envelope?.error) {
33!
NEW
71
        console.error(envelope.error);
×
NEW
72
        return originalPayload;
×
NEW
73
    }
×
74

33✔
75
    if (!envelope || isNilOrEmpty(envelope?.result)) {
33!
76
        return originalPayload;
33✔
77
    }
33!
NEW
78

×
NEW
79
    return stringifyContent(envelope.result);
×
NEW
80
};
×
81

1✔
82
type UseDecodingOnInputResult = [
1✔
83
    string,
1✔
84
    {
1✔
85
        specApplied: Specification | null;
1✔
86
        userSpecifications: Specification[];
1✔
87
        systemSpecifications: Specification[];
1✔
88
        error?: Error;
1✔
89
    },
1✔
90
];
1✔
91

1✔
92
/**
1✔
93
 * Receive the input from a graphQL call and
1✔
94
 * It may find a specification that matches a defined condition, therefore decoding the content.
1✔
95
 * If an specification is not found it returns the original payload.
1✔
96
 * @param input Input data returned by graphQL.
1✔
97
 * @param specName Specification name to apply instead of check for matching conditions. (optional)
1✔
98
 * @returns { UseDecodingOnInputResult }
1✔
99
 */
1✔
100
const useDecodingOnInput = (
1✔
101
    input: InputItemFragment,
33✔
102
    specName?: string,
33✔
103
): UseDecodingOnInputResult => {
33✔
104
    const { listSpecifications } = useSpecification();
33✔
105
    const { systemSpecificationAsList } = useSystemSpecifications();
33✔
106

33✔
107
    const userSpecifications = listSpecifications() ?? [];
33✔
108
    const specifications = [
33✔
109
        ...systemSpecificationAsList,
33✔
110
        ...userSpecifications,
33✔
111
    ];
33✔
112

33✔
113
    const specification = isNilOrEmpty(specName)
33✔
114
        ? findSpecificationFor(input, specifications)
33!
NEW
115
        : find((spec) => spec.name === specName, specifications) ?? null;
×
116

33✔
117
    const envelope = specification
33!
NEW
118
        ? decodePayload(specification, input.payload as Hex)
×
119
        : undefined;
33✔
120

33✔
121
    const payload = getStringifiedDecodedValueOrPayload(
33✔
122
        envelope,
33✔
123
        input.payload,
33✔
124
    );
33✔
125

33✔
126
    return [
33✔
127
        payload,
33✔
128
        {
33✔
129
            specApplied: specification,
33✔
130
            systemSpecifications: systemSpecificationAsList,
33✔
131
            userSpecifications,
33✔
132
            error: envelope?.error,
33!
133
        },
33✔
134
    ];
33✔
135
};
33✔
136

1✔
137
const buildSelectData = (
1✔
138
    userSpecifications: Specification[],
33✔
139
    systemSpecifications: Specification[],
33✔
140
) => {
33✔
141
    const groups = [];
33✔
142

33✔
143
    if (userSpecifications.length) {
33!
NEW
144
        groups.push({
×
NEW
145
            group: "Your Specs",
×
NEW
146
            items: userSpecifications.map((spec) => spec.name),
×
NEW
147
        });
×
NEW
148
    }
×
149

33✔
150
    if (systemSpecifications.length) {
33✔
151
        groups.push({
33✔
152
            group: "Sys Specs",
33✔
153
            items: systemSpecifications.map((spec) => spec.name),
33✔
154
        });
33✔
155
    }
33✔
156

33✔
157
    return groups;
33✔
158
};
33✔
159

1✔
160
/**
1✔
161
 * InputDetailsView should be lazy rendered.
1✔
162
 * to avoid multiple eager network calls.
1✔
163
 */
1✔
164
const InputDetailsView: FC<ApplicationInputDataProps> = ({ input }) => {
1✔
165
    const { getConnection, hasConnection, showConnectionModal } =
34✔
166
        useConnectionConfig();
34✔
167
    const appId = input.application.id as Address;
34✔
168
    const inputIdx = input.index;
34✔
169
    const connection = getConnection(appId);
34✔
170
    const [selectedSpec, setSelectedSpec] = useState<string>("");
34✔
171
    const [variables, updateQueryVars] = useState<InputDetailsQueryVariables>({
34✔
172
        firstNotices: 1,
34✔
173
        firstReports: 1,
34✔
174
        firstVouchers: 1,
34✔
175
        inputIdx,
34✔
176
    });
34✔
177

34✔
178
    const [result, execQuery] = useQuery<
34✔
179
        InputDetailsQuery,
34✔
180
        InputDetailsQueryVariables
34✔
181
    >({
34✔
182
        query: InputDetailsDocument,
34✔
183
        pause: true,
34✔
184
        variables,
34✔
185
    });
34✔
186

34✔
187
    const reports = result.data?.input.reports;
34✔
188
    const notices = result.data?.input.notices;
34✔
189
    const vouchers = result.data?.input.vouchers;
34✔
190

34✔
191
    const showNotices =
34✔
192
        !connection ||
34✔
193
        (connection && notices && payloadOrString(notices) !== "");
19✔
194
    const showReports =
34✔
195
        !connection ||
34✔
196
        (connection && reports && payloadOrString(reports) !== "");
19✔
197
    const showVouchers =
34✔
198
        !connection ||
34✔
199
        (connection && vouchers && payloadOrString(vouchers) !== "");
19✔
200
    const vouchersForExecution = (vouchers?.edges?.map((e) => e.node) ??
34✔
201
        []) as Partial<Voucher>[];
14✔
202
    const showVoucherForExecution =
34✔
203
        showVouchers && vouchersForExecution.length > 0;
34✔
204

34✔
205
    const [
34✔
206
        inputContent,
34✔
207
        { specApplied, error, systemSpecifications, userSpecifications },
34✔
208
    ] = useDecodingOnInput(input, selectedSpec);
34✔
209

34✔
210
    const selectData = buildSelectData(
34✔
211
        userSpecifications,
34✔
212
        systemSpecifications,
34✔
213
    );
34✔
214

34✔
215
    useEffect(() => {
34✔
216
        if (connection) execQuery({ url: connection.url });
21✔
217
    }, [connection, execQuery, variables]);
34✔
218

34✔
219
    useEffect(() => {
34✔
220
        if (specApplied !== null) {
13!
NEW
221
            setSelectedSpec(specApplied.name);
×
NEW
222
        }
×
223
    }, [specApplied]);
34✔
224

34✔
225
    const hasUserSpecifications = isNotNilOrEmpty(userSpecifications);
34✔
226

34✔
227
    return (
34✔
228
        <Box py="md">
34✔
229
            <InputDetails>
34✔
230
                <InputDetails.InputContent
34✔
231
                    content={inputContent}
34✔
232
                    contentType="raw"
34✔
233
                >
34✔
234
                    <Stack gap="sm">
34✔
235
                        <Group>
34✔
236
                            <Select
34✔
237
                                label="Decode Specification"
34✔
238
                                description="When a specification condition(s) match(es), it will be auto-selected."
34✔
239
                                placeholder="Decode content with..."
34✔
240
                                value={selectedSpec ?? specApplied}
34!
241
                                size="md"
34✔
242
                                checkIconPosition="right"
34✔
243
                                data={selectData}
34✔
244
                                onChange={(value) => {
34✔
NEW
245
                                    setSelectedSpec(value ?? "");
×
NEW
246
                                }}
×
247
                                error={
34✔
248
                                    error
34!
NEW
249
                                        ? `We're not able to decode using ${selectedSpec}`
✔
250
                                        : null
33✔
251
                                }
34✔
252
                            />
34✔
253
                        </Group>
34✔
254
                        {!hasUserSpecifications ? (
34✔
255
                            <Group gap={3}>
33✔
256
                                <Text c="dimmed">
33✔
257
                                    {`You do not have a decoding specification!`}
33✔
258
                                </Text>
33✔
259
                                <NewSpecificationButton
33✔
260
                                    p={0}
33✔
261
                                    variant="transparent"
33✔
262
                                    btnText="Create one"
33✔
263
                                />
33✔
264
                            </Group>
33!
NEW
265
                        ) : null}
×
266
                    </Stack>
34✔
267
                </InputDetails.InputContent>
34✔
268

34✔
269
                {showReports && (
34✔
270
                    <InputDetails.ReportContent
33✔
271
                        content={payloadOrString(reports)}
33✔
272
                        contentType="raw"
33✔
273
                        onConnect={() => showConnectionModal(appId)}
33✔
274
                        isLoading={result.fetching}
33✔
275
                        isConnected={hasConnection(appId)}
33✔
276
                        paging={{
33✔
277
                            total: reports?.totalCount ?? 0,
33✔
278
                            onNextPage: () => {
33✔
279
                                updateQueryVars((vars) => {
1✔
280
                                    const newVars = {
1✔
281
                                        ...vars,
1✔
282
                                        firstReports: 1,
1✔
283
                                        reportsNextPage:
1✔
284
                                            reports?.pageInfo.endCursor,
1✔
285
                                    };
1✔
286

1✔
287
                                    return updateForNextPage(
1✔
288
                                        "reports",
1✔
289
                                        newVars,
1✔
290
                                    );
1✔
291
                                });
1✔
292
                            },
1✔
293
                            onPreviousPage: () => {
33✔
294
                                updateQueryVars((vars) => {
1✔
295
                                    const newVars = {
1✔
296
                                        ...vars,
1✔
297
                                        lastReports: 1,
1✔
298
                                        reportsPrevPage:
1✔
299
                                            reports?.pageInfo.startCursor,
1✔
300
                                    };
1✔
301
                                    return updateForPrevPage(
1✔
302
                                        "reports",
1✔
303
                                        newVars,
1✔
304
                                    );
1✔
305
                                });
1✔
306
                            },
1✔
307
                        }}
33✔
308
                    />
33✔
309
                )}
34✔
310

34✔
311
                {showNotices && (
34✔
312
                    <InputDetails.NoticeContent
28✔
313
                        content={payloadOrString(notices)}
28✔
314
                        contentType="raw"
28✔
315
                        onConnect={() => showConnectionModal(appId)}
28✔
316
                        isLoading={result.fetching}
28✔
317
                        isConnected={hasConnection(appId)}
28✔
318
                        paging={{
28✔
319
                            total: notices?.totalCount ?? 0,
28✔
320
                            onNextPage: () => {
28✔
321
                                updateQueryVars((vars) => {
1✔
322
                                    const newVars = {
1✔
323
                                        ...vars,
1✔
324
                                        firstNotices: 1,
1✔
325
                                        noticesNextPage:
1✔
326
                                            notices?.pageInfo.endCursor,
1✔
327
                                    };
1✔
328
                                    return updateForNextPage(
1✔
329
                                        "notices",
1✔
330
                                        newVars,
1✔
331
                                    );
1✔
332
                                });
1✔
333
                            },
1✔
334
                            onPreviousPage: () => {
28✔
335
                                updateQueryVars((vars) => {
1✔
336
                                    const newVars = {
1✔
337
                                        ...vars,
1✔
338
                                        lastNotices: 1,
1✔
339
                                        noticesPrevPage:
1✔
340
                                            notices?.pageInfo.startCursor,
1✔
341
                                    };
1✔
342

1✔
343
                                    return updateForPrevPage(
1✔
344
                                        "notices",
1✔
345
                                        newVars,
1✔
346
                                    );
1✔
347
                                });
1✔
348
                            },
1✔
349
                        }}
28✔
350
                    />
28✔
351
                )}
34✔
352

34✔
353
                {showVouchers && (
34✔
354
                    <InputDetails.VoucherContent
28✔
355
                        content={payloadOrString(vouchers)}
28✔
356
                        contentType="raw"
28✔
357
                        onConnect={() => showConnectionModal(appId)}
28✔
358
                        isLoading={result.fetching}
28✔
359
                        isConnected={hasConnection(appId)}
28✔
360
                        paging={{
28✔
361
                            total: vouchers?.totalCount ?? 0,
28✔
362
                            onNextPage: () => {
28✔
363
                                updateQueryVars((vars) => {
1✔
364
                                    const newVars = {
1✔
365
                                        ...vars,
1✔
366
                                        firstVouchers: 1,
1✔
367
                                        vouchersNextPage:
1✔
368
                                            vouchers?.pageInfo.endCursor,
1✔
369
                                    };
1✔
370

1✔
371
                                    return updateForNextPage(
1✔
372
                                        "vouchers",
1✔
373
                                        newVars,
1✔
374
                                    );
1✔
375
                                });
1✔
376
                            },
1✔
377
                            onPreviousPage: () => {
28✔
378
                                updateQueryVars((vars) => {
1✔
379
                                    const newVars = {
1✔
380
                                        ...vars,
1✔
381
                                        lastVouchers: 1,
1✔
382
                                        vouchersPrevPage:
1✔
383
                                            vouchers?.pageInfo.startCursor,
1✔
384
                                    };
1✔
385

1✔
386
                                    return updateForPrevPage(
1✔
387
                                        "vouchers",
1✔
388
                                        newVars,
1✔
389
                                    );
1✔
390
                                });
1✔
391
                            },
1✔
392
                        }}
28✔
393
                    >
28✔
394
                        {showVoucherForExecution ? (
28✔
395
                            <VoucherExecution
14✔
396
                                appId={appId}
14✔
397
                                voucher={vouchersForExecution[0]}
14✔
398
                            />
14✔
399
                        ) : null}
14✔
400
                    </InputDetails.VoucherContent>
28✔
401
                )}
34✔
402
            </InputDetails>
34✔
403
        </Box>
34✔
404
    );
34✔
405
};
34✔
406

1✔
407
export default InputDetailsView;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc