diff --git a/plugins/gatsby-source-snooty-prod/gatsby-node.js b/plugins/gatsby-source-snooty-prod/gatsby-node.js index 34344ec97..cba7efee3 100644 --- a/plugins/gatsby-source-snooty-prod/gatsby-node.js +++ b/plugins/gatsby-source-snooty-prod/gatsby-node.js @@ -160,6 +160,7 @@ exports.sourceNodes = async ({ actions, createContentDigest, createNodeId, getNo id: key, page_id: key, ast: doc.ast, + facets: doc.facets, internal: { type: 'Page', contentDigest: createContentDigest(doc), @@ -379,6 +380,7 @@ exports.createSchemaCustomization = ({ actions }) => { branch: String pagePath: String ast: JSON! + facets: [JSON] metadata: SnootyMetadata @link componentNames: [String!] } diff --git a/src/components/Breadcrumbs/BreadcrumbContainer.js b/src/components/Breadcrumbs/BreadcrumbContainer.js index 68e67936e..8ded22a2e 100644 --- a/src/components/Breadcrumbs/BreadcrumbContainer.js +++ b/src/components/Breadcrumbs/BreadcrumbContainer.js @@ -86,7 +86,7 @@ const crumbObjectShape = { }; BreadcrumbContainer.propTypes = { - breadcrumbs: PropTypes.shape(crumbObjectShape).isRequired, + breadcrumbs: PropTypes.arrayOf(PropTypes.shape(crumbObjectShape)).isRequired, }; export default BreadcrumbContainer; diff --git a/src/components/Code/Code.js b/src/components/Code/Code.js index e5d5a080f..081955720 100644 --- a/src/components/Code/Code.js +++ b/src/components/Code/Code.js @@ -1,5 +1,5 @@ import { css } from '@emotion/react'; -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { default as CodeBlock } from '@leafygreen-ui/code'; @@ -12,6 +12,8 @@ import { TabContext } from '../Tabs/tab-context'; import { reportAnalytics } from '../../utils/report-analytics'; import { getLanguage } from '../../utils/get-language'; import { DRIVER_ICON_MAP } from '../icons/DriverIconMap'; +import { SoftwareSourceCodeSd } from '../../utils/structured-data'; +import { usePageContext } from '../../context/page-context'; import { baseCodeStyle, borderCodeStyle, lgStyles } from './styles/codeStyle'; import { CodeContext } from './code-context'; @@ -41,6 +43,7 @@ const Code = ({ const { setActiveTab } = useContext(TabContext); const { languageOptions, codeBlockLanguage } = useContext(CodeContext); const { darkMode } = useDarkMode(); + const { slug } = usePageContext(); const code = value; let language = (languageOptions?.length > 0 && codeBlockLanguage) || getLanguage(lang); @@ -85,77 +88,92 @@ const Code = ({ reportAnalytics('CodeblockCopied', { code }); }, [code]); + const softwareSourceCodeSd = useMemo(() => { + const sd = new SoftwareSourceCodeSd({ code, lang, slug }); + return sd.isValid() ? sd.toString() : undefined; + }, [code, lang, slug]); + return ( -
div > div { - display: grid; - grid-template-columns: ${!copyable && (languageOptions?.length === 0 || language === 'none') - ? 'auto 0px !important' - : 'code panel'}; - } - - > div { - border-top-left-radius: ${captionBorderRadius}; - border-top-right-radius: ${captionBorderRadius}; - display: grid; - border-color: ${palette.gray.light2}; - - .dark-theme & { - border-color: ${palette.gray.dark2}; + <> + {softwareSourceCodeSd && ( + + )} ); }; @@ -232,6 +247,7 @@ export const query = graphql` query ($page_id: String, $slug: String) { page(id: { eq: $page_id }) { ast + facets } pageImage(slug: { eq: $slug }) { slug diff --git a/src/components/Procedure/index.js b/src/components/Procedure/index.js index 478bbc9ba..f58ad5444 100644 --- a/src/components/Procedure/index.js +++ b/src/components/Procedure/index.js @@ -2,7 +2,13 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import styled from '@emotion/styled'; import { palette } from '@leafygreen-ui/palette'; +import { + AncestorComponentContextProvider, + useAncestorComponentContext, +} from '../../context/ancestor-components-context'; import { theme } from '../../theme/docsTheme'; +import { constructHowToSd } from '../../utils/structured-data'; +import { useHeadingContext } from '../../context/heading-context'; import Step from './Step'; const StyledProcedure = styled('div')` @@ -47,18 +53,37 @@ const getSteps = (children) => { return steps; }; -const Procedure = ({ nodeData: { children, options }, ...rest }) => { +const Procedure = ({ nodeData, ...rest }) => { // Make the style 'connected' by default for now to give time for PLPs that use this directive to // add the "style" option + const children = nodeData['children']; + const options = nodeData['options']; const style = options?.style || 'connected'; const steps = useMemo(() => getSteps(children), [children]); + const ancestors = useAncestorComponentContext(); + const { lastHeading } = useHeadingContext(); + + // construct Structured Data + const howToSd = useMemo(() => { + if (ancestors['procedure']) return undefined; + + const howToSd = constructHowToSd({ steps, parentHeading: lastHeading }); + return howToSd.isValid() ? howToSd.toString() : undefined; + }, [ancestors, lastHeading, steps]); return ( - - {steps.map((child, i) => ( - - ))} - + + {howToSd && ( + // using dangerouslySetInnerHTML as JSON is rendered with + // encoded quotes at build time + ); diff --git a/src/components/Tabs/index.js b/src/components/Tabs/index.js index b242891f2..7c0e54699 100644 --- a/src/components/Tabs/index.js +++ b/src/components/Tabs/index.js @@ -9,6 +9,8 @@ import { theme } from '../../theme/docsTheme'; import { reportAnalytics } from '../../utils/report-analytics'; import { getNestedValue } from '../../utils/get-nested-value'; import { isBrowser } from '../../utils/is-browser'; +import { HeadingContextProvider, useHeadingContext } from '../../context/heading-context'; +import { getPlaintext } from '../../utils/get-plaintext'; import { TabContext } from './tab-context'; const TAB_BUTTON_SELECTOR = 'button[role="tab"]'; @@ -115,6 +117,7 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => { // Hide tabset if it includes the :hidden: option, or if it is controlled by a dropdown selector const isHidden = options.hidden || Object.keys(selectors).includes(tabsetName); const isProductLanding = page?.options?.template === 'product-landing'; + const { lastHeading } = useHeadingContext(); useEffect(() => { const index = tabIds.indexOf(activeTabs[tabsetName]); @@ -166,11 +169,15 @@ const Tabs = ({ nodeData: { children, options = {} }, page, ...rest }) => { return ( -
- {tab.children.map((child, i) => ( - - ))} -
+ +
+ {tab.children.map((child, i) => ( + + ))} +
+
); })} diff --git a/src/components/Video/index.js b/src/components/Video/index.js index 4b0029e89..43b78f52e 100644 --- a/src/components/Video/index.js +++ b/src/components/Video/index.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import ReactPlayerYT from 'react-player/youtube'; import ReactPlayerWistia from 'react-player/wistia'; import PropTypes from 'prop-types'; @@ -7,6 +7,7 @@ import { css } from '@emotion/react'; import { palette } from '@leafygreen-ui/palette'; import { withPrefix } from 'gatsby'; import { theme } from '../../theme/docsTheme'; +import { VideoObjectSd } from '../../utils/structured-data'; import VideoPlayButton from './VideoPlayButton'; // Imported both players to keep bundle size low and rendering the one associated to the URL being passed in @@ -68,10 +69,15 @@ const getTheSupportedMedia = (url) => { return REACT_PLAYERS[supportedType]; }; -const Video = ({ nodeData: { argument }, ...rest }) => { +const Video = ({ nodeData: { argument, options = {} } }) => { const url = `${argument[0]['refuri']}`; // use placeholder image for video thumbnail if invalid URL provided const [previewImage, setPreviewImage] = useState(withPrefix('assets/meta_generic.png')); + const { title, description, 'upload-date': uploadDate, 'thumbnail-url': thumbnailUrl } = options; + const videoObjectSd = useMemo(() => { + const sd = new VideoObjectSd({ embedUrl: url, name: title, uploadDate, thumbnailUrl, description }); + return sd.isValid() ? sd.toString() : undefined; + }, [url, title, uploadDate, thumbnailUrl, description]); useEffect(() => { // handles URL validity checking for well-formed YT links @@ -104,25 +110,41 @@ const Video = ({ nodeData: { argument }, ...rest }) => { } return ( - - } - light={previewImage} - /> - + <> + {videoObjectSd && ( + .emotion-0 { display: table; margin: 24px 0; @@ -402,6 +407,11 @@ exports[`renders correctly 1`] = ` exports[`renders correctly when none is passed in as a language 1`] = ` + .emotion-0 { display: table; margin: 24px 0; diff --git a/tests/unit/__snapshots__/CodeIO.test.js.snap b/tests/unit/__snapshots__/CodeIO.test.js.snap index 983f5b7e4..08e775de1 100644 --- a/tests/unit/__snapshots__/CodeIO.test.js.snap +++ b/tests/unit/__snapshots__/CodeIO.test.js.snap @@ -505,33 +505,33 @@ exports[`CodeIO renders correctly 1`] = ` justify-self: right; } -.emotion-21>div>* { +.emotion-22>div>* { display: inline!important; } -.emotion-21 * { +.emotion-22 * { border-top-right-radius: 0px; border-top-left-radius: 0px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; } -.emotion-21>div { +.emotion-22>div { border: var(--code-container-border); } -.emotion-21>div>div>pre { +.emotion-22>div>div>pre { border: var(--code-pre-border); border-top: none; } -.emotion-23 { +.emotion-24 { border: 1px solid #3D4F58; border-radius: 12px; overflow: hidden; } -.emotion-24 { +.emotion-25 { position: relative; display: grid; grid-template-areas: 'code panel'; @@ -541,8 +541,8 @@ exports[`CodeIO renders correctly 1`] = ` grid-template-areas: 'code code'; } -.emotion-24:before, -.emotion-24:after { +.emotion-25:before, +.emotion-25:after { content: ''; display: block; position: absolute; @@ -556,20 +556,20 @@ exports[`CodeIO renders correctly 1`] = ` transition: box-shadow 100ms ease-in-out; } -.emotion-24:before { +.emotion-25:before { grid-column: 1; left: -40px; } -.emotion-24:after { +.emotion-25:after { grid-column: 2; } -.emotion-24:after { +.emotion-25:after { grid-column: -1; } -.emotion-25 { +.emotion-26 { grid-area: code; overflow-x: auto; border-radius: inherit; @@ -602,45 +602,45 @@ exports[`CodeIO renders correctly 1`] = ` } @media only screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2) { - .emotion-25 { + .emotion-26 { white-space: pre-wrap; } } @media only screen and (min-device-width: 813px) and (-webkit-min-device-pixel-ratio: 2) { - .emotion-25 { + .emotion-26 { white-space: pre; } } -.emotion-25:focus-visible { +.emotion-26:focus-visible { outline: none; box-shadow: 0 0 0 2px #0498EC inset; } -.emotion-28 { +.emotion-29 { background-color: transparent; background-image: linear-gradient(90deg, #1C2D38, #001E2B); background-attachment: fixed; } -.emotion-28>td { +.emotion-29>td { border-top: 1px solid #1C2D38; } -.emotion-28+tr>td { +.emotion-29+tr>td { border-top: 1px solid #1C2D38; } -.emotion-28+.emotion-28>td { +.emotion-29+.emotion-29>td { border-top: 0; } -.emotion-28:last-child>td { +.emotion-29:last-child>td { border-bottom: 1px solid #1C2D38; } -.emotion-29 { +.emotion-30 { border-spacing: 0; vertical-align: top; padding: 0 16px; @@ -657,6 +657,11 @@ exports[`CodeIO renders correctly 1`] = `
+
@@ -766,44 +771,53 @@ exports[`CodeIO renders correctly 1`] = `
+
-
-            
-              
-                
-                  
-                    
+                    
-                      1
-                    
-                    
-                  
-                
-              
+
- hello world -
-
-
+ + 1 + + + hello world + + + + + + +
diff --git a/tests/unit/__snapshots__/Collapsible.test.js.snap b/tests/unit/__snapshots__/Collapsible.test.js.snap index a8be9e6ed..86da7511f 100644 --- a/tests/unit/__snapshots__/Collapsible.test.js.snap +++ b/tests/unit/__snapshots__/Collapsible.test.js.snap @@ -589,6 +589,11 @@ exports[`collapsible component renders all the content in the options and childr > This is collapsible content

+
diff --git a/tests/unit/__snapshots__/LiteralInclude.test.js.snap b/tests/unit/__snapshots__/LiteralInclude.test.js.snap index 3fb0fb180..f55e71ab2 100644 --- a/tests/unit/__snapshots__/LiteralInclude.test.js.snap +++ b/tests/unit/__snapshots__/LiteralInclude.test.js.snap @@ -2,6 +2,11 @@ exports[`renders correctly 1`] = ` + .emotion-0 { display: table; margin: 24px 0; diff --git a/tests/unit/__snapshots__/ReleaseSpecification.test.js.snap b/tests/unit/__snapshots__/ReleaseSpecification.test.js.snap index 2731a55ee..f812ed02a 100644 --- a/tests/unit/__snapshots__/ReleaseSpecification.test.js.snap +++ b/tests/unit/__snapshots__/ReleaseSpecification.test.js.snap @@ -2,6 +2,11 @@ exports[`renders correctly 1`] = ` + .emotion-0 { display: table; margin: 24px 0; diff --git a/tests/unit/__snapshots__/Video.test.js.snap b/tests/unit/__snapshots__/Video.test.js.snap index 71675fe8d..1339d9499 100644 --- a/tests/unit/__snapshots__/Video.test.js.snap +++ b/tests/unit/__snapshots__/Video.test.js.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`DailyMotion video renders correctly 1`] = ``; +exports[`DailyMotion video renders null 1`] = ``; -exports[`Vimeo video renders correctly 1`] = ``; +exports[`Vimeo video renders null 1`] = ``; exports[`Wistia video renders correctly 1`] = ` diff --git a/tests/unit/utils/__snapshots__/structured-data.test.js.snap b/tests/unit/utils/__snapshots__/structured-data.test.js.snap new file mode 100644 index 000000000..e48583bfd --- /dev/null +++ b/tests/unit/utils/__snapshots__/structured-data.test.js.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Structured Data HowTo Structured Data converts steps into expected structured data format 1`] = ` +HowToSd { + "@context": "https://schema.org", + "@type": "HowTo", + "image": "https://webimages.mongodb.com/_com_assets/cms/kuyj2focmkbxv7gh3-stacked_default_slate_blue.svg?auto=format%252Ccompress", + "name": undefined, + "steps": [ + { + "@type": "HowToStep", + "name": "In Atlas, go to your federated database instance for your project.", + "text": "If it's not already displayed, select the +organization that contains your project from the + Organizations menu in the navigation bar.If it's not already displayed, select your project +from the Projects menu in the navigation bar.In the sidebar, click Data Federation under +the Services heading.The Data Federation page displays.", + }, + { + "@type": "HowToStep", + "name": "Click Create Federated Database Instance.", + "text": "If you have an existing federated database instance, instead click +Create Federated Database in the +top right corner of the dashboard.", + }, + { + "@type": "HowToSection", + "itemListElement": [ + { + "@type": "HowToStep", + "name": "Rename the default collection.", + "text": "Click next to the default collection +VirtualCollection0 to edit its name. For this tutorial, +rename your collection Sessions.", + }, + { + "@type": "HowToStep", + "name": "Create a second collection.", + "text": "Click next to the default name +VirtualDatabase0 to add a collection to the database. +For this tutorial, name your new collection Users.", + }, + { + "@type": "HowToStep", + "name": "Add data to your virtual database.", + "text": "Drag and drop the following data sources into the +respective federated database instance virtual collections:/mflix/sessions.json, into the Sessions +collection, and/mflix/users.json into the Users collection.", + }, + ], + "name": "Connect to a Data Source and add sample data to your federated database instance.", + }, + { + "@type": "HowToStep", + "name": "Click Save.", + "text": "Your federated database instance appears on the Data Federation page.", + }, + ], +} +`; + +exports[`Structured Data SoftwareSourceCode returns valid structured data with programmingLanguage 1`] = ` +SoftwareSourceCodeSd { + "@context": "https://schema.org", + "@type": "SoftwareSourceCode", + "codeSampleType": "code snippet", + "text": "print("hello world")", +} +`; + +exports[`Structured Data SoftwareSourceCode returns valid structured data without programmingLangauge 1`] = ` +SoftwareSourceCodeSd { + "@context": "https://schema.org", + "@type": "SoftwareSourceCode", + "codeSampleType": "code snippet", + "text": "print("hello world")", +} +`; + +exports[`Structured Data VideoObject returns valid structured data with description 1`] = ` +VideoObjectSd { + "@context": "https://schema.org", + "@type": "VideoObject", + "description": "Learn more about indexes in Atlas Search", + "embedUrl": "https://www.youtube.com/embed/XrJG994YxD8", + "name": "Mastering Indexing for Perfect Query Matching", + "thumbnailUrl": "https://i.ytimg.com/vi/XrJG994YxD8/maxresdefault.jpg", + "uploadDate": "2023-11-08T05:00:28-08:00", +} +`; + +exports[`Structured Data VideoObject returns valid structured data without description 1`] = ` +VideoObjectSd { + "@context": "https://schema.org", + "@type": "VideoObject", + "embedUrl": "https://www.youtube.com/embed/XrJG994YxD8", + "name": "Mastering Indexing for Perfect Query Matching", + "thumbnailUrl": "https://i.ytimg.com/vi/XrJG994YxD8/maxresdefault.jpg", + "uploadDate": "2023-11-08T05:00:28-08:00", +} +`; diff --git a/tests/unit/utils/structured-data.test.js b/tests/unit/utils/structured-data.test.js new file mode 100644 index 000000000..17184dfe4 --- /dev/null +++ b/tests/unit/utils/structured-data.test.js @@ -0,0 +1,53 @@ +import { SoftwareSourceCodeSd, VideoObjectSd, constructHowToSd } from '../../../src/utils/structured-data'; +import stepsData from '../../utils/data/how-to-structured-data.json'; + +describe('Structured Data', () => { + describe('HowTo Structured Data', () => { + it('converts steps into expected structured data format', () => { + const howToSd = constructHowToSd({ steps: stepsData }); + expect(howToSd).toMatchSnapshot(); + }); + }); + + describe('SoftwareSourceCode', () => { + it('returns valid structured data with programmingLanguage', () => { + const code = 'print("hello world")'; + const lang = 'py'; + const softwareSourceCodeSd = new SoftwareSourceCodeSd({ code, lang }); + expect(softwareSourceCodeSd.isValid()).toBeTruthy(); + expect(softwareSourceCodeSd).toMatchSnapshot(); + }); + + it('returns valid structured data without programmingLangauge', () => { + const code = 'print("hello world")'; + const softwareSourceCodeSd = new SoftwareSourceCodeSd({ code }); + expect(softwareSourceCodeSd.isValid()).toBeTruthy(); + expect(softwareSourceCodeSd).toMatchSnapshot(); + }); + }); + + describe('VideoObject', () => { + const embedUrl = 'https://www.youtube.com/embed/XrJG994YxD8'; + const name = 'Mastering Indexing for Perfect Query Matching'; + const uploadDate = '2023-11-08T05:00:28-08:00'; + const thumbnailUrl = 'https://i.ytimg.com/vi/XrJG994YxD8/maxresdefault.jpg'; + const description = 'Learn more about indexes in Atlas Search'; + + it('returns valid structured data with description', () => { + const videoObjectSd = new VideoObjectSd({ embedUrl, name, uploadDate, thumbnailUrl, description }); + expect(videoObjectSd.isValid()).toBeTruthy(); + expect(videoObjectSd).toMatchSnapshot(); + }); + + it('returns valid structured data without description', () => { + const videoObjectSd = new VideoObjectSd({ embedUrl, name, uploadDate, thumbnailUrl }); + expect(videoObjectSd.isValid()).toBeTruthy(); + expect(videoObjectSd).toMatchSnapshot(); + }); + + it('returns invalid structured data with missing name field', () => { + const videoObjectSd = new VideoObjectSd({ embedUrl, uploadDate, thumbnailUrl, description }); + expect(videoObjectSd.isValid()).toBeFalsy(); + }); + }); +}); diff --git a/tests/utils/data/how-to-structured-data.json b/tests/utils/data/how-to-structured-data.json new file mode 100644 index 000000000..0448552dd --- /dev/null +++ b/tests/utils/data/how-to-structured-data.json @@ -0,0 +1,1020 @@ +[ + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "In " + }, + { + "type": "substitution_reference", + "children": [ + { + "type": "text", + "value": "Atlas" + } + ], + "name": "service" + }, + { + "type": "text", + "value": ", go to your federated database instance for your project." + } + ], + "id": "in---go-to-your-federated-database-instance-for-your-project." + }, + { + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "If it's not already displayed, select the\norganization that contains your project from the\n" + }, + { + "type": "substitution_reference", + "children": [ + { + "type": "role", + "children": [], + "domain": "", + "name": "icon-mms", + "target": "office", + "flag": "" + }, + { + "type": "text", + "value": " " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Organizations" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " menu" + } + ], + "name": "ui-org-menu" + }, + { + "type": "text", + "value": " in the navigation bar." + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "If it's not already displayed, select your project\nfrom the " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Projects" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " menu in the navigation bar." + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "In the sidebar, click " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Data Federation" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " under\nthe " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Services" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " heading." + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The " + }, + { + "type": "reference", + "children": [ + { + "type": "text", + "value": "Data Federation" + } + ], + "refuri": "https://cloud.mongodb.com/go?l=https%3A%2F%2Fcloud.mongodb.com%2Fv2%2F%3Cproject%3E%23%2FdataFederation" + }, + { + "type": "text", + "value": " page displays." + } + ] + } + ] + } + ], + "enumtype": "loweralpha" + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 0 + } + }, + "value": "In " + }, + { + "type": "substitution_reference", + "position": { + "start": { + "line": 0 + } + }, + "children": [ + { + "type": "text", + "position": { + "start": { + "line": 0 + } + }, + "value": "Atlas" + } + ], + "name": "service" + }, + { + "type": "text", + "position": { + "start": { + "line": 0 + } + }, + "value": ", go to your federated database instance for your project." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Click " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Create Federated Database Instance" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": "." + } + ], + "id": "click-create-federated-database-instance." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "If you have an existing federated database instance, instead click\n" + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Create Federated Database" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " in the\ntop right corner of the dashboard." + } + ] + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 5 + } + }, + "value": "Click " + }, + { + "type": "role", + "position": { + "start": { + "line": 5 + } + }, + "children": [ + { + "type": "text", + "position": { + "start": { + "line": 5 + } + }, + "value": "Create Federated Database Instance" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "position": { + "start": { + "line": 5 + } + }, + "value": "." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Connect to a Data Source and add sample data to your federated database instance." + } + ], + "id": "connect-to-a-data-source-and-add-sample-data-to-your-federated-database-instance." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "You can use a sample dataset to start exploring\nAtlas SQL through Atlas Data Federation without configuring a data source\nyourself. This tutorial references a specific sample dataset." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "To connect to your own data instead, click\n" + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Add Data Sources" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": ". To learn more about\nconfiguring different types of data sources, see\n" + }, + { + "type": "ref_role", + "children": [ + { + "type": "text", + "value": "Define Data Stores for a Federated Database Instance" + } + ], + "domain": "std", + "name": "label", + "target": "config-adf", + "flag": "", + "fileid": ["data-federation/config/config-data-stores", "std-label-config-adf"] + }, + { + "type": "text", + "value": "." + } + ] + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "If you want to configure data from a " + }, + { + "type": "substitution_reference", + "children": [ + { + "type": "text", + "value": "Atlas" + } + ], + "name": "service" + }, + { + "type": "text", + "value": " cluster, you\nmust use MongoDB version 5.0 or greater for that cluster to\ntake advantage of Atlas SQL." + } + ] + } + ], + "domain": "", + "name": "note", + "argument": [] + }, + { + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Click " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Add Sample Data" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": "." + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Select " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "AWS S3" + } + ] + }, + { + "type": "text", + "value": " from the " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Filter" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " dropdown if it\nisn't selected already." + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Expand the " + }, + { + "type": "substitution_reference", + "children": [ + { + "type": "role", + "children": [ + { + "type": "text", + "value": "S3 (Simple Storage Service)" + } + ], + "domain": "", + "name": "abbr", + "target": "", + "flag": "" + } + ], + "name": "s3" + }, + { + "type": "text", + "value": " store " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "sample-data-atlas-data-lake" + } + ] + }, + { + "type": "text", + "value": "\nif it isn't expanded already." + } + ] + } + ] + } + ], + "enumtype": "loweralpha" + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "For this tutorial, configure your federated database instance as follows using the\n" + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Federated Database Instance" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " panel:" + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Rename the default collection." + } + ], + "id": "rename-the-default-collection." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Click " + }, + { + "type": "role", + "children": [], + "domain": "", + "name": "icon-fa4", + "target": "pencil", + "flag": "" + }, + { + "type": "text", + "value": " next to the default collection\n" + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "VirtualCollection0" + } + ] + }, + { + "type": "text", + "value": " to edit its name. For this tutorial,\nrename your collection " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "Sessions" + } + ] + }, + { + "type": "text", + "value": "." + } + ] + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 42 + } + }, + "value": "Rename the default collection." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Create a second collection." + } + ], + "id": "create-a-second-collection." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Click " + }, + { + "type": "role", + "children": [], + "domain": "", + "name": "icon-fa4", + "target": "plus-square", + "flag": "" + }, + { + "type": "text", + "value": " next to the default name\n" + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "VirtualDatabase0" + } + ] + }, + { + "type": "text", + "value": " to add a collection to the database.\nFor this tutorial, name your new collection " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "Users" + } + ] + }, + { + "type": "text", + "value": "." + } + ] + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 48 + } + }, + "value": "Create a second collection." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Add data to your virtual database." + } + ], + "id": "add-data-to-your-virtual-database." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Drag and drop the following data sources into the\nrespective federated database instance virtual collections:" + } + ] + }, + { + "type": "list", + "children": [ + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "/mflix/sessions.json" + } + ] + }, + { + "type": "text", + "value": ", into the " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "Sessions" + } + ] + }, + { + "type": "text", + "value": "\ncollection, and" + } + ] + } + ] + }, + { + "type": "listItem", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "/mflix/users.json" + } + ] + }, + { + "type": "text", + "value": " into the " + }, + { + "type": "literal", + "children": [ + { + "type": "text", + "value": "Users" + } + ] + }, + { + "type": "text", + "value": " collection." + } + ] + } + ] + } + ], + "enumtype": "unordered" + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 54 + } + }, + "value": "Add data to your virtual database." + } + ] + } + ], + "domain": "mongodb", + "name": "procedure", + "argument": [], + "options": { + "style": "normal" + } + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 11 + } + }, + "value": "Connect to a Data Source and add sample data to your federated database instance." + } + ] + }, + { + "type": "directive", + "children": [ + { + "type": "section", + "children": [ + { + "type": "heading", + "children": [ + { + "type": "text", + "value": "Click " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Save" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": "." + } + ], + "id": "click-save." + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Your federated database instance appears on the " + }, + { + "type": "role", + "children": [ + { + "type": "text", + "value": "Data Federation" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "value": " page." + } + ] + } + ] + } + ], + "domain": "mongodb", + "name": "step", + "argument": [ + { + "type": "text", + "position": { + "start": { + "line": 63 + } + }, + "value": "Click " + }, + { + "type": "role", + "position": { + "start": { + "line": 63 + } + }, + "children": [ + { + "type": "text", + "position": { + "start": { + "line": 63 + } + }, + "value": "Save" + } + ], + "domain": "", + "name": "guilabel", + "target": "", + "flag": "" + }, + { + "type": "text", + "position": { + "start": { + "line": 63 + } + }, + "value": "." + } + ] + } +]