From d95c45bbd6116bf6b7be6586cb68f2091dbe837c Mon Sep 17 00:00:00 2001 From: Oleksandr Raspopov Date: Thu, 26 Dec 2024 16:28:11 +0100 Subject: [PATCH] chore: update schema details ui for select a default display method --- ui/src/adapters/api/schemas.ts | 37 ++++- ui/src/components/schemas/SchemaDetails.tsx | 90 ++++++++++-- ui/src/components/schemas/SchemaViewer.tsx | 151 ++++++++++++-------- ui/src/domain/schema.ts | 1 + 4 files changed, 208 insertions(+), 71 deletions(-) diff --git a/ui/src/adapters/api/schemas.ts b/ui/src/adapters/api/schemas.ts index 1608ba476..51328a9af 100644 --- a/ui/src/adapters/api/schemas.ts +++ b/ui/src/adapters/api/schemas.ts @@ -2,7 +2,7 @@ import axios from "axios"; import { z } from "zod"; import { Response, buildErrorResponse, buildSuccessResponse } from "src/adapters"; -import { ID, IDParser, buildAuthorizationHeader } from "src/adapters/api"; +import { ID, IDParser, Message, buildAuthorizationHeader, messageParser } from "src/adapters/api"; import { datetimeParser, getListParser, getStrictParser } from "src/adapters/parsers"; import { ApiSchema, Env, JsonLdType } from "src/domain"; import { getStorageByKey } from "src/utils/browser"; @@ -18,6 +18,7 @@ const apiSchemaParser = getStrictParser()( bigInt: z.string(), createdAt: datetimeParser, description: z.string().nullable(), + displayMethodID: z.string().nullable(), hash: z.string(), id: z.string(), title: z.string().nullable(), @@ -75,7 +76,7 @@ export async function getApiSchema({ env: Env; identifier: string; schemaID: string; - signal: AbortSignal; + signal?: AbortSignal; }): Promise> { try { const response = await axios({ @@ -132,6 +133,38 @@ export async function getApiSchemas({ } } +export type UpdateSchema = { + displayMethodID: string | null; +}; + +export async function updateSchema({ + env, + identifier, + payload, + schemaID, +}: { + env: Env; + identifier: string; + payload: UpdateSchema; + schemaID: string; +}): Promise> { + try { + const response = await axios({ + baseURL: env.api.url, + data: payload, + headers: { + Authorization: buildAuthorizationHeader(env), + }, + method: "PATCH", + url: `${API_VERSION}/identities/${identifier}/schemas/${schemaID}`, + }); + + return buildSuccessResponse(messageParser.parse(response.data)); + } catch (error) { + return buildErrorResponse(error); + } +} + export const getIPFSGatewayUrl = (env: Env, ipfsUrl: string): Response => { const ipfsGatewayUrl = getStorageByKey({ defaultValue: env.ipfsGatewayUrl, diff --git a/ui/src/components/schemas/SchemaDetails.tsx b/ui/src/components/schemas/SchemaDetails.tsx index 5f32ddbd2..8c4c411d1 100644 --- a/ui/src/components/schemas/SchemaDetails.tsx +++ b/ui/src/components/schemas/SchemaDetails.tsx @@ -1,13 +1,15 @@ -import { Button, Card, Space, Typography } from "antd"; +import { App, Button, Card, Space, Typography } from "antd"; import { useCallback, useEffect, useState } from "react"; import { generatePath, useNavigate, useParams } from "react-router-dom"; +import { getDisplayMethods } from "src/adapters/api/display-method"; -import { getApiSchema, processUrl } from "src/adapters/api/schemas"; +import { UpdateSchema, getApiSchema, processUrl, updateSchema } from "src/adapters/api/schemas"; import { getJsonSchemaFromUrl, getSchemaJsonLdTypes } from "src/adapters/jsonSchemas"; import { buildAppError, jsonLdContextErrorToString, jsonSchemaErrorToString, + notifyErrors, } from "src/adapters/parsers"; import CreditCardIcon from "src/assets/icons/credit-card-plus.svg?react"; import { DownloadSchema } from "src/components/schemas/DownloadSchema"; @@ -18,9 +20,14 @@ import { LoadingResult } from "src/components/shared/LoadingResult"; import { SiderLayoutContent } from "src/components/shared/SiderLayoutContent"; import { useEnvContext } from "src/contexts/Env"; import { useIdentityContext } from "src/contexts/Identity"; -import { ApiSchema, AppError, Json, JsonLdType, JsonSchema } from "src/domain"; +import { ApiSchema, AppError, DisplayMethod, Json, JsonLdType, JsonSchema } from "src/domain"; import { ROUTES } from "src/routes"; -import { AsyncTask, hasAsyncTaskFailed, isAsyncTaskStarting } from "src/utils/async"; +import { + AsyncTask, + hasAsyncTaskFailed, + isAsyncTaskDataAvailable, + isAsyncTaskStarting, +} from "src/utils/async"; import { isAbortedError, makeRequestAbortable } from "src/utils/browser"; import { SCHEMA_SEARCH_PARAM } from "src/utils/constants"; import { formatDate } from "src/utils/forms"; @@ -29,7 +36,7 @@ export function SchemaDetails() { const { identifier } = useIdentityContext(); const navigate = useNavigate(); const { schemaID } = useParams(); - + const { message } = App.useApp(); const env = useEnvContext(); const [jsonSchemaTuple, setJsonSchemaTuple] = useState>({ @@ -42,6 +49,10 @@ export function SchemaDetails() { status: "pending", }); + const [displayMethods, setDisplayMethods] = useState>({ + status: "pending", + }); + const fetchJsonSchemaFromUrl = useCallback( (schema: ApiSchema): void => { setJsonSchemaTuple({ status: "loading" }); @@ -90,8 +101,34 @@ export function SchemaDetails() { [env] ); + const fetchDisplayMethods = useCallback(async () => { + setDisplayMethods((previousDisplayMethods) => + isAsyncTaskDataAvailable(previousDisplayMethods) + ? { data: previousDisplayMethods.data, status: "reloading" } + : { status: "loading" } + ); + + const response = await getDisplayMethods({ + env, + identifier, + params: {}, + }); + if (response.success) { + setDisplayMethods({ + data: response.data.items.successful, + status: "successful", + }); + + void notifyErrors(response.data.items.failed); + } else { + if (!isAbortedError(response.error)) { + setDisplayMethods({ error: response.error, status: "failed" }); + } + } + }, [env, identifier]); + const fetchApiSchema = useCallback( - async (signal: AbortSignal) => { + async (signal?: AbortSignal) => { if (schemaID) { setSchema({ status: "loading" }); @@ -105,6 +142,7 @@ export function SchemaDetails() { if (response.success) { setSchema({ data: response.data, status: "successful" }); fetchJsonSchemaFromUrl(response.data); + void fetchDisplayMethods(); } else { if (!isAbortedError(response.error)) { setSchema({ error: response.error, status: "failed" }); @@ -112,7 +150,7 @@ export function SchemaDetails() { } } }, - [env, fetchJsonSchemaFromUrl, schemaID, identifier] + [env, fetchJsonSchemaFromUrl, schemaID, identifier, fetchDisplayMethods] ); useEffect(() => { @@ -126,12 +164,30 @@ export function SchemaDetails() { const loading = isAsyncTaskStarting(schema) || isAsyncTaskStarting(jsonSchemaTuple) || - isAsyncTaskStarting(contextTuple); + isAsyncTaskStarting(contextTuple) || + isAsyncTaskStarting(displayMethods); if (!schemaID) { return ; } + const handleEdit = (formValues: UpdateSchema) => { + void updateSchema({ + env, + identifier, + payload: formValues, + schemaID, + }).then((response) => { + if (response.success) { + void fetchApiSchema().then(() => { + void message.success("Schema edited successfully"); + }); + } else { + void message.error(response.error.message); + } + }); + }; + return ( ); } else { - const { bigInt, createdAt, hash, url, version } = schema.data; + const { bigInt, createdAt, displayMethodID, hash, url, version } = schema.data; const processedSchemaUrl = processUrl(url, env); const [jsonSchema, jsonSchemaObject] = jsonSchemaTuple.data; const [jsonLdType, jsonLdContextObject] = contextTuple.data; + const displayMethodsList = isAsyncTaskDataAvailable(displayMethods) + ? displayMethods.data + : []; + const displayMethod = displayMethodsList.find(({ id }) => id === displayMethodID); return ( + {displayMethod && ( + + )} + } + displayMethodID={displayMethodID} + displayMethods={displayMethodsList} jsonLdContextObject={jsonLdContextObject} jsonLdType={jsonLdType} jsonSchema={jsonSchema} jsonSchemaObject={jsonSchemaObject} + onEdit={handleEdit} /> ); } diff --git a/ui/src/components/schemas/SchemaViewer.tsx b/ui/src/components/schemas/SchemaViewer.tsx index 8b18319ae..8f1dac835 100644 --- a/ui/src/components/schemas/SchemaViewer.tsx +++ b/ui/src/components/schemas/SchemaViewer.tsx @@ -1,10 +1,11 @@ -import { Button, Card, Dropdown, Row, Space, Typography } from "antd"; +import { Button, Card, Dropdown, Flex, Form, Row, Select, Space, Typography } from "antd"; import { ReactNode, useState } from "react"; +import { UpdateSchema } from "src/adapters/api/schemas"; import ChevronDownIcon from "src/assets/icons/chevron-down.svg?react"; import { JSONHighlighter } from "src/components/schemas/JSONHighlighter"; import { SchemaTree } from "src/components/schemas/SchemaTree"; -import { Json, JsonLdType, JsonSchema } from "src/domain"; +import { DisplayMethod, Json, JsonLdType, JsonSchema } from "src/domain"; type JsonView = "formatted" | "jsonLdContext" | "jsonSchema"; @@ -17,10 +18,13 @@ const JSON_VIEW_LABELS: Record = { export function SchemaViewer({ actions, contents, + displayMethodID, + displayMethods, jsonLdContextObject, jsonLdType, jsonSchema, jsonSchemaObject, + onEdit, }: { actions: ReactNode; contents: ReactNode; @@ -28,74 +32,101 @@ export function SchemaViewer({ jsonLdType: JsonLdType; jsonSchema: JsonSchema; jsonSchemaObject: Json; -}) { +} & ( + | { displayMethodID?: never; displayMethods?: undefined; onEdit?: never } + | { + displayMethodID: string | null; + displayMethods: DisplayMethod[]; + onEdit: (formValues: UpdateSchema) => void; + } +)) { + const [form] = Form.useForm(); const [jsonView, setJsonView] = useState("formatted"); + const { schema: { description, title }, } = jsonSchema; return ( - { - setJsonView("formatted"); - }, - }, - { - key: "jsonLdContext", - label: JSON_VIEW_LABELS["jsonLdContext"], - onClick: () => { - setJsonView("jsonLdContext"); - }, - }, - { - key: "jsonSchema", - label: JSON_VIEW_LABELS["jsonSchema"], - onClick: () => { - setJsonView("jsonSchema"); - }, - }, - ], - }} - > - - - } - title={title || jsonLdType.name} - > + {contents} - {(() => { - switch (jsonView) { - case "formatted": { - return ( - - - ATTRIBUTES + {displayMethods?.length && ( +
{ + onEdit(formValues); + }} + > + + + +
+ )} - -
-
- ); - } - case "jsonLdContext": { - return ; - } - case "jsonSchema": { - return ; - } - } - })()} + + + + ATTRIBUTES + { + setJsonView("formatted"); + }, + }, + { + key: "jsonLdContext", + label: JSON_VIEW_LABELS["jsonLdContext"], + onClick: () => { + setJsonView("jsonLdContext"); + }, + }, + { + key: "jsonSchema", + label: JSON_VIEW_LABELS["jsonSchema"], + onClick: () => { + setJsonView("jsonSchema"); + }, + }, + ], + }} + > + + + + {(() => { + switch (jsonView) { + case "formatted": { + return ; + } + case "jsonLdContext": { + return ; + } + case "jsonSchema": { + return ; + } + } + })()} + + {actions}
diff --git a/ui/src/domain/schema.ts b/ui/src/domain/schema.ts index c918131b1..9cb39aaea 100644 --- a/ui/src/domain/schema.ts +++ b/ui/src/domain/schema.ts @@ -2,6 +2,7 @@ export type Schema = { bigInt: string; createdAt: Date; description: string | null; + displayMethodID: string | null; hash: string; id: string; title: string | null;