From 35aea6370943d3a45d730af6e0e604d9b1758482 Mon Sep 17 00:00:00 2001 From: Adrien Delannoy Date: Thu, 22 Aug 2024 11:16:20 +0200 Subject: [PATCH 1/2] feat: implement linkify-it on workflow node attribute row Signed-off-by: Adrien Delannoy --- ui/package.json | 2 ++ .../workflow-node-info/workflow-node-info.tsx | 33 ++++++++++++++++++- ui/yarn.lock | 17 ++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index dd456cdb1dc1..4e672a23d037 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,6 +24,7 @@ "cronstrue": "^2.50.0", "dagre": "^0.8.5", "history": "^4.10.1", + "linkify-it": "^5.0.0", "moment": "^2.30.1", "monaco-editor": "^0.45.0", "prop-types": "^15.8.1", @@ -48,6 +49,7 @@ "@types/dagre": "^0.7.52", "@types/history": "^4.6.2", "@types/jest": "^26.0.15", + "@types/linkify-it": "^5.0.0", "@types/prop-types": "^15.7.11", "@types/react": "^16.8.5", "@types/react-autocomplete": "^1.8.10", diff --git a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx index 66b4b92e578d..6c8625a6afcc 100644 --- a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx +++ b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx @@ -5,6 +5,7 @@ import {Tooltip} from 'argo-ui/src/components/tooltip/tooltip'; import moment from 'moment'; import * as React from 'react'; import {useState} from 'react'; +import LinkifyIt from 'linkify-it'; import * as models from '../../../../models'; import {Artifact, NodeStatus, Workflow} from '../../../../models'; @@ -70,10 +71,40 @@ interface Props { onRetryNode?: () => void; } +const linkify = new LinkifyIt(); + +function linkifyText(text: string) { + const matches = linkify.match(text); + if (!matches) { + return text; + } + + const parts = []; + let lastIndex = 0; + + matches.forEach(match => { + if (match.index > lastIndex) { + parts.push({text.slice(lastIndex, match.index)}); + } + parts.push( + + {match.text} + + ); + lastIndex = match.lastIndex; + }); + + if (lastIndex < text.length) { + parts.push({text.slice(lastIndex)}); + } + + return parts; +} + const AttributeRow = (attr: {title: string; value: any}) => (
{attr.title}
-
{attr.value}
+
{linkifyText(attr.value)}
); const AttributeRows = (props: {attributes: {title: string; value: any}[]}) => ( diff --git a/ui/yarn.lock b/ui/yarn.lock index 130c0bc1d300..ccb924f66403 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2085,6 +2085,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/linkify-it@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/mdast@^3.0.0": version "3.0.15" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5" @@ -6514,6 +6519,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -9712,6 +9724,11 @@ typescript@^4.6.4, typescript@^4.9.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +uc.micro@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From 511acae26a551d42bbcefef62643b94937e5ccb2 Mon Sep 17 00:00:00 2001 From: Adrien Delannoy Date: Mon, 26 Aug 2024 19:48:10 +0200 Subject: [PATCH 2/2] feat: make Linkify component reusable Signed-off-by: Adrien Delannoy --- .../app/shared/components/linkified-text.tsx | 37 +++++++++++++++++ .../workflow-node-info/workflow-node-info.tsx | 40 +++---------------- 2 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 ui/src/app/shared/components/linkified-text.tsx diff --git a/ui/src/app/shared/components/linkified-text.tsx b/ui/src/app/shared/components/linkified-text.tsx new file mode 100644 index 000000000000..233bbd6ba5a7 --- /dev/null +++ b/ui/src/app/shared/components/linkified-text.tsx @@ -0,0 +1,37 @@ +import LinkifyIt from 'linkify-it'; +import React from 'react'; + +interface Props { + text: string; +} + +const linkify = new LinkifyIt(); + +export default function LinkifiedText({text}: Props) { + const matches = linkify.match(text); + + if (!matches) { + return <>{text}; + } + + const parts = []; + let lastIndex = 0; + + matches.forEach(match => { + if (match.index > lastIndex) { + parts.push({text.slice(lastIndex, match.index)}); + } + parts.push( + + {match.text} + + ); + lastIndex = match.lastIndex; + }); + + if (lastIndex < text.length) { + parts.push({text.slice(lastIndex)}); + } + + return <>{parts}; +} diff --git a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx index 6c8625a6afcc..874f219b31ef 100644 --- a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx +++ b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx @@ -5,7 +5,6 @@ import {Tooltip} from 'argo-ui/src/components/tooltip/tooltip'; import moment from 'moment'; import * as React from 'react'; import {useState} from 'react'; -import LinkifyIt from 'linkify-it'; import * as models from '../../../../models'; import {Artifact, NodeStatus, Workflow} from '../../../../models'; @@ -13,6 +12,7 @@ import {Button} from '../../../shared/components/button'; import {ClipboardText} from '../../../shared/components/clipboard-text'; import {DurationPanel} from '../../../shared/components/duration-panel'; import {InlineTable} from '../../../shared/components/inline-table/inline-table'; +import LinkifiedText from '../../../shared/components/linkified-text'; import {Links} from '../../../shared/components/links'; import {Phase} from '../../../shared/components/phase'; import {Timestamp} from '../../../shared/components/timestamp'; @@ -20,9 +20,9 @@ import {getPodName} from '../../../shared/pod-name'; import {ResourcesDuration} from '../../../shared/resources-duration'; import {services} from '../../../shared/services'; import {getResolvedTemplates} from '../../../shared/template-resolution'; +import {TIMESTAMP_KEYS} from '../../../shared/use-timestamp'; import './workflow-node-info.scss'; -import {TIMESTAMP_KEYS} from '../../../shared/use-timestamp'; function nodeDuration(node: models.NodeStatus, now: moment.Moment) { const endTime = node.finishedAt ? moment(node.finishedAt) : now; @@ -71,43 +71,13 @@ interface Props { onRetryNode?: () => void; } -const linkify = new LinkifyIt(); - -function linkifyText(text: string) { - const matches = linkify.match(text); - if (!matches) { - return text; - } - - const parts = []; - let lastIndex = 0; - - matches.forEach(match => { - if (match.index > lastIndex) { - parts.push({text.slice(lastIndex, match.index)}); - } - parts.push( - - {match.text} - - ); - lastIndex = match.lastIndex; - }); - - if (lastIndex < text.length) { - parts.push({text.slice(lastIndex)}); - } - - return parts; -} - -const AttributeRow = (attr: {title: string; value: any}) => ( +const AttributeRow = (attr: {title: string; value: string | React.JSX.Element}) => (
{attr.title}
-
{linkifyText(attr.value)}
+
{typeof attr.value === 'string' ? : attr.value}
); -const AttributeRows = (props: {attributes: {title: string; value: any}[]}) => ( +const AttributeRows = (props: {attributes: {title: string; value: string | React.JSX.Element}[]}) => (
{props.attributes.map(attr => (