diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.spec.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.spec.tsx new file mode 100644 index 0000000000..c73bf46376 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.spec.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { render, screen } from 'uiSrc/utils/test-utils' + +import CloudLink from './CloudLink' + +describe('CloudLink', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should render proper url', () => { + const url = 'https://site.com' + render() + + expect(screen.getByTestId('guide-free-database-link').getAttribute('href')).toBe(url) + }) +}) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.tsx new file mode 100644 index 0000000000..96f3ddb791 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/CloudLink.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { EuiLink } from '@elastic/eui' +import { OAuthSocialSource } from 'uiSrc/slices/interfaces' +import { OAuthSsoHandlerDialog } from 'uiSrc/components' + +export interface Props { + url: string + text: string +} + +const CloudLink = (props: Props) => { + const { url, text } = props + return ( + + {(ssoCloudHandlerClick) => ( + { + ssoCloudHandlerClick(e, OAuthSocialSource.Tutorials) + }} + external={false} + target="_blank" + href={url} + data-testid="guide-free-database-link" + > + {text} + + )} + + ) +} + +export default CloudLink diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/index.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/index.ts new file mode 100644 index 0000000000..3d899dd923 --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/CloudLink/index.ts @@ -0,0 +1,3 @@ +import CloudLink from './CloudLink' + +export default CloudLink diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/InternalPage/InternalPage.tsx b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/InternalPage/InternalPage.tsx index 6ea62df990..7804b19b74 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/InternalPage/InternalPage.tsx +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/InternalPage/InternalPage.tsx @@ -17,6 +17,7 @@ import { Code, EmptyPrompt, RedisUploadButton, + CloudLink, Pagination } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/components' import { getTutorialSection } from 'uiSrc/pages/workbench/components/enablement-area/EnablementArea/utils' @@ -59,7 +60,7 @@ const InternalPage = (props: Props) => { manifestPath, sourcePath } = props - const components: any = { LazyCodeButton, Image, Code, RedisUploadButton } + const components: any = { LazyCodeButton, Image, Code, RedisUploadButton, CloudLink } const containerRef = useRef(null) const { instanceId = '' } = useParams<{ instanceId: string }>() const handleScroll = debounce(() => { diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/index.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/index.ts index 52c5608c3e..1ce46b22f3 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/index.ts +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/components/index.ts @@ -12,6 +12,7 @@ import Pagination from './Pagination' import UploadTutorialForm from './UploadTutorialForm' import WelcomeMyTutorials from './WelcomeMyTutorials' import RedisUploadButton from './RedisUploadButton' +import CloudLink from './CloudLink' export { Code, @@ -28,4 +29,5 @@ export { UploadTutorialForm, WelcomeMyTutorials, RedisUploadButton, + CloudLink, } diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/formatter/MarkdownToJsxString.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/formatter/MarkdownToJsxString.ts index 889b48b444..b51f6ec49d 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/formatter/MarkdownToJsxString.ts +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/formatter/MarkdownToJsxString.ts @@ -10,6 +10,7 @@ import { rehypeLinks } from '../transform/rehypeLinks' import { remarkRedisUpload } from '../transform/remarkRedisUpload' import { remarkRedisCode } from '../transform/remarkRedisCode' import { remarkImage } from '../transform/remarkImage' +import { remarkLink } from '../transform/remarkLink' class MarkdownToJsxString implements IFormatter { format(input: any, config?: IFormatterConfig): Promise { @@ -21,6 +22,7 @@ class MarkdownToJsxString implements IFormatter { .use(remarkRedisUpload, path) // Add custom component for redis-upload code block .use(remarkRedisCode) // Add custom component for Redis code block .use(remarkImage, path) // Add custom component for Redis code block + .use(remarkLink) // Add custom component for Redis code block .use(remarkRehype, { allowDangerousHtml: true }) // Pass raw HTML strings through. .use(rehypeLinks, config ? { history: config.history } : undefined) // Customise links .use(MarkdownToJsxString.rehypeWrapSymbols) // Wrap special symbols inside curly braces for JSX parse diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkLink.spec.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkLink.spec.ts new file mode 100644 index 0000000000..2fa5d45bae --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/tests/remarkLink.spec.ts @@ -0,0 +1,53 @@ +import { visit } from 'unist-util-visit' +import { remarkLink } from '../transform/remarkLink' + +jest.mock('unist-util-visit') + +describe('remarkLink', () => { + it('should not modify codeNode if title is not Redis Cloud', () => { + const codeNode = { + type: 'link', + url: 'https://mysite.com', + children: [ + { + type: 'text', + value: 'Redis Stack Server' + } + ] + }; + // mock implementation + (visit as jest.Mock) + .mockImplementation((_tree: any, _name: string, callback: (codeNode: any) => void) => { callback(codeNode) }) + + const remark = remarkLink() + remark({} as Node) + expect(codeNode).toEqual({ + ...codeNode + }) + }) + + it('should properly modify codeNode with title Redis Cloud', () => { + const codeNode = { + title: 'Redis Cloud', + type: 'link', + url: 'https://mysite.com', + children: [ + { + type: 'text', + value: 'Setup Redis Cloud' + } + ] + }; + // mock implementation + (visit as jest.Mock) + .mockImplementation((_tree: any, _name: string, callback: (codeNode: any) => void) => { callback(codeNode) }) + + const remark = remarkLink() + remark({} as Node) + expect(codeNode).toEqual({ + ...codeNode, + type: 'html', + value: '', + }) + }) +}) diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/rehypeLinks.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/rehypeLinks.ts index 21d00bd610..eeee0b2229 100644 --- a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/rehypeLinks.ts +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/rehypeLinks.ts @@ -1,6 +1,6 @@ import { visit } from 'unist-util-visit' -import { IS_ABSOLUTE_PATH } from 'uiSrc/constants/regex' import { createLocation, History } from 'history' +import { IS_ABSOLUTE_PATH } from 'uiSrc/constants/regex' interface IConfig { history: History @@ -13,6 +13,7 @@ export const rehypeLinks = (config?: IConfig): (tree: Node) => void => (tree: an if (IS_ABSOLUTE_PATH.test(url)) { // External link node.properties.rel = ['nofollow', 'noopener', 'noreferrer'] node.properties.target = '_blank' + delete node.properties.title } if (url.startsWith('#') && config?.history) { const { location: currentLocation } = config.history diff --git a/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/remarkLink.ts b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/remarkLink.ts new file mode 100644 index 0000000000..28a8c4e3cf --- /dev/null +++ b/redisinsight/ui/src/pages/workbench/components/enablement-area/EnablementArea/utils/transform/remarkLink.ts @@ -0,0 +1,12 @@ +import { visit } from 'unist-util-visit' + +export const remarkLink = (): (tree: Node) => void => (tree: any) => { + // Find link node in syntax tree + visit(tree, 'link', (node) => { + if (node.title === 'Redis Cloud') { + const [text] = node.children || [] + node.type = 'html' + node.value = `` + } + }) +} diff --git a/redisinsight/ui/src/slices/interfaces/cloud.ts b/redisinsight/ui/src/slices/interfaces/cloud.ts index aa0f7ace48..a753109657 100644 --- a/redisinsight/ui/src/slices/interfaces/cloud.ts +++ b/redisinsight/ui/src/slices/interfaces/cloud.ts @@ -72,4 +72,5 @@ export enum OAuthSocialSource { ConfirmationMessage = 'confirmation message', TriggersAndFunctions = 'triggers_and_functions', 'triggers and functions' = 'workbench triggers_and_functions', + Tutorials = 'tutorials' }