diff --git a/src/pages/AdvancedSearch/CodeSnippetBlock/CodeSnippetBlock.tsx b/src/pages/AdvancedSearch/CodeSnippetBlock/CodeSnippetBlock.tsx index 27a43bd03..d30bcddd1 100644 --- a/src/pages/AdvancedSearch/CodeSnippetBlock/CodeSnippetBlock.tsx +++ b/src/pages/AdvancedSearch/CodeSnippetBlock/CodeSnippetBlock.tsx @@ -2,30 +2,19 @@ import { CodeSnippet, CodeSnippetBlockAppearance, } from "@canonical/react-components"; -import { isValid, parseISO } from "date-fns"; import { Highlight } from "prism-react-renderer"; import Prism from "prismjs/components/prism-core"; -import { useCallback, useState } from "react"; -import type { KeyPath } from "react-json-tree"; -import { - JSONTree, - type LabelRenderer, - type ValueRenderer, -} from "react-json-tree"; +import { useState } from "react"; +import { JSONTree } from "react-json-tree"; import "prismjs/components/prism-json"; -import AppLink from "components/AppLink"; -import MachineLink from "components/MachineLink"; -import Status from "components/Status"; -import UnitLink from "components/UnitLink"; -import { formatFriendlyDateToNow } from "components/utils"; import { type CrossModelQueryResponse } from "juju/jimm/JIMMV4"; -import type { CrossModelQueryState } from "store/juju/types"; -import { ModelTab } from "urls"; -import ResultsModelLink from "../ResultsModelLink"; import { CodeSnippetView } from "../types"; +import Label from "./Label"; +import Value from "./Value"; + type Props = { className: string; title: string; @@ -54,226 +43,11 @@ const THEME = { base0F: DEFAULT_THEME_COLOUR, }; -const getTab = (key: string) => { - switch (key) { - case "applications": - case "offers": - return ModelTab.APPS; - case "machines": - return ModelTab.MACHINES; - case "relations": - return ModelTab.INTEGRATIONS; - default: - return; - } -}; - -// Use a key path to get the parent object that contains the current item from the results data. -const getCurrentObject = ( - keyPath: KeyPath, - results: CrossModelQueryState["results"], -) => - keyPath - // Ignore the first item, as we want to get the parent object. - .slice(1, keyPath.length) - // The first item in the key path is the current parameter and then has - // the parents in ascending order (e.g. [current, parent, grandparent]), - // but we need to follow the path from the outside in. - .reverse() - // This reduce follows the keypath by returning the next child until it - // finally reaches the end. - .reduce((current, key) => { - // Check that the current item in the path is a valid object. - if (current && typeof current === "object" && key in current) { - const inner = current[key as keyof typeof current]; - // Check that the next child is a valid object. - if (typeof inner === "object") { - // Store the next child. - return inner; - } - } - // Handle the case where the key path does not match the object that was provided. - return null; - }, results); - -const labelRenderer: LabelRenderer = (keyPath) => { - const currentKey = keyPath[0]; - const parentKey = keyPath.length >= 2 ? keyPath[1] : null; - // The last item in keyPath should always be the model UUID. - const modelUUID = keyPath[keyPath.length - 1]; - if (!modelUUID || typeof modelUUID !== "string") { - // If this is not a value that can be displayed then display "[none]" instead. - return [none]:; - } - // If this is a top level key then it is a model UUID. - if (keyPath.length === 1) { - return ( - - ); - } - // Link units[unit-key] to unit in model details. - if (parentKey === "units" && typeof currentKey === "string") { - const [appName, unitId] = currentKey.split("/"); - return ( - - {currentKey}: - - ); - } - // Link machines[machine-key] to machine in model details. - if (parentKey === "machines" && typeof currentKey === "string") { - return ( - - {currentKey}: - - ); - } - // Link offers[app-key] and applications[app-key] to app in model details. - if ( - (parentKey === "offers" || parentKey === "applications") && - typeof currentKey === "string" - ) { - return ( - - {currentKey}: - - ); - } - switch (currentKey) { - case "applications": - case "machines": - case "offers": - case "relations": - case "model": - return ( - - {currentKey} - - ); - // Display something when the key is a blank string. - case "": - return [none]:; - default: - return <>{currentKey}:; - } -}; - const CodeSnippetBlock = ({ className, title, code }: Props): JSX.Element => { const [codeSnippetView, setCodeSnippetView] = useState( CodeSnippetView.TREE, ); - const valueRenderer = useCallback( - (valueAsString, value, ...keyPath) => { - const currentKey = keyPath[0]; - const parentKey = keyPath.length >= 2 ? keyPath[1] : null; - const grandparentKey = keyPath.length >= 3 ? keyPath[2] : null; - const modelUUID = keyPath[keyPath.length - 1]; - // Match a status of the following structure: - // "application-status": { - // "current": "unknown", - // ... - // } - if ( - currentKey === "current" && - typeof parentKey === "string" && - parentKey.endsWith("-status") && - typeof value === "string" && - typeof valueAsString === "string" - ) { - return {valueAsString}; - } - // Display date values as tooltip with relative date. - if (typeof value === "string" && isValid(parseISO(value))) { - return ( - <> - {value}{" "} - - ({formatFriendlyDateToNow(value)}) - - - ); - } - // Match charms with the following structure: - // "charm": "mysql", - // "charm-channel": "8.0/stable", - // "charm-name": "mysql", - // "charm-origin": "charmhub", - if (currentKey === "charm" && typeof valueAsString === "string") { - const currentObject = getCurrentObject(keyPath, code); - if ( - currentObject && - "charm-name" in currentObject && - currentObject["charm-name"] && - "charm-origin" in currentObject && - currentObject["charm-origin"] === "charmhub" - ) { - let charmURL = `https://charmhub.io/${currentObject["charm-name"]}`; - if ( - "charm-channel" in currentObject && - currentObject["charm-channel"] - ) { - charmURL += `?channel=${currentObject["charm-channel"]}`; - } - return ( - - {valueAsString} - - ); - } - } - // Link units[unit-key].machine to machine in model details. - if ( - grandparentKey === "units" && - currentKey === "machine" && - typeof valueAsString === "string" && - typeof value === "string" && - typeof modelUUID === "string" - ) { - return ( - - {valueAsString} - - ); - } - // Link relations[key][app-key] and 'subordinate-to'[value] - // to app in model details. - if ( - (grandparentKey === "relations" || parentKey === "subordinate-to") && - typeof valueAsString === "string" && - typeof value === "string" && - typeof modelUUID === "string" - ) { - return ( - - {valueAsString} - - ); - } - // Link ‘application-endpoints’[app-key].url to the app in the - // offer’s model details. - if ( - grandparentKey === "application-endpoints" && - currentKey === "url" && - typeof valueAsString === "string" && - typeof parentKey === "string" && - typeof modelUUID === "string" - ) { - return ( - - {valueAsString} - - ); - } - return <>{valueAsString}; - }, - [code], - ); - return ( { + labelRenderer={(keyPath) =>