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'
}