From 7ed1fff489bf1b28b16edb50e2eb564235e9a8f6 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 29 Sep 2023 14:06:22 -0400 Subject: [PATCH 001/152] start work --- website/docs/quickstarts/bigquery-qs.md | 2 + website/docs/quickstarts/codespace-qs.md | 1 + website/docs/quickstarts/databricks-qs.md | 1 + website/docs/quickstarts/redshift-qs.md | 1 + website/package.json | 2 + .../components/quickstartGuideCard/index.js | 25 ++++++++--- .../quickstartGuideCard/styles.module.css | 27 +++++++++-- .../components/quickstartGuideList/index.js | 45 +++++++++++++++++-- .../quickstartGuideList/styles.module.css | 10 ++++- .../src/components/selectDropdown/index.js | 17 +++++++ .../selectDropdown/styles.module.css | 1 + 11 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 website/src/components/selectDropdown/index.js create mode 100644 website/src/components/selectDropdown/styles.module.css diff --git a/website/docs/quickstarts/bigquery-qs.md b/website/docs/quickstarts/bigquery-qs.md index 7f7f9aa7655..01b2cf7ba7e 100644 --- a/website/docs/quickstarts/bigquery-qs.md +++ b/website/docs/quickstarts/bigquery-qs.md @@ -5,6 +5,8 @@ time_to_complete: '30 minutes' platform: 'dbt-cloud' icon: 'bigquery' hide_table_of_contents: true +tags: ['BigQuery', 'dbt Cloud'] +level: 'Beginner' --- ## Introduction diff --git a/website/docs/quickstarts/codespace-qs.md b/website/docs/quickstarts/codespace-qs.md index 3cd048c97a4..f2671335ddc 100644 --- a/website/docs/quickstarts/codespace-qs.md +++ b/website/docs/quickstarts/codespace-qs.md @@ -4,6 +4,7 @@ id: codespace platform: 'dbt-core' icon: 'fa-github' hide_table_of_contents: true +tags: ['dbt Core'] --- ## Introduction diff --git a/website/docs/quickstarts/databricks-qs.md b/website/docs/quickstarts/databricks-qs.md index 08334862517..cbd1c36f9a1 100644 --- a/website/docs/quickstarts/databricks-qs.md +++ b/website/docs/quickstarts/databricks-qs.md @@ -4,6 +4,7 @@ id: "databricks" platform: 'dbt-cloud' icon: 'databricks' hide_table_of_contents: true +recently_updated: true --- ## Introduction diff --git a/website/docs/quickstarts/redshift-qs.md b/website/docs/quickstarts/redshift-qs.md index 67f66d6e275..82c865ae123 100644 --- a/website/docs/quickstarts/redshift-qs.md +++ b/website/docs/quickstarts/redshift-qs.md @@ -4,6 +4,7 @@ id: "redshift" platform: 'dbt-cloud' icon: 'redshift' hide_table_of_contents: true +tags: ['Redshift', 'dbt Cloud'] --- ## Introduction diff --git a/website/package.json b/website/package.json index afb7a9b1cd4..5dee165966f 100644 --- a/website/package.json +++ b/website/package.json @@ -39,6 +39,7 @@ "react-dom": "^17.0.1", "react-full-screen": "^1.1.1", "react-is": "^18.1.0", + "react-select": "^5.7.5", "react-tooltip": "^4.2.21", "redoc": "^2.0.0-rc.57", "rehype-katex": "^5.0.0", @@ -79,6 +80,7 @@ "stream-http": "^3.2.0", "style-loader": "^1.1.3", "svg-inline-loader": "^0.8.2", + "tailwindcss": "^3.3.3", "tty-browserify": "0.0.1", "webpack": "^5.75.0", "webpack-dev-server": "^4.11.1" diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index fdc629bd7b0..a019022d079 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -4,14 +4,15 @@ import styles from "./styles.module.css"; import getIconType from "../../utils/get-icon-type"; function QuickstartGuideCard({ frontMatter }) { - const { id, title, time_to_complete, icon } = frontMatter; + const { id, title, time_to_complete, icon, tags, level, recently_updated } = frontMatter; + return ( - + + {recently_updated && ( + Updated + )} {icon && getIconType(icon, styles.icon)} - +

{title}

{time_to_complete && ( @@ -21,6 +22,18 @@ function QuickstartGuideCard({ frontMatter }) { Start + + {(tags || level) && ( +
+ {tags && + tags.map((tag, i) => ( +
+ {tag} +
+ ))} + {level &&
{level}
} +
+ )} ); } diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index 8202f694fcd..d8f60cb3a73 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -4,21 +4,20 @@ box-shadow: 0px 11px 24px rgba(138, 138, 138, .1); padding: 2.5rem 2.5rem 1.5rem 2.5rem; flex: 0 0 30%; - border-bottom: solid 4px var(--color-light-teal); display: flex; flex-direction: column; text-decoration: none !important; transition: all 0.2s ease-in-out; + position: relative; } .quickstartCard:hover { - border-bottom-color: var(--color-orange); transform: translateY(-7px); } .quickstartCard .icon { - max-width: 25px; - font-size: 25px; + max-width: 46px; + font-size: 46px; margin-bottom: .8rem; color: var(--ifm-menu-color); } @@ -63,3 +62,23 @@ content: " →"; margin-left: 5px; } + +.quickstartCard .recently_updated { + position: absolute; + top: 1.5rem; + right: 1.5rem; +} + +.quickstartCard .tag_container { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-top: 1rem; +} + +.quickstartCard .tag_container .tag { + background: #E5E7EB; + border-radius: 1.5rem; + color:#262A38; + padding: 0rem 0.75rem; +} diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 954d54e6d47..e48b8b2e7ef 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -1,20 +1,56 @@ import React from 'react'; +import { useState } from 'react'; import Head from '@docusaurus/Head'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; import Hero from '@site/src/components/hero'; import QuickstartGuideCard from '../quickstartGuideCard' import styles from './styles.module.css'; +import { SelectDropdown } from '../selectDropdown'; const quickstartTitle = 'Quickstarts' const quickstartDescription = 'dbt Core is a powerful open-source tool for data transformations and dbt Cloud is the fastest and most reliable way to deploy your dbt jobs. With the help of a sample project, learn how to quickly start using dbt and one of the most common data platforms.' + function QuickstartList({ quickstartData }) { const { siteConfig } = useDocusaurusContext() + const [filteredData, setFilteredData] = useState(quickstartData); // Build meta title from quickstartTitle and docusaurus config site title const metaTitle = `${quickstartTitle}${siteConfig?.title ? ` | ${siteConfig.title}` : ''}` + // Create an options array for the tag select dropdown + const tagOptions = []; + quickstartData.forEach((guide) => { + if (guide.data.tags) { + guide.data.tags.forEach((tag) => { + const tagOption = { value: tag, label: tag }; + if (!tagOptions.find((option) => option.value === tag)) { + tagOptions.push(tagOption); + } + }); + } + }); + + // Filter the quickstart guides based on the selected tags + const onChange = (selectedOption) => { + // only return the quickstart guides that have the selected tag and match all of the selected tags + const filteredGuides = quickstartData.filter((guide) => { + if (guide.data.tags) { + return selectedOption.every((option) => + guide.data.tags.includes(option.value) + ); + } + }); + setFilteredData(filteredGuides); + + // If no tags are selected, show all quickstart guides + if (selectedOption.length === 0) { + setFilteredData(quickstartData); + } + + }; + return ( @@ -30,15 +66,18 @@ function QuickstartList({ quickstartData }) { classNames={styles.quickstartHero} />
+
+ +
- {quickstartData && quickstartData.length > 0 ? ( + {filteredData && filteredData.length > 0 ? ( <> - {quickstartData.map((guide, i) => ( + {filteredData.map((guide, i) => ( ))} ) : -

No quickstarts are available at this time. 😕

+

No quickstarts are available with the selected filters.

}
diff --git a/website/src/components/quickstartGuideList/styles.module.css b/website/src/components/quickstartGuideList/styles.module.css index 8c4e45edc8c..54b6a2e70f0 100644 --- a/website/src/components/quickstartGuideList/styles.module.css +++ b/website/src/components/quickstartGuideList/styles.module.css @@ -18,10 +18,18 @@ .quickstartCardContainer { display: grid; grid-template-columns: 1fr 1fr 1fr; - grid-gap: 2rem; + grid-gap: 1rem; padding: 5rem 1rem; } +.quickstartFilterContainer { + +} + +.quickstartFilterContainer > div:first-child { + padding: 0; +} + @media (max-width: 996px) { .quickstartCardContainer { grid-template-columns: 1fr; diff --git a/website/src/components/selectDropdown/index.js b/website/src/components/selectDropdown/index.js new file mode 100644 index 00000000000..216c5213aae --- /dev/null +++ b/website/src/components/selectDropdown/index.js @@ -0,0 +1,17 @@ +import React from "react"; +import Select from "react-select"; +import styles from "./styles.module.css"; + +export const SelectDropdown = ({ options, value, onChange }) => { + return ( + ); diff --git a/website/src/components/selectDropdown/styles.module.css b/website/src/components/selectDropdown/styles.module.css index 8b137891791..9892fea5f93 100644 --- a/website/src/components/selectDropdown/styles.module.css +++ b/website/src/components/selectDropdown/styles.module.css @@ -1 +1,3 @@ - +.selectContainer { + min-width: 25rem; +} From 37a3ea029c4e3c4f162a6b12773fa48f0fc6ba59 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 29 Sep 2023 14:40:56 -0400 Subject: [PATCH 003/152] update package lock --- website/package-lock.json | 1022 ++++++++++++++++++++++++++++++++++++- 1 file changed, 994 insertions(+), 28 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index b15a903e97f..0e907bfd99f 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -36,6 +36,7 @@ "react-dom": "^17.0.1", "react-full-screen": "^1.1.1", "react-is": "^18.1.0", + "react-select": "^5.7.5", "react-tooltip": "^4.2.21", "redoc": "^2.0.0-rc.57", "rehype-katex": "^5.0.0", @@ -73,6 +74,7 @@ "stream-http": "^3.2.0", "style-loader": "^1.1.3", "svg-inline-loader": "^0.8.2", + "tailwindcss": "^3.3.3", "tty-browserify": "0.0.1", "webpack": "^5.75.0", "webpack-dev-server": "^4.11.1" @@ -232,6 +234,18 @@ "@algolia/requester-common": "4.16.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -3098,6 +3112,59 @@ "node": ">=12" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -3111,6 +3178,56 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, "node_modules/@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -3121,6 +3238,24 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@endiliey/react-ideal-image": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz", @@ -3204,6 +3339,28 @@ "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==" }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz", + "integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==" + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", @@ -6820,6 +6977,14 @@ "@types/react-router": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz", + "integrity": "sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resize-observer-browser": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz", @@ -7636,6 +7801,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -7981,6 +8152,20 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -10535,6 +10720,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -10570,6 +10761,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -12111,6 +12308,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", @@ -14081,9 +14283,9 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dependencies": { "has": "^1.0.3" }, @@ -16052,6 +16254,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", + "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/joi": { "version": "17.8.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", @@ -17456,6 +17667,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -17884,6 +18100,17 @@ "multicast-dns": "cli.js" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nano-memoize": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-1.3.1.tgz", @@ -17898,9 +18125,15 @@ } }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -18335,6 +18568,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -19034,9 +19276,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -19045,10 +19287,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -19158,6 +19404,80 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", @@ -19428,6 +19748,25 @@ "url": "https://opencollective.com/postcss/" } }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", @@ -20644,6 +20983,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-select": { + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.5.tgz", + "integrity": "sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-tabs": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", @@ -20696,6 +21055,30 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-transition-group/node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/react-universal-interface": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", @@ -20719,6 +21102,15 @@ "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -21378,11 +21770,11 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -22870,6 +23262,71 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, + "node_modules/sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23165,6 +23622,64 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tailwindcss": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -23333,6 +23848,27 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -23505,6 +24041,12 @@ "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/ts-keycode-enum": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ts-keycode-enum/-/ts-keycode-enum-1.0.6.tgz", @@ -25404,6 +25946,12 @@ "@algolia/requester-common": "4.16.0" } }, + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -27423,6 +27971,60 @@ "tslib": "^2.4.0" } }, + "@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "requires": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + } + } + }, + "@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, "@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -27436,6 +28038,50 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, + "@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "requires": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + } + } + }, + "@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, "@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -27446,6 +28092,22 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "@endiliey/react-ideal-image": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@endiliey/react-ideal-image/-/react-ideal-image-0.0.11.tgz", @@ -27502,6 +28164,28 @@ "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==" }, + "@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "requires": { + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "requires": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/utils": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.4.tgz", + "integrity": "sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==" + }, "@fortawesome/fontawesome-common-types": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", @@ -30317,6 +31001,14 @@ "@types/react-router": "*" } }, + "@types/react-transition-group": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz", + "integrity": "sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==", + "requires": { + "@types/react": "*" + } + }, "@types/resize-observer-browser": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz", @@ -30966,6 +31658,12 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -31224,6 +31922,16 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, "babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -33156,6 +33864,12 @@ } } }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -33187,6 +33901,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -34367,6 +35087,11 @@ "pkg-dir": "^4.1.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", @@ -35795,9 +36520,9 @@ } }, "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "requires": { "has": "^1.0.3" } @@ -37299,6 +38024,12 @@ } } }, + "jiti": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", + "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "dev": true + }, "joi": { "version": "17.8.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", @@ -38307,6 +39038,11 @@ "fs-monkey": "^1.0.3" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -38591,6 +39327,17 @@ "thunky": "^1.0.2" } }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nano-memoize": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-1.3.1.tgz", @@ -38605,9 +39352,9 @@ } }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "napi-build-utils": { "version": "1.0.2", @@ -38932,6 +39679,12 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -39455,11 +40208,11 @@ } }, "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -39525,6 +40278,44 @@ "postcss-selector-parser": "^6.0.5" } }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "dependencies": { + "yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "dev": true + } + } + }, "postcss-loader": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", @@ -39713,6 +40504,15 @@ } } }, + "postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.11" + } + }, "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", @@ -40579,6 +41379,22 @@ "prop-types": "^15.7.2" } }, + "react-select": { + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.5.tgz", + "integrity": "sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA==", + "requires": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + } + }, "react-tabs": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", @@ -40614,6 +41430,28 @@ } } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "dependencies": { + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + } + } + }, "react-universal-interface": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", @@ -40631,6 +41469,15 @@ "react-is": "^17.0.1 || ^18.0.0" } }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, "readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -41126,11 +41973,11 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -42254,6 +43101,54 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, + "sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -42470,6 +43365,53 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tailwindcss": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", + "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "dev": true, + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + } + } + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -42584,6 +43526,24 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -42717,6 +43677,12 @@ "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "ts-keycode-enum": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ts-keycode-enum/-/ts-keycode-enum-1.0.6.tgz", From 0682660a91a92322fa0f2c01c58c3d5ee648af33 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 29 Sep 2023 16:09:48 -0400 Subject: [PATCH 004/152] get search working --- .../components/quickstartGuideList/index.js | 19 ++++++++++++------- website/src/components/searchInput/index.js | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 website/src/components/searchInput/index.js diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 61069299c6b..638eec0ae55 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -7,6 +7,7 @@ import Hero from '@site/src/components/hero'; import QuickstartGuideCard from '../quickstartGuideCard' import styles from './styles.module.css'; import { SelectDropdown } from '../selectDropdown'; +import SearchInput from '../searchInput'; const quickstartTitle = 'Quickstarts' const quickstartDescription = 'dbt Core is a powerful open-source tool for data transformations and dbt Cloud is the fastest and most reliable way to deploy your dbt jobs. With the help of a sample project, learn how to quickly start using dbt and one of the most common data platforms.' @@ -17,11 +18,12 @@ function QuickstartList({ quickstartData }) { const [filteredData, setFilteredData] = useState(quickstartData); const [selectedTags, setSelectedTags] = useState([]); const [selectedLevel, setSelectedLevel] = useState([]); + const [searchInput, setSearchInput] = useState(''); // Build meta title from quickstartTitle and docusaurus config site title const metaTitle = `${quickstartTitle}${siteConfig?.title ? ` | ${siteConfig.title}` : ''}` - // Create an options array for the tag select dropdown + // Array for the tag select dropdown const tagOptions = []; quickstartData?.forEach((guide) => { if (guide?.data?.tags) { @@ -34,7 +36,7 @@ function QuickstartList({ quickstartData }) { } }); - // Create an options array for the level select dropdown + // Array for the level select dropdown const levelOptions = [] quickstartData?.forEach((guide) => { if (guide?.data?.level) { @@ -45,7 +47,8 @@ function QuickstartList({ quickstartData }) { } }); - const handleFilterChange = () => { + // Handle all filters + const handleDataFilter = () => { const filteredGuides = quickstartData.filter((guide) => { const tagsMatch = selectedTags.length === 0 || (Array.isArray(guide?.data?.tags) && selectedTags.every((tag) => guide?.data?.tags.includes(tag.value) @@ -53,15 +56,15 @@ function QuickstartList({ quickstartData }) { const levelMatch = selectedLevel.length === 0 || (guide?.data?.level && selectedLevel.some((level) => guide?.data?.level === level.value )); - return tagsMatch && levelMatch; + const titleMatch = searchInput === '' || guide?.data?.title?.toLowerCase().includes(searchInput.toLowerCase()); + return tagsMatch && levelMatch && titleMatch; }); setFilteredData(filteredGuides); }; - useEffect(() => { - handleFilterChange(); - }, [selectedTags, selectedLevel]); + handleDataFilter(); + }, [selectedTags, selectedLevel, searchInput]); return ( @@ -81,6 +84,8 @@ function QuickstartList({ quickstartData }) {
+ setSearchInput(value)} placeholder='Search Quickstarts' /> +
{filteredData && filteredData.length > 0 ? ( diff --git a/website/src/components/searchInput/index.js b/website/src/components/searchInput/index.js new file mode 100644 index 00000000000..04d1ded7661 --- /dev/null +++ b/website/src/components/searchInput/index.js @@ -0,0 +1,15 @@ +import React from 'react'; + +const SearchInput = ({ value, onChange, placeholder = "Search...", ...props }) => { + return ( + onChange && onChange(e.target.value)} + placeholder={placeholder} + {...props} + /> + ); +}; + +export default SearchInput; From 71c382c7f1a151c0b14a3f0501b839507d5b0364 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 29 Sep 2023 16:33:22 -0400 Subject: [PATCH 005/152] small performance refactors --- .../components/quickstartGuideList/index.js | 78 +++++++++---------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 638eec0ae55..a37ec23966a 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import Head from '@docusaurus/Head'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; @@ -14,38 +14,31 @@ const quickstartDescription = 'dbt Core is a powerful open-source tool for data function QuickstartList({ quickstartData }) { - const { siteConfig } = useDocusaurusContext() - const [filteredData, setFilteredData] = useState(quickstartData); + const { siteConfig } = useDocusaurusContext(); + const [filteredData, setFilteredData] = useState(() => quickstartData); const [selectedTags, setSelectedTags] = useState([]); const [selectedLevel, setSelectedLevel] = useState([]); - const [searchInput, setSearchInput] = useState(''); - + const [searchInput, setSearchInput] = useState(''); + // Build meta title from quickstartTitle and docusaurus config site title - const metaTitle = `${quickstartTitle}${siteConfig?.title ? ` | ${siteConfig.title}` : ''}` + const metaTitle = `${quickstartTitle}${siteConfig?.title ? ` | ${siteConfig.title}` : ''}`; - // Array for the tag select dropdown - const tagOptions = []; - quickstartData?.forEach((guide) => { - if (guide?.data?.tags) { - guide?.data?.tags?.forEach((tag) => { - const tagOption = { value: tag, label: tag }; - if (!tagOptions.find((option) => option?.value === tag)) { - tagOptions.push(tagOption); - } - }); - } - }); + // Memoized computation of tag and level options + const tagOptions = useMemo(() => { + const tags = new Set(); + quickstartData.forEach(guide => + guide?.data?.tags?.forEach(tag => tags.add(tag)) + ); + return Array.from(tags).map(tag => ({ value: tag, label: tag })); + }, [quickstartData]); - // Array for the level select dropdown - const levelOptions = [] - quickstartData?.forEach((guide) => { - if (guide?.data?.level) { - const levelOption = { value: guide?.data?.level, label: guide?.data?.level }; - if (!levelOptions.find((option) => option?.value === guide?.data?.level)) { - levelOptions.push(levelOption); - } - } - }); + const levelOptions = useMemo(() => { + const levels = new Set(); + quickstartData.forEach(guide => + guide?.data?.level && levels.add(guide.data.level) + ); + return Array.from(levels).map(level => ({ value: level, label: level })); + }, [quickstartData]); // Handle all filters const handleDataFilter = () => { @@ -61,7 +54,7 @@ function QuickstartList({ quickstartData }) { }); setFilteredData(filteredGuides); }; - + useEffect(() => { handleDataFilter(); }, [selectedTags, selectedLevel, searchInput]); @@ -73,28 +66,27 @@ function QuickstartList({ quickstartData }) { -
- - - setSearchInput(value)} placeholder='Search Quickstarts' /> - + + + setSearchInput(value)} placeholder='Search Quickstarts' />
-
+
{filteredData && filteredData.length > 0 ? ( <> - {filteredData.map((guide, i) => ( - + {filteredData.map((guide) => ( + ))} - ) : + ) :

No quickstarts are available with the selected filters.

}
@@ -103,4 +95,4 @@ function QuickstartList({ quickstartData }) { ) } -export default QuickstartList +export default QuickstartList; From 61690ad93097c04b69bac8e50dfe908dfb1f7dd4 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 2 Oct 2023 10:23:16 -0400 Subject: [PATCH 006/152] test prod build with styles --- website/src/components/searchInput/index.js | 2 ++ .../src/components/selectDropdown/index.js | 7 +++++++ .../selectDropdown/styles.module.css | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/website/src/components/searchInput/index.js b/website/src/components/searchInput/index.js index 04d1ded7661..905c2b4131f 100644 --- a/website/src/components/searchInput/index.js +++ b/website/src/components/searchInput/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import styles from './styles.module.css'; const SearchInput = ({ value, onChange, placeholder = "Search...", ...props }) => { return ( @@ -8,6 +9,7 @@ const SearchInput = ({ value, onChange, placeholder = "Search...", ...props }) = onChange={(e) => onChange && onChange(e.target.value)} placeholder={placeholder} {...props} + className={styles.inputContainer} /> ); }; diff --git a/website/src/components/selectDropdown/index.js b/website/src/components/selectDropdown/index.js index 7169d214160..f42e1b1b3f6 100644 --- a/website/src/components/selectDropdown/index.js +++ b/website/src/components/selectDropdown/index.js @@ -3,16 +3,23 @@ import Select from "react-select"; import styles from "./styles.module.css"; export const SelectDropdown = ({ options, value, onChange, isMulti, placeHolder }) => { + + return ( Date: Tue, 3 Oct 2023 10:39:39 -0400 Subject: [PATCH 014/152] more style wips --- website/docs/quickstarts/redshift-qs.md | 1 + website/src/components/searchInput/styles.module.css | 7 +++++++ website/src/components/selectDropdown/styles.module.css | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/website/docs/quickstarts/redshift-qs.md b/website/docs/quickstarts/redshift-qs.md index 82c865ae123..04b21cf8d57 100644 --- a/website/docs/quickstarts/redshift-qs.md +++ b/website/docs/quickstarts/redshift-qs.md @@ -5,6 +5,7 @@ platform: 'dbt-cloud' icon: 'redshift' hide_table_of_contents: true tags: ['Redshift', 'dbt Cloud'] +level: 'Expert' --- ## Introduction diff --git a/website/src/components/searchInput/styles.module.css b/website/src/components/searchInput/styles.module.css index e16c1ced26c..df7d6509524 100644 --- a/website/src/components/searchInput/styles.module.css +++ b/website/src/components/searchInput/styles.module.css @@ -3,9 +3,16 @@ border-radius: 0.3125rem; border: 2px solid var(--navy-200-c-6-ccd-4, #C6CCD4); min-height: 38px; + font-size: .875rem; + font-family: var(--ifm-font-family-base); } .inputContainer:active, .inputContainer:focus { border: 2px solid #4f5d75; outline: none; } + +.inputContainer::placeholder { + all: unset; + -webkit-text-security: initial; +} diff --git a/website/src/components/selectDropdown/styles.module.css b/website/src/components/selectDropdown/styles.module.css index 84484a1e79e..7b9a82094d7 100644 --- a/website/src/components/selectDropdown/styles.module.css +++ b/website/src/components/selectDropdown/styles.module.css @@ -8,6 +8,10 @@ border: 2px solid #4f5d75; } +.selectContainer [class$="-palceholder"] { + font-size: .875rem; +} + .selectContainer [class$="-control"] { padding: 0rem 1rem } From d11be7dd2a49b05c52050fdae8c679de3de65270 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 10:48:53 -0400 Subject: [PATCH 015/152] update selectors --- website/src/components/selectDropdown/styles.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/selectDropdown/styles.module.css b/website/src/components/selectDropdown/styles.module.css index 7b9a82094d7..afe8b8d742f 100644 --- a/website/src/components/selectDropdown/styles.module.css +++ b/website/src/components/selectDropdown/styles.module.css @@ -49,7 +49,7 @@ } -.selectContainer [class$="-MultiValueRemove"] { +.selectContainer [aria-label^="Remove"] { padding: .5rem; border-radius: 0 0.3125rem 0.3125rem 0; padding-left: 4px; @@ -57,7 +57,7 @@ margin-left: .2rem; } -.selectContainer [class$="-MultiValueRemove"]:hover { +.selectContainer [aria-label^="Remove"]:hover { background-color: rgb(255, 189, 173); color: rgb(222, 53, 11); } From 8e9da30118b23226e1df12a2cdad249719a50df9 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 11:18:22 -0400 Subject: [PATCH 016/152] get data on individual pages --- .../components/quickstartGuideCard/index.js | 33 +++++++++++++++++-- .../quickstartGuideCard/styles.module.css | 4 +++ .../quickstartTOC/styles.module.css | 2 +- website/src/theme/DocItem/Content/index.js | 7 ++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index a019022d079..97bc936923d 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -3,8 +3,9 @@ import Link from "@docusaurus/Link"; import styles from "./styles.module.css"; import getIconType from "../../utils/get-icon-type"; -function QuickstartGuideCard({ frontMatter }) { - const { id, title, time_to_complete, icon, tags, level, recently_updated } = frontMatter; +export default function QuickstartGuideCard({ frontMatter }) { + const { id, title, time_to_complete, icon, tags, level, recently_updated } = + frontMatter; return ( @@ -38,4 +39,30 @@ function QuickstartGuideCard({ frontMatter }) { ); } -export default QuickstartGuideCard; +export function QuickstartGuideTitle({ frontMatter }) { + const { id, title, time_to_complete, icon, tags, level, recently_updated } = + frontMatter; + + return ( +
+ {recently_updated && ( + Updated + )} + {time_to_complete && ( + {time_to_complete} + )} + + {(tags || level) && ( +
+ {tags && + tags.map((tag, i) => ( +
+ {tag} +
+ ))} + {level &&
{level}
} +
+ )} +
+ ); +} diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index d8f60cb3a73..5d517d951ef 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -82,3 +82,7 @@ color:#262A38; padding: 0rem 0.75rem; } + +.quickstartTitle { + +} diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index edfd0380098..20a4846cf3c 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -1,5 +1,5 @@ .quickstartTitle { - padding: 1rem 0 2rem; + } .tocList { diff --git a/website/src/theme/DocItem/Content/index.js b/website/src/theme/DocItem/Content/index.js index faca59ee2f3..5a5a9df9a76 100644 --- a/website/src/theme/DocItem/Content/index.js +++ b/website/src/theme/DocItem/Content/index.js @@ -23,8 +23,10 @@ import MDXContent from "@theme/MDXContent"; */ import CommunitySpotlightCard from "@site/src/components/communitySpotlightCard"; import QuickstartTOC from "@site/src/components/quickstartTOC"; +import {QuickstartGuideTitle} from "../../../components/quickstartGuideCard"; import styles from "./styles.module.css"; + function useSyntheticTitle() { const { metadata, frontMatter, contentTitle } = useDoc(); const shouldRender = @@ -60,12 +62,17 @@ export default function DocItemContent({ children }) { {children}
) : isQuickstartGuide ? ( + <> +
+
{children}
+ + ) : ( {children} )} From 31132db2efc54e77a6f88a5d3ff2c3fd57bd03e7 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 15:13:00 -0400 Subject: [PATCH 017/152] styles wip --- website/docs/quickstarts/bigquery-qs.md | 1 + .../components/quickstartGuideCard/index.js | 7 +-- .../quickstartGuideCard/styles.module.css | 43 ++++++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/website/docs/quickstarts/bigquery-qs.md b/website/docs/quickstarts/bigquery-qs.md index 01b2cf7ba7e..ee88dc93402 100644 --- a/website/docs/quickstarts/bigquery-qs.md +++ b/website/docs/quickstarts/bigquery-qs.md @@ -7,6 +7,7 @@ icon: 'bigquery' hide_table_of_contents: true tags: ['BigQuery', 'dbt Cloud'] level: 'Beginner' +recently_updated: true --- ## Introduction diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index 97bc936923d..7c3faa3613e 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -39,17 +39,18 @@ export default function QuickstartGuideCard({ frontMatter }) { ); } +// Component that handles the information under the title on the quickstart guide page export function QuickstartGuideTitle({ frontMatter }) { - const { id, title, time_to_complete, icon, tags, level, recently_updated } = + const { time_to_complete, tags, level, recently_updated } = frontMatter; return ( -
+
{recently_updated && ( Updated )} {time_to_complete && ( - {time_to_complete} + {time_to_complete} )} {(tags || level) && ( diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index 5d517d951ef..93667fae75c 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -83,6 +83,47 @@ padding: 0rem 0.75rem; } -.quickstartTitle { +.infoContainer { + display: flex; + gap: 1rem; + margin-bottom: 4rem; +} + +.infoContainer > *:not(:first-child):not(:last-child) { + border-right: solid #262A38 3px; + border-left: solid #262A38 3px; + padding: 0 1rem 0 1rem; +} + +.infoContainer .tag_container { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + align-items: center; +} + +.infoContainer .tag_container .tag { + background: #E5E7EB; + border-radius: 1.5rem; + color:#262A38; + padding: 0rem 0.75rem; +} + +.infoContainer .time_to_complete { + font-weight: 700; + +} + +.infoContainer .recently_updated { + color: var(--color-green-blue); +} +@media (max-width: 996px) { + .infoContainer { + flex-direction: column; + } + .infoContainer > *:not(:first-child):not(:last-child) { + border: none; + padding: 0; + } } From c737d80dbb94c5d4e5ee6f99b4639eea63430366 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 15:33:19 -0400 Subject: [PATCH 018/152] continue styling --- website/src/components/quickstartTOC/styles.module.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 20a4846cf3c..57c6c79b670 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -8,14 +8,16 @@ margin: 0; width: 370px; flex-shrink: 0; - padding-right: 3rem; + padding-right: 4rem; + margin-right: 4rem; + border-right: solid 5px #E0E3E8; } .tocList li { padding: 1rem; display: block; border: 1px solid #EFF2F3; - box-shadow: 0px 11px 24px rgba(138, 138, 138, 0.1), 0px 0px 0px rgba(138, 138, 138, 0.1); + box-shadow: 0px 10px 16px 0px rgba(31, 41, 55, 0.20); border-radius: 10px; margin-bottom: 1rem; display: grid; From fb3d89c07ade30205107b61a38d564398f7e395c Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 15:56:10 -0400 Subject: [PATCH 019/152] cleanup mobile styles --- .../quickstartGuideCard/styles.module.css | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index 93667fae75c..f475ce03bc1 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -85,16 +85,19 @@ .infoContainer { display: flex; - gap: 1rem; margin-bottom: 4rem; } -.infoContainer > *:not(:first-child):not(:last-child) { - border-right: solid #262A38 3px; - border-left: solid #262A38 3px; +.infoContainer > * { + border-left: solid #e0e3e8 3px; padding: 0 1rem 0 1rem; } +.infoContainer > *:first-child { + border: none; + padding-left: 0; +} + .infoContainer .tag_container { display: flex; flex-wrap: wrap; @@ -120,9 +123,10 @@ @media (max-width: 996px) { .infoContainer { + gap: 1rem; flex-direction: column; } - .infoContainer > *:not(:first-child):not(:last-child) { + .infoContainer > * { border: none; padding: 0; } From 930f400513d2a83481fcca9775c8ca767c4f483a Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 16:03:26 -0400 Subject: [PATCH 020/152] add arrows to nav buttons --- website/src/components/quickstartTOC/index.js | 6 ++++++ website/src/components/quickstartTOC/styles.module.css | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/website/src/components/quickstartTOC/index.js b/website/src/components/quickstartTOC/index.js index 8c9b8fba910..34727d40c79 100644 --- a/website/src/components/quickstartTOC/index.js +++ b/website/src/components/quickstartTOC/index.js @@ -81,13 +81,19 @@ function QuickstartTOC() { buttonContainer.classList.add(style.buttonContainer); const prevButton = document.createElement("a"); const nextButton = document.createElement("a"); + const nextButtonIcon = document.createElement("i"); + const prevButtonIcon = document.createElement("i"); + prevButtonIcon.classList.add("fa-regular", "fa-arrow-left"); prevButton.textContent = "Back"; + prevButton.prepend(prevButtonIcon); prevButton.classList.add(clsx(style.button, style.prevButton)); prevButton.disabled = index === 0; prevButton.addEventListener("click", () => handlePrev(index + 1)); + nextButtonIcon.classList.add("fa-regular", "fa-arrow-right"); nextButton.textContent = "Next"; + nextButton.appendChild(nextButtonIcon); nextButton.classList.add(clsx(style.button, style.nextButton)); nextButton.disabled = index === stepWrappers.length - 1; nextButton.addEventListener("click", () => handleNext(index + 1)); diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 57c6c79b670..292bd4c0f43 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -90,10 +90,20 @@ html[data-theme="dark"] .tocList .active span { margin-right: auto; } +.buttonContainer .prevButton i { + font-size: .8rem; + margin-right: .4rem; +} + .buttonContainer .nextButton { margin-left: auto; } +.buttonContainer .nextButton i { + font-size: .8rem; + margin-left: .4rem; +} + .stepWrapper[data-step="1"] .nextButton { background: var(--color-light-teal); color: var(--color-white) From 2da30795103595b69983b74cacf87efaa7096bcc Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 16:17:19 -0400 Subject: [PATCH 021/152] style cleanup --- .../quickstartGuideCard/styles.module.css | 13 +++++++++++++ .../src/components/searchInput/styles.module.css | 7 ++++++- .../src/components/selectDropdown/styles.module.css | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index f475ce03bc1..3397629377a 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -44,6 +44,10 @@ color:var(--ifm-menu-color) } +[data-theme='dark'] .quickstartCard .recently_updated { + color: #fff; +} + .quickstartCard .start { font-size: 1.125rem; margin-top: auto; @@ -83,6 +87,11 @@ padding: 0rem 0.75rem; } +[data-theme='dark'] .quickstartCard .tag_container .tag { + background: #374151; + color: #fff; +} + .infoContainer { display: flex; margin-bottom: 4rem; @@ -121,6 +130,10 @@ color: var(--color-green-blue); } +[data-theme='dark'] .infoContainer .recently_updated { + color: #fff; +} + @media (max-width: 996px) { .infoContainer { gap: 1rem; diff --git a/website/src/components/searchInput/styles.module.css b/website/src/components/searchInput/styles.module.css index df7d6509524..1e3e6d617fa 100644 --- a/website/src/components/searchInput/styles.module.css +++ b/website/src/components/searchInput/styles.module.css @@ -3,7 +3,7 @@ border-radius: 0.3125rem; border: 2px solid var(--navy-200-c-6-ccd-4, #C6CCD4); min-height: 38px; - font-size: .875rem; + font-size: .975rem; font-family: var(--ifm-font-family-base); } @@ -16,3 +16,8 @@ all: unset; -webkit-text-security: initial; } + +[data-theme='dark'] .inputContainer { + background: #1b1b1d; + color: #e3e3e3; +} diff --git a/website/src/components/selectDropdown/styles.module.css b/website/src/components/selectDropdown/styles.module.css index afe8b8d742f..66cc1f4b837 100644 --- a/website/src/components/selectDropdown/styles.module.css +++ b/website/src/components/selectDropdown/styles.module.css @@ -45,8 +45,13 @@ padding: 0 0 0 .5rem; } +[data-theme='dark'] .selectContainer [class$="-multiValue"] { + background: var(--color-green-blue); + color: #fff; +} + [data-theme='dark'] .selectContainer [class$="-option"]:hover { - + background: var(--color-green-blue); } .selectContainer [aria-label^="Remove"] { From 5a6ce5fb24b14f9876c32050c75a40b1d5691715 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 23 Oct 2023 16:46:42 -0400 Subject: [PATCH 022/152] build mobile functionality --- website/src/components/quickstartTOC/index.js | 18 ++++++++++++++++++ .../components/quickstartTOC/styles.module.css | 18 ++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/website/src/components/quickstartTOC/index.js b/website/src/components/quickstartTOC/index.js index 34727d40c79..3ff5e027208 100644 --- a/website/src/components/quickstartTOC/index.js +++ b/website/src/components/quickstartTOC/index.js @@ -196,7 +196,24 @@ function QuickstartTOC() { updateStep(activeStep, stepNumber); }; + // Handle TOC menu click + const handleTocMenuClick = () => { + const tocList = document.querySelector(`.${style.tocList}`); + const tocMenuBtn = document.querySelector(`.${style.toc_menu_btn}`); + const tocListStyles = window.getComputedStyle(tocList); + + if (tocListStyles.display === "none") { + tocList.style.display = "block"; + tocMenuBtn.querySelector("i").style.transform = "rotate(0deg)"; + } else { + tocList.style.display = "none"; + tocMenuBtn.querySelector("i").style.transform = "rotate(-90deg)"; + } + }; + return ( + <> + Menu
    {tocData.map((step) => (
  • ))}
+ ); } diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 292bd4c0f43..cf044ce2e6a 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -117,12 +117,26 @@ html[data-theme="dark"] .tocList .active span { display: none; } +.toc_menu_btn { + display: none; +} + +.toc_menu_btn i { + transform: rotate(-90deg); + vertical-align: middle; +} + @media (max-width: 996px) { .tocList { width: 100%; padding-right: 0; margin-bottom: 2rem; - height: 160px; - overflow-y: auto; + display: none; + } + + .toc_menu_btn { + display: inline-block; + margin-bottom: 2rem; + cursor: pointer; } } From b248e7c6a8c430a4c534b36cdd8f913f2d35fffe Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 09:00:11 -0400 Subject: [PATCH 023/152] add some checks --- website/src/components/quickstartGuideList/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index a37ec23966a..695cebfc62c 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -23,7 +23,8 @@ function QuickstartList({ quickstartData }) { // Build meta title from quickstartTitle and docusaurus config site title const metaTitle = `${quickstartTitle}${siteConfig?.title ? ` | ${siteConfig.title}` : ''}`; - // Memoized computation of tag and level options + // UseMemo to prevent re-rendering on every filter change + // Get tag options const tagOptions = useMemo(() => { const tags = new Set(); quickstartData.forEach(guide => @@ -32,6 +33,7 @@ function QuickstartList({ quickstartData }) { return Array.from(tags).map(tag => ({ value: tag, label: tag })); }, [quickstartData]); + // Get level options const levelOptions = useMemo(() => { const levels = new Set(); quickstartData.forEach(guide => @@ -75,8 +77,12 @@ function QuickstartList({ quickstartData }) { />
- - + {tagOptions && tagOptions.length > 0 && ( + + )} + {levelOptions && levelOptions.length > 0 && ( + + )} setSearchInput(value)} placeholder='Search Quickstarts' />
From e3f512698c440408696b6361f944f33581a67bb6 Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 09:27:15 -0400 Subject: [PATCH 024/152] add search icon --- website/src/components/searchInput/index.js | 31 ++++++++++++------- .../components/searchInput/styles.module.css | 17 +++++++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/website/src/components/searchInput/index.js b/website/src/components/searchInput/index.js index 905c2b4131f..a70175742c1 100644 --- a/website/src/components/searchInput/index.js +++ b/website/src/components/searchInput/index.js @@ -1,16 +1,25 @@ -import React from 'react'; -import styles from './styles.module.css'; +import React from "react"; +import styles from "./styles.module.css"; -const SearchInput = ({ value, onChange, placeholder = "Search...", ...props }) => { +const SearchInput = ({ + value, + onChange, + placeholder = "Search...", + ...props +}) => { return ( - onChange && onChange(e.target.value)} - placeholder={placeholder} - {...props} - className={styles.inputContainer} - /> + ); }; diff --git a/website/src/components/searchInput/styles.module.css b/website/src/components/searchInput/styles.module.css index 1e3e6d617fa..1171b045106 100644 --- a/website/src/components/searchInput/styles.module.css +++ b/website/src/components/searchInput/styles.module.css @@ -2,21 +2,28 @@ padding: 0 1rem; border-radius: 0.3125rem; border: 2px solid var(--navy-200-c-6-ccd-4, #C6CCD4); - min-height: 38px; - font-size: .975rem; - font-family: var(--ifm-font-family-base); + + } -.inputContainer:active, .inputContainer:focus { +.inputContainer:active, .input:focus { border: 2px solid #4f5d75; outline: none; } -.inputContainer::placeholder { +.input::placeholder { all: unset; -webkit-text-security: initial; } +.inputContainer .input { + border: none; + min-height: 38px; + font-size: .975rem; + color: var(--ifm-font-color-base); + font-family: var(--ifm-font-family-base); +} + [data-theme='dark'] .inputContainer { background: #1b1b1d; color: #e3e3e3; From b743a9307304f37f790ca6359064b0731ac55a11 Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 09:33:04 -0400 Subject: [PATCH 025/152] update dark mode styles --- .../src/components/quickstartGuideCard/styles.module.css | 6 ++++++ website/src/components/quickstartTOC/styles.module.css | 2 +- website/src/components/searchInput/styles.module.css | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index 3397629377a..4c6f17bff79 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -121,6 +121,12 @@ padding: 0rem 0.75rem; } +[data-theme='dark'] .infoContainer .tag_container .tag { + background: #374151; + color: #fff; +} + + .infoContainer .time_to_complete { font-weight: 700; diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index cf044ce2e6a..1979f2d4c04 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -16,7 +16,7 @@ .tocList li { padding: 1rem; display: block; - border: 1px solid #EFF2F3; + border: 2px solid #EFF2F3; box-shadow: 0px 10px 16px 0px rgba(31, 41, 55, 0.20); border-radius: 10px; margin-bottom: 1rem; diff --git a/website/src/components/searchInput/styles.module.css b/website/src/components/searchInput/styles.module.css index 1171b045106..ae19a3bb81b 100644 --- a/website/src/components/searchInput/styles.module.css +++ b/website/src/components/searchInput/styles.module.css @@ -24,7 +24,7 @@ font-family: var(--ifm-font-family-base); } -[data-theme='dark'] .inputContainer { +[data-theme='dark'] .input{ background: #1b1b1d; color: #e3e3e3; } From fd262007228ae5ffc60d6602b5155748a01d3a7a Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 09:34:19 -0400 Subject: [PATCH 026/152] add some comments --- website/src/components/quickstartGuideList/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 695cebfc62c..98a1eef1d1b 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -25,6 +25,7 @@ function QuickstartList({ quickstartData }) { // UseMemo to prevent re-rendering on every filter change // Get tag options + // Populated from the tags frontmatter array const tagOptions = useMemo(() => { const tags = new Set(); quickstartData.forEach(guide => @@ -34,6 +35,7 @@ function QuickstartList({ quickstartData }) { }, [quickstartData]); // Get level options + // Populated by the level frontmatter string const levelOptions = useMemo(() => { const levels = new Set(); quickstartData.forEach(guide => From 247e94a6391c888b096e6c9c5e28b99d8364297b Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 09:41:59 -0400 Subject: [PATCH 027/152] sort by alphabetically --- website/src/components/quickstartGuideList/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 98a1eef1d1b..55d0b24176e 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -31,7 +31,8 @@ function QuickstartList({ quickstartData }) { quickstartData.forEach(guide => guide?.data?.tags?.forEach(tag => tags.add(tag)) ); - return Array.from(tags).map(tag => ({ value: tag, label: tag })); + // Sort alphabetically + return Array.from(tags).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).map(tag => ({ value: tag, label: tag })); }, [quickstartData]); // Get level options From 8d74fccbaca0dee4b6295e247ae58e59e0a34f1c Mon Sep 17 00:00:00 2001 From: john-rock Date: Tue, 24 Oct 2023 10:14:42 -0400 Subject: [PATCH 028/152] update package json --- website/package-lock.json | 534 -------------------------------------- website/package.json | 1 - 2 files changed, 535 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 0e907bfd99f..282056e5922 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -74,7 +74,6 @@ "stream-http": "^3.2.0", "style-loader": "^1.1.3", "svg-inline-loader": "^0.8.2", - "tailwindcss": "^3.3.3", "tty-browserify": "0.0.1", "webpack": "^5.75.0", "webpack-dev-server": "^4.11.1" @@ -234,18 +233,6 @@ "@algolia/requester-common": "4.16.0" } }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -7801,12 +7788,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -10720,12 +10701,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, "node_modules/diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -10761,12 +10736,6 @@ "node": ">=8" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -16254,15 +16223,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, "node_modules/joi": { "version": "17.8.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", @@ -18100,17 +18060,6 @@ "multicast-dns": "cli.js" } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nano-memoize": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-1.3.1.tgz", @@ -18568,15 +18517,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -19404,80 +19344,6 @@ "postcss": "^8.2.15" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - }, - "engines": { - "node": ">= 14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/postcss-loader": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", @@ -19748,25 +19614,6 @@ "url": "https://opencollective.com/postcss/" } }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, "node_modules/postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", @@ -21102,15 +20949,6 @@ "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, "node_modules/readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -23262,71 +23100,6 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, - "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23622,64 +23395,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.18.2", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -23848,27 +23563,6 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -24041,12 +23735,6 @@ "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, "node_modules/ts-keycode-enum": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ts-keycode-enum/-/ts-keycode-enum-1.0.6.tgz", @@ -25946,12 +25634,6 @@ "@algolia/requester-common": "4.16.0" } }, - "@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true - }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -31658,12 +31340,6 @@ "color-convert": "^2.0.1" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -33864,12 +33540,6 @@ } } }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, "diff-sequences": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", @@ -33901,12 +33571,6 @@ "path-type": "^4.0.0" } }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -38024,12 +37688,6 @@ } } }, - "jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", - "dev": true - }, "joi": { "version": "17.8.3", "resolved": "https://registry.npmjs.org/joi/-/joi-17.8.3.tgz", @@ -39327,17 +38985,6 @@ "thunky": "^1.0.2" } }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "nano-memoize": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/nano-memoize/-/nano-memoize-1.3.1.tgz", @@ -39679,12 +39326,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true - }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -40278,44 +39919,6 @@ "postcss-selector-parser": "^6.0.5" } }, - "postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - }, - "dependencies": { - "yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true - } - } - }, "postcss-loader": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", @@ -40504,15 +40107,6 @@ } } }, - "postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.11" - } - }, "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", @@ -41469,15 +41063,6 @@ "react-is": "^17.0.1 || ^18.0.0" } }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, "readable-stream": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", @@ -43101,54 +42686,6 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, - "sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -43365,53 +42902,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, - "requires": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.2.12", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.18.2", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "dependencies": { - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - } - } - }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -43526,24 +43016,6 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "throat": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", @@ -43677,12 +43149,6 @@ "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==" }, - "ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, "ts-keycode-enum": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ts-keycode-enum/-/ts-keycode-enum-1.0.6.tgz", diff --git a/website/package.json b/website/package.json index 5dee165966f..b0105102359 100644 --- a/website/package.json +++ b/website/package.json @@ -80,7 +80,6 @@ "stream-http": "^3.2.0", "style-loader": "^1.1.3", "svg-inline-loader": "^0.8.2", - "tailwindcss": "^3.3.3", "tty-browserify": "0.0.1", "webpack": "^5.75.0", "webpack-dev-server": "^4.11.1" From 769905b940eff51fdd686301c36336524b827793 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 10:21:01 -0400 Subject: [PATCH 029/152] change quickstart dir to guides --- website/docs/{quickstarts => guides}/bigquery-qs.md | 0 website/docs/{quickstarts => guides}/codespace-qs.md | 0 website/docs/{quickstarts => guides}/databricks-qs.md | 0 .../docs/{quickstarts => guides}/manual-install-qs.md | 0 website/docs/{quickstarts => guides}/redshift-qs.md | 0 website/docs/{quickstarts => guides}/snowflake-qs.md | 0 .../docs/{quickstarts => guides}/starburst-galaxy-qs.md | 0 website/plugins/buildQuickstartIndexPage/index.js | 9 ++++++--- 8 files changed, 6 insertions(+), 3 deletions(-) rename website/docs/{quickstarts => guides}/bigquery-qs.md (100%) rename website/docs/{quickstarts => guides}/codespace-qs.md (100%) rename website/docs/{quickstarts => guides}/databricks-qs.md (100%) rename website/docs/{quickstarts => guides}/manual-install-qs.md (100%) rename website/docs/{quickstarts => guides}/redshift-qs.md (100%) rename website/docs/{quickstarts => guides}/snowflake-qs.md (100%) rename website/docs/{quickstarts => guides}/starburst-galaxy-qs.md (100%) diff --git a/website/docs/quickstarts/bigquery-qs.md b/website/docs/guides/bigquery-qs.md similarity index 100% rename from website/docs/quickstarts/bigquery-qs.md rename to website/docs/guides/bigquery-qs.md diff --git a/website/docs/quickstarts/codespace-qs.md b/website/docs/guides/codespace-qs.md similarity index 100% rename from website/docs/quickstarts/codespace-qs.md rename to website/docs/guides/codespace-qs.md diff --git a/website/docs/quickstarts/databricks-qs.md b/website/docs/guides/databricks-qs.md similarity index 100% rename from website/docs/quickstarts/databricks-qs.md rename to website/docs/guides/databricks-qs.md diff --git a/website/docs/quickstarts/manual-install-qs.md b/website/docs/guides/manual-install-qs.md similarity index 100% rename from website/docs/quickstarts/manual-install-qs.md rename to website/docs/guides/manual-install-qs.md diff --git a/website/docs/quickstarts/redshift-qs.md b/website/docs/guides/redshift-qs.md similarity index 100% rename from website/docs/quickstarts/redshift-qs.md rename to website/docs/guides/redshift-qs.md diff --git a/website/docs/quickstarts/snowflake-qs.md b/website/docs/guides/snowflake-qs.md similarity index 100% rename from website/docs/quickstarts/snowflake-qs.md rename to website/docs/guides/snowflake-qs.md diff --git a/website/docs/quickstarts/starburst-galaxy-qs.md b/website/docs/guides/starburst-galaxy-qs.md similarity index 100% rename from website/docs/quickstarts/starburst-galaxy-qs.md rename to website/docs/guides/starburst-galaxy-qs.md diff --git a/website/plugins/buildQuickstartIndexPage/index.js b/website/plugins/buildQuickstartIndexPage/index.js index 4724478883a..da50e230dca 100644 --- a/website/plugins/buildQuickstartIndexPage/index.js +++ b/website/plugins/buildQuickstartIndexPage/index.js @@ -6,10 +6,13 @@ module.exports = function buildQuickstartIndexPage() { name: 'docusaurus-build-quickstart-index-page-plugin', async loadContent() { // Quickstart files directory - const quickstartDirectory = 'docs/quickstarts' + const quickstartDirectory = 'docs/guides' // Get all Quickstart files and content - const quickstartFiles = fs.readdirSync(quickstartDirectory) + const quickstartFiles = fs.readdirSync(quickstartDirectory, { withFileTypes: true }) + .filter(dirent => dirent.isFile()) + .map(dirent => dirent.name) + const quickstartData = quickstartFiles.reduce((arr, quickstartFile) => { const fileData = fs.readFileSync( @@ -53,7 +56,7 @@ module.exports = function buildQuickstartIndexPage() { // Build the quickstart index page addRoute({ - path: `/quickstarts`, + path: `/guides`, component: '@site/src/components/quickstartGuideList/index.js', modules: { // propName -> JSON file path From c9f85daedc808f5fe81d1ea3188c546415b2ec63 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 10:35:13 -0400 Subject: [PATCH 030/152] migration work for guides --- website/plugins/buildQuickstartIndexPage/index.js | 6 +++++- website/src/components/quickstartGuideCard/index.js | 4 ++-- website/src/components/searchInput/index.js | 4 ++-- website/src/theme/DocItem/Content/index.js | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/website/plugins/buildQuickstartIndexPage/index.js b/website/plugins/buildQuickstartIndexPage/index.js index da50e230dca..5139ca3bcd7 100644 --- a/website/plugins/buildQuickstartIndexPage/index.js +++ b/website/plugins/buildQuickstartIndexPage/index.js @@ -22,8 +22,12 @@ module.exports = function buildQuickstartIndexPage() { if(!fileData) return null - // convert frontmatter to json + // Convert frontmatter to json const fileJson = matter(fileData) + + // Add the original directory to build links + fileJson.data.original_directory = quickstartDirectory.replace('docs/', '') + if(!fileJson) return null diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index 7c3faa3613e..a8760bcc883 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -8,7 +8,7 @@ export default function QuickstartGuideCard({ frontMatter }) { frontMatter; return ( - + {recently_updated && ( Updated )} @@ -20,7 +20,7 @@ export default function QuickstartGuideCard({ frontMatter }) { {time_to_complete} )} - + Start diff --git a/website/src/components/searchInput/index.js b/website/src/components/searchInput/index.js index a70175742c1..e0a5faf4a82 100644 --- a/website/src/components/searchInput/index.js +++ b/website/src/components/searchInput/index.js @@ -8,8 +8,8 @@ const SearchInput = ({ ...props }) => { return ( -
From e2c7f487b83f883ee4d87596f8fa182138f9fe01 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:00:47 -0400 Subject: [PATCH 033/152] try fixing merge conflict --- website/docs/quickstarts/bigquery-qs.md | 301 ++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 website/docs/quickstarts/bigquery-qs.md diff --git a/website/docs/quickstarts/bigquery-qs.md b/website/docs/quickstarts/bigquery-qs.md new file mode 100644 index 00000000000..5f0b641875f --- /dev/null +++ b/website/docs/quickstarts/bigquery-qs.md @@ -0,0 +1,301 @@ +--- +title: "Quickstart for dbt Cloud and BigQuery" +id: "bigquery" +time_to_complete: '30 minutes' +platform: 'dbt-cloud' +icon: 'bigquery' +hide_table_of_contents: true +tags: ['BigQuery', 'dbt Cloud'] +level: 'Beginner' +recently_updated: true +--- + +## Introduction + +In this quickstart guide, you'll learn how to use dbt Cloud with BigQuery. It will show you how to: + +- Create a Google Cloud Platform (GCP) project. +- Access sample data in a public dataset. +- Connect dbt Cloud to BigQuery. +- Take a sample query and turn it into a model in your dbt project. A model in dbt is a select statement. +- Add tests to your models. +- Document your models. +- Schedule a job to run. + +:::tip Videos for you +You can check out [dbt Fundamentals](https://courses.getdbt.com/courses/fundamentals) for free if you're interested in course learning with videos. + +::: + +### Prerequisites​ + +- You have a [dbt Cloud account](https://www.getdbt.com/signup/). +- You have a [Google account](https://support.google.com/accounts/answer/27441?hl=en). +- You can use a personal or work account to set up BigQuery through [Google Cloud Platform (GCP)](https://cloud.google.com/free). + +### Related content + +- Learn more with [dbt Courses](https://courses.getdbt.com/collections) +- [CI jobs](/docs/deploy/continuous-integration) +- [Deploy jobs](/docs/deploy/deploy-jobs) +- [Job notifications](/docs/deploy/job-notifications) +- [Source freshness](/docs/deploy/source-freshness) + +## Create a new GCP project​ + +1. Go to the [BigQuery Console](https://console.cloud.google.com/bigquery) after you log in to your Google account. If you have multiple Google accounts, make sure you’re using the correct one. +2. Create a new project from the [Manage resources page](https://console.cloud.google.com/projectcreate?previousPage=%2Fcloud-resource-manager%3Fwalkthrough_id%3Dresource-manager--create-project%26project%3D%26folder%3D%26organizationId%3D%23step_index%3D1&walkthrough_id=resource-manager--create-project). For more information, refer to [Creating a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project) in the Google Cloud docs. GCP automatically populates the Project name field for you. You can change it to be more descriptive for your use. For example, `dbt Learn - BigQuery Setup`. + +## Create BigQuery datasets + +1. From the [BigQuery Console](https://console.cloud.google.com/bigquery), click **Editor**. Make sure to select your newly created project, which is available at the top of the page. +1. Verify that you can run SQL queries. Copy and paste these queries into the Query Editor: + ```sql + select * from `dbt-tutorial.jaffle_shop.customers`; + select * from `dbt-tutorial.jaffle_shop.orders`; + select * from `dbt-tutorial.stripe.payment`; + ``` + + Click **Run**, then check for results from the queries. For example: +
+ +
+2. Create new datasets from the [BigQuery Console](https://console.cloud.google.com/bigquery). For more information, refer to [Create datasets](https://cloud.google.com/bigquery/docs/datasets#create-dataset) in the Google Cloud docs. Datasets in BigQuery are equivalent to schemas in a traditional database. On the **Create dataset** page: + - **Dataset ID** — Enter a name that fits the purpose. This name is used like schema in fully qualified references to your database objects such as `database.schema.table`. As an example for this guide, create one for `jaffle_shop` and another one for `stripe` afterward. + - **Data location** — Leave it blank (the default). It determines the GCP location of where your data is stored. The current default location is the US multi-region. All tables within this dataset will share this location. + - **Enable table expiration** — Leave it unselected (the default). The default for the billing table expiration is 60 days. Because billing isn’t enabled for this project, GCP defaults to deprecating tables. + - **Google-managed encryption key** — This option is available under **Advanced options**. Allow Google to manage encryption (the default). +
+ +
+3. After you create the `jaffle_shop` dataset, create one for `stripe` with all the same values except for **Dataset ID**. + +## Generate BigQuery credentials {#generate-bigquery-credentials} +In order to let dbt connect to your warehouse, you'll need to generate a keyfile. This is analogous to using a database username and password with most other data warehouses. + +1. Start the [GCP credentials wizard](https://console.cloud.google.com/apis/credentials/wizard). Make sure your new project is selected in the header. If you do not see your account or project, click your profile picture to the right and verify you are using the correct email account. For **Credential Type**: + - From the **Select an API** dropdown, choose **BigQuery API** + - Select **Application data** for the type of data you will be accessing + - Click **Next** to create a new service account. +2. Create a service account for your new project from the [Service accounts page](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project). For more information, refer to [Create a service account](https://developers.google.com/workspace/guides/create-credentials#create_a_service_account) in the Google Cloud docs. As an example for this guide, you can: + - Type `dbt-user` as the **Service account name** + - From the **Select a role** dropdown, choose **BigQuery Admin** and click **Continue** + - Leave the **Grant users access to this service account** fields blank + - Click **Done** +3. Create a service account key for your new project from the [Service accounts page](https://console.cloud.google.com/iam-admin/serviceaccounts?walkthrough_id=iam--create-service-account-keys&start_index=1#step_index=1). For more information, refer to [Create a service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) in the Google Cloud docs. When downloading the JSON file, make sure to use a filename you can easily remember. For example, `dbt-user-creds.json`. For security reasons, dbt Labs recommends that you protect this JSON file like you would your identity credentials; for example, don't check the JSON file into your version control software. + +## Connect dbt Cloud to BigQuery​ +1. Create a new project in [dbt Cloud](https://cloud.getdbt.com/). From **Account settings** (using the gear menu in the top right corner), click **+ New Project**. +2. Enter a project name and click **Continue**. +3. For the warehouse, click **BigQuery** then **Next** to set up your connection. +4. Click **Upload a Service Account JSON File** in settings. +5. Select the JSON file you downloaded in [Generate BigQuery credentials](#generate-bigquery-credentials) and dbt Cloud will fill in all the necessary fields. +6. Click **Test Connection**. This verifies that dbt Cloud can access your BigQuery account. +7. Click **Next** if the test succeeded. If it failed, you might need to go back and regenerate your BigQuery credentials. + + +## Set up a dbt Cloud managed repository + + + +## Initialize your dbt project​ and start developing +Now that you have a repository configured, you can initialize your project and start development in dbt Cloud: + +1. Click **Start developing in the IDE**. It might take a few minutes for your project to spin up for the first time as it establishes your git connection, clones your repo, and tests the connection to the warehouse. +2. Above the file tree to the left, click **Initialize dbt project**. This builds out your folder structure with example models. +3. Make your initial commit by clicking **Commit and sync**. Use the commit message `initial commit` and click **Commit**. This creates the first commit to your managed repo and allows you to open a branch where you can add new dbt code. +4. You can now directly query data from your warehouse and execute `dbt run`. You can try this out now: + - Click **+ Create new file**, add this query to the new file, and click **Save as** to save the new file: + ```sql + select * from `dbt-tutorial.jaffle_shop.customers` + ``` + - In the command line bar at the bottom, enter `dbt run` and click **Enter**. You should see a `dbt run succeeded` message. + +## Build your first model +1. Under **Version Control** on the left, click **Create branch**. You can name it `add-customers-model`. You need to create a new branch since the main branch is set to read-only mode. +3. Click the **...** next to the `models` directory, then select **Create file**. +4. Name the file `customers.sql`, then click **Create**. +5. Copy the following query into the file and click **Save**. + +```sql +with customers as ( + + select + id as customer_id, + first_name, + last_name + + from `dbt-tutorial`.jaffle_shop.customers + +), + +orders as ( + + select + id as order_id, + user_id as customer_id, + order_date, + status + + from `dbt-tutorial`.jaffle_shop.orders + +), + +customer_orders as ( + + select + customer_id, + + min(order_date) as first_order_date, + max(order_date) as most_recent_order_date, + count(order_id) as number_of_orders + + from orders + + group by 1 + +), + +final as ( + + select + customers.customer_id, + customers.first_name, + customers.last_name, + customer_orders.first_order_date, + customer_orders.most_recent_order_date, + coalesce(customer_orders.number_of_orders, 0) as number_of_orders + + from customers + + left join customer_orders using (customer_id) + +) + +select * from final +``` + +6. Enter `dbt run` in the command prompt at the bottom of the screen. You should get a successful run and see the three models. + +Later, you can connect your business intelligence (BI) tools to these views and tables so they only read cleaned up data rather than raw data in your BI tool. + +#### FAQs + + + + + + + +## Change the way your model is materialized + + + +## Delete the example models + + + +## Build models on top of other models + + + +1. Create a new SQL file, `models/stg_customers.sql`, with the SQL from the `customers` CTE in our original query. +2. Create a second new SQL file, `models/stg_orders.sql`, with the SQL from the `orders` CTE in our original query. + + + + ```sql + select + id as customer_id, + first_name, + last_name + + from `dbt-tutorial`.jaffle_shop.customers + ``` + + + + + + ```sql + select + id as order_id, + user_id as customer_id, + order_date, + status + + from `dbt-tutorial`.jaffle_shop.orders + ``` + + + +3. Edit the SQL in your `models/customers.sql` file as follows: + + + + ```sql + with customers as ( + + select * from {{ ref('stg_customers') }} + + ), + + orders as ( + + select * from {{ ref('stg_orders') }} + + ), + + customer_orders as ( + + select + customer_id, + + min(order_date) as first_order_date, + max(order_date) as most_recent_order_date, + count(order_id) as number_of_orders + + from orders + + group by 1 + + ), + + final as ( + + select + customers.customer_id, + customers.first_name, + customers.last_name, + customer_orders.first_order_date, + customer_orders.most_recent_order_date, + coalesce(customer_orders.number_of_orders, 0) as number_of_orders + + from customers + + left join customer_orders using (customer_id) + + ) + + select * from final + + ``` + + + +4. Execute `dbt run`. + + This time, when you performed a `dbt run`, separate views/tables were created for `stg_customers`, `stg_orders` and `customers`. dbt inferred the order to run these models. Because `customers` depends on `stg_customers` and `stg_orders`, dbt builds `customers` last. You do not need to explicitly define these dependencies. + +#### FAQs {#faq-2} + + + + + + + + + + + From 4da2077edf25c0875fefd1a574844d6d5e34d659 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:02:46 -0400 Subject: [PATCH 034/152] update broken markdown links --- website/docs/docs/build/projects.md | 2 +- .../release-notes/03-Oct-2023/product-docs-sept-rn.md | 2 +- .../release-notes/09-April-2023/product-docs.md | 2 +- .../10-Mar-2023/public-preview-trino-in-dbt-cloud.md | 2 +- website/docs/docs/introduction.md | 2 +- .../materializations-guide-6-examining-builds.md | 2 +- website/docs/guides/codespace-qs.md | 8 ++++---- .../how-to-set-up-your-databricks-dbt-project.md | 8 ++++---- .../productionizing-your-dbt-databricks-project.md | 2 +- website/docs/guides/manual-install-qs.md | 2 +- .../migrating-from-stored-procedures/2-mapping-inserts.md | 2 +- .../2-setting-up-airflow-and-dbt-cloud.md | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/website/docs/docs/build/projects.md b/website/docs/docs/build/projects.md index b4b04e3334d..150879da4be 100644 --- a/website/docs/docs/build/projects.md +++ b/website/docs/docs/build/projects.md @@ -93,4 +93,4 @@ If you want to see what a mature, production project looks like, check out the [ ## Related docs * [Best practices: How we structure our dbt projects](/guides/best-practices/how-we-structure/1-guide-overview) * [Quickstarts for dbt Cloud](/quickstarts) -* [Quickstart for dbt Core](/quickstarts/manual-install) +* [Quickstart for dbt Core](/guides/manual-install) diff --git a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md index e669b037d17..42a2c8daba1 100644 --- a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md +++ b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md @@ -27,7 +27,7 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/): - Deprecated dbt Core v1.0 and v1.1 from the docs. - Added configuration instructions for the [AWS Glue](/docs/core/connect-data-platform/glue-setup) community plugin. -- Revised the dbt Core quickstart, making it easier to follow. Divided this guide into steps that align with the [other guides](/quickstarts/manual-install?step=1). +- Revised the dbt Core quickstart, making it easier to follow. Divided this guide into steps that align with the [other guides](/guides/manual-install?step=1). ## New 📚 Guides, ✏️ blog posts, and FAQs diff --git a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md index d30bcf85b99..faa8517cfbd 100644 --- a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md +++ b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md @@ -17,7 +17,7 @@ Hello from the dbt Docs team: @mirnawong1, @matthewshaver, @nghi-ly, and @runleo ## ☁ Cloud projects - Added Starburst/Trino adapter docs, including: - * [dbt Cloud quickstart guide](/quickstarts/starburst-galaxy),  + * [dbt Cloud quickstart guide](/guides/starburst-galaxy),  * [connection page](/docs/cloud/connect-data-platform/connect-starburst-trino),  * [set up page](/docs/core/connect-data-platform/trino-setup), and [config page](/reference/resource-configs/trino-configs). - Enhanced [dbt Cloud jobs page](/docs/deploy/jobs) and section to include conceptual info on the queue time, improvements made around it, and about failed jobs. diff --git a/website/docs/docs/dbt-versions/release-notes/10-Mar-2023/public-preview-trino-in-dbt-cloud.md b/website/docs/docs/dbt-versions/release-notes/10-Mar-2023/public-preview-trino-in-dbt-cloud.md index bf3840a8b02..06abf178b8a 100644 --- a/website/docs/docs/dbt-versions/release-notes/10-Mar-2023/public-preview-trino-in-dbt-cloud.md +++ b/website/docs/docs/dbt-versions/release-notes/10-Mar-2023/public-preview-trino-in-dbt-cloud.md @@ -8,7 +8,7 @@ tags: [Mar-2023] dbt Labs is introducing the newest connection option in dbt Cloud: the `dbt-trino` adapter is now available in Public Preview. This allows you to connect to Starburst Galaxy, Starburst Enterprise, and self-hosted Trino from dbt Cloud. -Check out our [Quickstart for dbt Cloud and Starburst Galaxy](/quickstarts/starburst-galaxy) to explore more. +Check out our [Quickstart for dbt Cloud and Starburst Galaxy](/guides/starburst-galaxy) to explore more. ## What’s the reason users should be excited about this? diff --git a/website/docs/docs/introduction.md b/website/docs/docs/introduction.md index 0aeef0201cb..efe050d3205 100644 --- a/website/docs/docs/introduction.md +++ b/website/docs/docs/introduction.md @@ -43,7 +43,7 @@ Learn more about [dbt Cloud features](/docs/cloud/about-cloud/dbt-cloud-features ### dbt Core -dbt Core is an open-source tool that enables data teams to transform data using analytics engineering best practices. You can install and use dbt Core on the command line. Learn more with the [quickstart for dbt Core](/quickstarts/codespace). +dbt Core is an open-source tool that enables data teams to transform data using analytics engineering best practices. You can install and use dbt Core on the command line. Learn more with the [quickstart for dbt Core](/guides/codespace). ## The power of dbt diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md b/website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md index 07811b42594..ee160d2a7ad 100644 --- a/website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md +++ b/website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md @@ -16,7 +16,7 @@ hoverSnippet: Read this guide to understand how to examine your builds in dbt. ### Model Timing -That’s where dbt Cloud’s Model Timing visualization comes in extremely handy. If we’ve set up a [Job](/quickstarts/bigquery) in dbt Cloud to run our models, we can use the Model Timing tab to pinpoint our longest-running models. +That’s where dbt Cloud’s Model Timing visualization comes in extremely handy. If we’ve set up a [Job](/guides/bigquery) in dbt Cloud to run our models, we can use the Model Timing tab to pinpoint our longest-running models. ![dbt Cloud's Model Timing diagram](/img/guides/best-practices/materializations/model-timing-diagram.png) diff --git a/website/docs/guides/codespace-qs.md b/website/docs/guides/codespace-qs.md index f2671335ddc..f30d82457a8 100644 --- a/website/docs/guides/codespace-qs.md +++ b/website/docs/guides/codespace-qs.md @@ -20,10 +20,10 @@ dbt Labs provides a [GitHub Codespace](https://docs.github.com/en/codespaces/ove ## Related content -- [Create a GitHub repository](/quickstarts/manual-install?step=2) -- [Build your first models](/quickstarts/manual-install?step=3) -- [Test and document your project](/quickstarts/manual-install?step=4) -- [Schedule a job](/quickstarts/manual-install?step=5) +- [Create a GitHub repository](/guides/manual-install?step=2) +- [Build your first models](/guides/manual-install?step=3) +- [Test and document your project](/guides/manual-install?step=4) +- [Schedule a job](/guides/manual-install?step=5) - Learn more with [dbt Courses](https://courses.getdbt.com/collections) ## Create a codespace diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md index b0be39a4273..ba66bba60d1 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md @@ -57,11 +57,11 @@ Now that the Databricks components are in place, we can configure our dbt projec If you are migrating an existing dbt project from the dbt-spark adapter to dbt-databricks, follow this [migration guide](https://docs.getdbt.com/guides/migration/tools/migrating-from-spark-to-databricks#migration) to switch adapters without needing to update developer credentials and other existing configs. -If you’re starting a new dbt project, follow the steps below. For a more detailed setup flow, check out our [quickstart guide.](/quickstarts/databricks) +If you’re starting a new dbt project, follow the steps below. For a more detailed setup flow, check out our [quickstart guide.](/guides/databricks) ### Connect dbt to Databricks -First, you’ll need to connect your dbt project to Databricks so it can send transformation instructions and build objects in Unity Catalog. Follow the instructions for [dbt Cloud](/quickstarts/databricks?step=4) or [Core](https://docs.getdbt.com/reference/warehouse-setups/databricks-setup) to configure your project’s connection credentials. +First, you’ll need to connect your dbt project to Databricks so it can send transformation instructions and build objects in Unity Catalog. Follow the instructions for [dbt Cloud](/guides/databricks?step=4) or [Core](https://docs.getdbt.com/reference/warehouse-setups/databricks-setup) to configure your project’s connection credentials. Each developer must generate their Databricks PAT and use the token in their development credentials. They will also specify a unique developer schema that will store the tables and views generated by dbt runs executed from their IDE. This provides isolated developer environments and ensures data access is fit for purpose. @@ -84,7 +84,7 @@ During your first invocation of `dbt run`, dbt will create the developer schema Last, we need to give dbt a way to deploy code outside of development environments. To do so, we’ll use dbt [environments](https://docs.getdbt.com/docs/collaborate/environments) to define the production targets that end users will interact with. -Core projects can use [targets in profiles](https://docs.getdbt.com/docs/core/connection-profiles#understanding-targets-in-profiles) to separate environments. [dbt Cloud environments](https://docs.getdbt.com/docs/cloud/develop-in-the-cloud#set-up-and-access-the-cloud-ide) allow you to define environments via the UI and [schedule jobs](/quickstarts/databricks#create-and-run-a-job) for specific environments. +Core projects can use [targets in profiles](https://docs.getdbt.com/docs/core/connection-profiles#understanding-targets-in-profiles) to separate environments. [dbt Cloud environments](https://docs.getdbt.com/docs/cloud/develop-in-the-cloud#set-up-and-access-the-cloud-ide) allow you to define environments via the UI and [schedule jobs](/guides/databricks#create-and-run-a-job) for specific environments. Let’s set up our deployment environment: @@ -96,7 +96,7 @@ Let’s set up our deployment environment: ### Connect dbt to your git repository -Next, you’ll need somewhere to store and version control your code that allows you to collaborate with teammates. Connect your dbt project to a git repository with [dbt Cloud](/quickstarts/databricks#set-up-a-dbt-cloud-managed-repository). [Core](/quickstarts/manual-install#create-a-repository) projects will use the git CLI. +Next, you’ll need somewhere to store and version control your code that allows you to collaborate with teammates. Connect your dbt project to a git repository with [dbt Cloud](/guides/databricks#set-up-a-dbt-cloud-managed-repository). [Core](/guides/manual-install#create-a-repository) projects will use the git CLI. ## Next steps diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md b/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md index a3b4be5a051..35c5d852d74 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md @@ -184,5 +184,5 @@ To get the most out of both tools, you can use the [persist docs config](/refere - [Advanced deployments course](https://courses.getdbt.com/courses/advanced-deployment) if you want a deeper dive into these topics - [Autoscaling CI: The intelligent Slim CI](https://docs.getdbt.com/blog/intelligent-slim-ci) - [Trigger a dbt Cloud Job in your automated workflow with Python](https://discourse.getdbt.com/t/triggering-a-dbt-cloud-job-in-your-automated-workflow-with-python/2573) -- [Databricks + dbt Cloud Quickstart Guide](/quickstarts/databricks) +- [Databricks + dbt Cloud Quickstart Guide](/guides/databricks) - Reach out to your Databricks account team to get access to preview features on Databricks. diff --git a/website/docs/guides/manual-install-qs.md b/website/docs/guides/manual-install-qs.md index 2444cf29d7e..ee07bc846c2 100644 --- a/website/docs/guides/manual-install-qs.md +++ b/website/docs/guides/manual-install-qs.md @@ -15,7 +15,7 @@ When you use dbt Core to work with dbt, you will be editing files locally using * To use dbt Core, it's important that you know some basics of the Terminal. In particular, you should understand `cd`, `ls` and `pwd` to navigate through the directory structure of your computer easily. * Install dbt Core using the [installation instructions](/docs/core/installation) for your operating system. -* Complete [Setting up (in BigQuery)](/quickstarts/bigquery?step=2) and [Loading data (BigQuery)](/quickstarts/bigquery?step=3). +* Complete [Setting up (in BigQuery)](/guides/bigquery?step=2) and [Loading data (BigQuery)](/guides/bigquery?step=3). * [Create a GitHub account](https://github.com/join) if you don't already have one. ### Create a starter project diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md index d8f31a0f14a..44e01784f04 100644 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md +++ b/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md @@ -13,7 +13,7 @@ INSERT INTO returned_orders (order_id, order_date, total_return) SELECT order_id, order_date, total FROM orders WHERE type = 'return' ``` -Converting this with a first pass to a [dbt model](/quickstarts/bigquery?step=8) (in a file called returned_orders.sql) might look something like: +Converting this with a first pass to a [dbt model](/guides/bigquery?step=8) (in a file called returned_orders.sql) might look something like: ```sql SELECT diff --git a/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md b/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md index 9c3b8eb7f1b..01b15440920 100644 --- a/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md +++ b/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md @@ -77,7 +77,7 @@ Create a service token from within dbt Cloud using the instructions [found here] ## 6. Create a dbt Cloud job -In your dbt Cloud account create a job, paying special attention to the information in the bullets below. Additional information for creating a dbt Cloud job can be found [here](/quickstarts/bigquery). +In your dbt Cloud account create a job, paying special attention to the information in the bullets below. Additional information for creating a dbt Cloud job can be found [here](/guides/bigquery). - Configure the job with the commands that you want to include when this job kicks off, as Airflow will be referring to the job’s configurations for this rather than being explicitly coded in the Airflow DAG. This job will run a set of commands rather than a single command. - Ensure that the schedule is turned **off** since we’ll be using Airflow to kick things off. From baf15a0ef351c9ba3fe432177532e0dbec94b1e2 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:09:00 -0400 Subject: [PATCH 035/152] update another md link --- contributing/adding-page-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing/adding-page-components.md b/contributing/adding-page-components.md index 751f7c1f6c1..a07d0ff02e4 100644 --- a/contributing/adding-page-components.md +++ b/contributing/adding-page-components.md @@ -1,6 +1,6 @@ ## Using warehouse components -You can use the following components to provide code snippets for each supported warehouse. You can see a real-life example in the docs page [Initialize your project](/quickstarts/databricks?step=6). +You can use the following components to provide code snippets for each supported warehouse. You can see a real-life example in the docs page [Initialize your project](/guides/databricks?step=6). Identify code by labeling with the warehouse names: From 7698abfe122024673eaf32250b61f71de246c8f3 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:11:23 -0400 Subject: [PATCH 036/152] update more md links --- website/blog/2022-02-07-customer-360-view-census-playbook.md | 2 +- website/docs/community/resources/getting-help.md | 4 ++-- website/docs/docs/build/models.md | 2 +- website/docs/docs/build/projects.md | 4 ++-- website/docs/docs/build/sql-models.md | 2 +- website/docs/docs/build/tests.md | 2 +- website/docs/docs/cloud/about-cloud-develop.md | 2 +- website/docs/docs/cloud/about-cloud-setup.md | 2 +- website/docs/docs/cloud/about-cloud/about-dbt-cloud.md | 2 +- website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md | 2 +- website/docs/docs/collaborate/documentation.md | 2 +- .../release-notes/08-May-2023/product-docs-may.md | 2 +- website/docs/docs/deploy/deployment-tools.md | 2 +- website/docs/docs/introduction.md | 4 ++-- website/docs/docs/use-dbt-semantic-layer/dbt-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md | 2 +- website/docs/faqs/Models/create-dependencies.md | 2 +- website/docs/faqs/Project/example-projects.md | 2 +- .../how-we-build-our-metrics/semantic-layer-2-setup.md | 2 +- .../materializations-guide-1-guide-overview.md | 2 +- website/docs/guides/manual-install-qs.md | 2 +- website/docs/guides/migration/tools/refactoring-legacy-sql.md | 2 +- 22 files changed, 25 insertions(+), 25 deletions(-) diff --git a/website/blog/2022-02-07-customer-360-view-census-playbook.md b/website/blog/2022-02-07-customer-360-view-census-playbook.md index 01bea4b09c5..71acb32fe94 100644 --- a/website/blog/2022-02-07-customer-360-view-census-playbook.md +++ b/website/blog/2022-02-07-customer-360-view-census-playbook.md @@ -30,7 +30,7 @@ In short, a jaffle is: *See above: Tasty, tasty jaffles.* -Jaffle Shop is a demo repo referenced in [dbt’s Getting Started Guide](/quickstarts), and its jaffles hold a special place in the dbt community’s hearts, as well as on Data Twitter™. +Jaffle Shop is a demo repo referenced in [dbt’s Getting Started Guide](/guides), and its jaffles hold a special place in the dbt community’s hearts, as well as on Data Twitter™. ![jaffles on data twitter](/img/blog/2022-02-08-customer-360-view/image_1.png) diff --git a/website/docs/community/resources/getting-help.md b/website/docs/community/resources/getting-help.md index 5f423683014..953d04434c4 100644 --- a/website/docs/community/resources/getting-help.md +++ b/website/docs/community/resources/getting-help.md @@ -7,7 +7,7 @@ dbt is open source, and has a generous community behind it. Asking questions wel ### 1. Try to solve your problem first before asking for help #### Search the existing documentation -The docs site you're on is highly searchable, make sure to explore for the answer here as a first step. If you're new to dbt, try working through the [quickstart guide](/quickstarts) first to get a firm foundation on the essential concepts. +The docs site you're on is highly searchable, make sure to explore for the answer here as a first step. If you're new to dbt, try working through the [quickstart guide](/guides) first to get a firm foundation on the essential concepts. #### Try to debug the issue yourself We have a handy guide on [debugging errors](/guides/best-practices/debugging-errors) to help out! This guide also helps explain why errors occur, and which docs you might need to search for help. @@ -60,4 +60,4 @@ If you want to receive dbt training, check out our [dbt Learn](https://learn.get - Billing - Bug reports related to the web interface -As a rule of thumb, if you are using dbt Cloud, but your problem is related to code within your dbt project, then please follow the above process rather than reaching out to support. \ No newline at end of file +As a rule of thumb, if you are using dbt Cloud, but your problem is related to code within your dbt project, then please follow the above process rather than reaching out to support. diff --git a/website/docs/docs/build/models.md b/website/docs/docs/build/models.md index d10eb5ed01a..1cf2fbafeda 100644 --- a/website/docs/docs/build/models.md +++ b/website/docs/docs/build/models.md @@ -20,4 +20,4 @@ The top level of a dbt workflow is the project. A project is a directory of a `. Your organization may need only a few models, but more likely you’ll need a complex structure of nested models to transform the required data. A model is a single file containing a final `select` statement, and a project can have multiple models, and models can even reference each other. Add to that, numerous projects and the level of effort required for transforming complex data sets can improve drastically compared to older methods. -Learn more about models in [SQL models](/docs/build/sql-models) and [Python models](/docs/build/python-models) pages. If you'd like to begin with a bit of practice, visit our [Getting Started Guide](/quickstarts) for instructions on setting up the Jaffle_Shop sample data so you can get hands-on with the power of dbt. +Learn more about models in [SQL models](/docs/build/sql-models) and [Python models](/docs/build/python-models) pages. If you'd like to begin with a bit of practice, visit our [Getting Started Guide](/guides) for instructions on setting up the Jaffle_Shop sample data so you can get hands-on with the power of dbt. diff --git a/website/docs/docs/build/projects.md b/website/docs/docs/build/projects.md index 150879da4be..eeed8e52f90 100644 --- a/website/docs/docs/build/projects.md +++ b/website/docs/docs/build/projects.md @@ -79,7 +79,7 @@ After configuring the Project subdirectory option, dbt Cloud will use it as the You can create new projects and [share them](/docs/collaborate/git-version-control) with other people by making them available on a hosted git repository like GitHub, GitLab, and BitBucket. -After you set up a connection with your data platform, you can [initialize your new project in dbt Cloud](/quickstarts) and start developing. Or, run [dbt init from the command line](/reference/commands/init) to set up your new project. +After you set up a connection with your data platform, you can [initialize your new project in dbt Cloud](/guides) and start developing. Or, run [dbt init from the command line](/reference/commands/init) to set up your new project. During project initialization, dbt creates sample model files in your project directory to help you start developing quickly. @@ -92,5 +92,5 @@ If you want to see what a mature, production project looks like, check out the [ ## Related docs * [Best practices: How we structure our dbt projects](/guides/best-practices/how-we-structure/1-guide-overview) -* [Quickstarts for dbt Cloud](/quickstarts) +* [Quickstarts for dbt Cloud](/guides) * [Quickstart for dbt Core](/guides/manual-install) diff --git a/website/docs/docs/build/sql-models.md b/website/docs/docs/build/sql-models.md index 65fdd58adf0..237ac84c0c2 100644 --- a/website/docs/docs/build/sql-models.md +++ b/website/docs/docs/build/sql-models.md @@ -14,7 +14,7 @@ id: "sql-models" :::info Building your first models -If you're new to dbt, we recommend that you read a [quickstart guide](/quickstarts) to build your first dbt project with models. +If you're new to dbt, we recommend that you read a [quickstart guide](/guides) to build your first dbt project with models. ::: diff --git a/website/docs/docs/build/tests.md b/website/docs/docs/build/tests.md index 75ee5992a76..6de5bcdc471 100644 --- a/website/docs/docs/build/tests.md +++ b/website/docs/docs/build/tests.md @@ -30,7 +30,7 @@ There are two ways of defining tests in dbt: Defining tests is a great way to confirm that your code is working correctly, and helps prevent regressions when your code changes. Because you can use them over and over again, making similar assertions with minor variations, generic tests tend to be much more common—they should make up the bulk of your dbt testing suite. That said, both ways of defining tests have their time and place. :::tip Creating your first tests -If you're new to dbt, we recommend that you check out our [quickstart guide](/quickstarts) to build your first dbt project with models and tests. +If you're new to dbt, we recommend that you check out our [quickstart guide](/guides) to build your first dbt project with models and tests. ::: ## Singular tests diff --git a/website/docs/docs/cloud/about-cloud-develop.md b/website/docs/docs/cloud/about-cloud-develop.md index 9f864ede5ca..90abbb98bf4 100644 --- a/website/docs/docs/cloud/about-cloud-develop.md +++ b/website/docs/docs/cloud/about-cloud-develop.md @@ -25,7 +25,7 @@ dbt Cloud offers a fast and reliable way to work on your dbt project. It runs db

-The following sections provide detailed instructions on setting up the dbt Cloud CLI and dbt Cloud IDE. To get started with dbt development, you'll need a [developer](/docs/cloud/manage-access/seats-and-users) account. For a more comprehensive guide about developing in dbt, refer to our [quickstart guides](/quickstarts). +The following sections provide detailed instructions on setting up the dbt Cloud CLI and dbt Cloud IDE. To get started with dbt development, you'll need a [developer](/docs/cloud/manage-access/seats-and-users) account. For a more comprehensive guide about developing in dbt, refer to our [quickstart guides](/guides). --------- diff --git a/website/docs/docs/cloud/about-cloud-setup.md b/website/docs/docs/cloud/about-cloud-setup.md index 7b68b52a45a..5c8e5525bf1 100644 --- a/website/docs/docs/cloud/about-cloud-setup.md +++ b/website/docs/docs/cloud/about-cloud-setup.md @@ -16,7 +16,7 @@ dbt Cloud is the fastest and most reliable way to deploy your dbt jobs. It conta - Configuring the [dbt Cloud IDE](/docs/cloud/about-cloud-develop) - Installing and configuring the [dbt Cloud CLI](/docs/cloud/cloud-cli-installation) -These settings are intended for dbt Cloud administrators. If you need a more detailed first-time setup guide for specific data platforms, read our [quickstart guides](/quickstarts). +These settings are intended for dbt Cloud administrators. If you need a more detailed first-time setup guide for specific data platforms, read our [quickstart guides](/guides). If you want a more in-depth learning experience, we recommend taking the dbt Fundamentals on our [dbt Learn online courses site](https://courses.getdbt.com/). diff --git a/website/docs/docs/cloud/about-cloud/about-dbt-cloud.md b/website/docs/docs/cloud/about-cloud/about-dbt-cloud.md index 71f3175a108..518efe56a8b 100644 --- a/website/docs/docs/cloud/about-cloud/about-dbt-cloud.md +++ b/website/docs/docs/cloud/about-cloud/about-dbt-cloud.md @@ -99,6 +99,6 @@ dbt Cloud's [flexible plans](https://www.getdbt.com/pricing/) and features make ## Related docs - [dbt Cloud plans and pricing](https://www.getdbt.com/pricing/) -- [Quickstart guides](/quickstarts) +- [Quickstart guides](/guides) - [dbt Cloud IDE](/docs/cloud/dbt-cloud-ide/develop-in-the-cloud) diff --git a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md index 39db7832d79..ad0d5466714 100644 --- a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md +++ b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md @@ -59,6 +59,6 @@ There are default keyboard shortcuts that can help make development more product ## Related docs -- [Quickstart guide](/quickstarts) +- [Quickstart guide](/guides) - [About dbt Cloud](/docs/cloud/about-cloud/dbt-cloud-features) - [Develop in the Cloud](/docs/cloud/dbt-cloud-ide/develop-in-the-cloud) diff --git a/website/docs/docs/collaborate/documentation.md b/website/docs/docs/collaborate/documentation.md index 0fa00c7cca2..16a4e610c70 100644 --- a/website/docs/docs/collaborate/documentation.md +++ b/website/docs/docs/collaborate/documentation.md @@ -11,7 +11,7 @@ pagination_prev: null * [Declaring properties](/reference/configs-and-properties) * [`dbt docs` command](/reference/commands/cmd-docs) * [`doc` Jinja function](/reference/dbt-jinja-functions) -* If you're new to dbt, we recommend that you check out our [quickstart guide](/quickstarts) to build your first dbt project, complete with documentation. +* If you're new to dbt, we recommend that you check out our [quickstart guide](/guides) to build your first dbt project, complete with documentation. ## Assumed knowledge diff --git a/website/docs/docs/dbt-versions/release-notes/08-May-2023/product-docs-may.md b/website/docs/docs/dbt-versions/release-notes/08-May-2023/product-docs-may.md index 762a6a723f8..a692c901a80 100644 --- a/website/docs/docs/dbt-versions/release-notes/08-May-2023/product-docs-may.md +++ b/website/docs/docs/dbt-versions/release-notes/08-May-2023/product-docs-may.md @@ -16,7 +16,7 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/) in May: - We made sure everyone knows that Cloud-users don’t need a [profiles.yml file](/docs/core/connect-data-platform/profiles.yml) by adding a callout on several key pages. - Fleshed out the [model jinja variable page](/reference/dbt-jinja-functions/model), which originally lacked conceptual info and didn’t link to the schema page. -- Added a new [Quickstarts landing page](/quickstarts). This new format sets up for future iterations that will include filtering! But for now, we are excited you can step through quickstarts in a focused way. +- Added a new [Quickstarts landing page](/guides). This new format sets up for future iterations that will include filtering! But for now, we are excited you can step through quickstarts in a focused way. ## ☁ Cloud projects diff --git a/website/docs/docs/deploy/deployment-tools.md b/website/docs/docs/deploy/deployment-tools.md index 6fba9caf6e8..9769a2d24a8 100644 --- a/website/docs/docs/deploy/deployment-tools.md +++ b/website/docs/docs/deploy/deployment-tools.md @@ -133,7 +133,7 @@ For more info, refer to the guide on [Databricks workflows and dbt Cloud jobs](/ ## Related docs - [dbt Cloud plans and pricing](https://www.getdbt.com/pricing/) -- [Quickstart guides](/quickstarts) +- [Quickstart guides](/guides) - [Webhooks for your jobs](/docs/deploy/webhooks) - [Orchestration guides](https://docs.getdbt.com/guides/orchestration) - [Commands for your production deployment](https://discourse.getdbt.com/t/what-are-the-dbt-commands-you-run-in-your-production-deployment-of-dbt/366) diff --git a/website/docs/docs/introduction.md b/website/docs/docs/introduction.md index efe050d3205..5eeec43c0d5 100644 --- a/website/docs/docs/introduction.md +++ b/website/docs/docs/introduction.md @@ -39,7 +39,7 @@ You can learn about plans and pricing on [www.getdbt.com](https://www.getdbt.com ### dbt Cloud dbt Cloud is the fastest and most reliable way to deploy dbt. Develop, test, schedule, and investigate data models all in one web-based UI. It also natively supports developing using a command line with the [dbt Cloud CLI](/docs/cloud/cloud-cli-installation). -Learn more about [dbt Cloud features](/docs/cloud/about-cloud/dbt-cloud-features) and try one of the [dbt Cloud quickstarts](/quickstarts). +Learn more about [dbt Cloud features](/docs/cloud/about-cloud/dbt-cloud-features) and try one of the [dbt Cloud quickstarts](/guides). ### dbt Core @@ -62,7 +62,7 @@ As a dbt user, your main focus will be on writing models (i.e. select queries) t ### Related docs -- [Quickstarts for dbt](/quickstarts) +- [Quickstarts for dbt](/guides) - [Best practice guides](/guides/best-practices) - [What is a dbt Project?](/docs/build/projects) - [dbt run](/docs/running-a-dbt-project/run-your-dbt-projects) diff --git a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md index 8868c68ed20..bc1cf3e05be 100644 --- a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md @@ -99,7 +99,7 @@ The dbt Semantic Layer reduces code duplication and inconsistency regarding your :::info 📌 -New to dbt or metrics? Check out our [quickstart guide](/quickstarts) to build your first dbt project! If you'd like to define your first metrics, try our [Jaffle Shop](https://github.com/dbt-labs/jaffle_shop_metrics) example project. +New to dbt or metrics? Check out our [quickstart guide](/guides) to build your first dbt project! If you'd like to define your first metrics, try our [Jaffle Shop](https://github.com/dbt-labs/jaffle_shop_metrics) example project. ::: diff --git a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md index d0e5df18d94..5177685dffd 100644 --- a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md @@ -136,7 +136,7 @@ To use the dbt Semantic Layer, you’ll need to meet the following: :::info 📌 -New to dbt or metrics? Check out our [quickstart guide](/quickstarts) to build your first dbt project! If you'd like to define your first metrics, try our [Jaffle Shop](https://github.com/dbt-labs/jaffle_shop_metrics) example project. +New to dbt or metrics? Check out our [quickstart guide](/guides) to build your first dbt project! If you'd like to define your first metrics, try our [Jaffle Shop](https://github.com/dbt-labs/jaffle_shop_metrics) example project. ::: diff --git a/website/docs/faqs/Models/create-dependencies.md b/website/docs/faqs/Models/create-dependencies.md index 6a01aa18dca..e902d93b018 100644 --- a/website/docs/faqs/Models/create-dependencies.md +++ b/website/docs/faqs/Models/create-dependencies.md @@ -44,4 +44,4 @@ Found 2 models, 28 tests, 0 snapshots, 0 analyses, 130 macros, 0 operations, 0 s Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2 ``` -To learn more about building a dbt project, we recommend you complete the [quickstart guide](/quickstarts). +To learn more about building a dbt project, we recommend you complete the [quickstart guide](/guides). diff --git a/website/docs/faqs/Project/example-projects.md b/website/docs/faqs/Project/example-projects.md index f59d6e56e78..cd58c8832e2 100644 --- a/website/docs/faqs/Project/example-projects.md +++ b/website/docs/faqs/Project/example-projects.md @@ -8,7 +8,7 @@ id: example-projects Yes! -* **Quickstart Tutorial:** You can build your own example dbt project in the [quickstart guide](/quickstarts) +* **Quickstart Tutorial:** You can build your own example dbt project in the [quickstart guide](/guides) * **Jaffle Shop:** A demonstration project (closely related to the tutorial) for a fictional ecommerce store ([source code](https://github.com/dbt-labs/jaffle_shop)) * **MRR Playbook:** A demonstration project that models subscription revenue ([source code](https://github.com/dbt-labs/mrr-playbook), [docs](https://www.getdbt.com/mrr-playbook/#!/overview)) * **Attribution Playbook:** A demonstration project that models marketing attribution ([source code](https://github.com/dbt-labs/attribution-playbook), [docs](https://www.getdbt.com/attribution-playbook/#!/overview)) diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md b/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md index 801227924dd..ffbd78b939c 100644 --- a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md +++ b/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md @@ -33,7 +33,7 @@ Lastly, to get to the pre-Semantic Layer starting state, checkout the `start-her git checkout start-here ``` -For more information, refer to the [MetricFlow commands](/docs/build/metricflow-commands) or a [quickstart](/quickstarts) to get more familiar with setting up a dbt project. +For more information, refer to the [MetricFlow commands](/docs/build/metricflow-commands) or a [quickstart](/guides) to get more familiar with setting up a dbt project. ## Basic commands diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md b/website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md index 209041b1df5..467d58ce4a9 100644 --- a/website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md +++ b/website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md @@ -26,7 +26,7 @@ By the end of this guide you should have a solid understanding of: ### Prerequisites -- 📒 You’ll want to have worked through the [quickstart guide](/quickstarts) and have a project setup to work through these concepts. +- 📒 You’ll want to have worked through the [quickstart guide](/guides) and have a project setup to work through these concepts. - 🏃🏻‍♀️ Concepts like dbt runs, `ref()` statements, and models should be familiar to you. - 🔧 [**Optional**] Reading through the [How we structure our dbt projects](guides/best-practices/how-we-structure/1-guide-overview) Guide will be beneficial for the last section of this guide, when we review best practices for materializations using the dbt project approach of staging models and marts. diff --git a/website/docs/guides/manual-install-qs.md b/website/docs/guides/manual-install-qs.md index ee07bc846c2..fb91d4678f0 100644 --- a/website/docs/guides/manual-install-qs.md +++ b/website/docs/guides/manual-install-qs.md @@ -9,7 +9,7 @@ hide_table_of_contents: true --- ## Introduction -When you use dbt Core to work with dbt, you will be editing files locally using a code editor, and running projects using a command line interface (CLI). If you'd rather edit files and run projects using the web-based Integrated Development Environment (IDE), you should refer to the [dbt Cloud quickstarts](/quickstarts). You can also develop and run dbt commands using the [dbt Cloud CLI](/docs/cloud/cloud-cli-installation) — a dbt Cloud powered command line. +When you use dbt Core to work with dbt, you will be editing files locally using a code editor, and running projects using a command line interface (CLI). If you'd rather edit files and run projects using the web-based Integrated Development Environment (IDE), you should refer to the [dbt Cloud quickstarts](/guides). You can also develop and run dbt commands using the [dbt Cloud CLI](/docs/cloud/cloud-cli-installation) — a dbt Cloud powered command line. ### Prerequisites diff --git a/website/docs/guides/migration/tools/refactoring-legacy-sql.md b/website/docs/guides/migration/tools/refactoring-legacy-sql.md index d9acfea6dab..7026e4e14aa 100644 --- a/website/docs/guides/migration/tools/refactoring-legacy-sql.md +++ b/website/docs/guides/migration/tools/refactoring-legacy-sql.md @@ -38,7 +38,7 @@ To get going, you'll copy your legacy SQL query into your dbt project, by saving Once you've copied it over, you'll want to `dbt run` to execute the query and populate the in your warehouse. -If this is your first time running dbt, you may want to start with the [Introduction to dbt](/docs/introduction) and the earlier sections of the [quickstart guide](/quickstarts) before diving into refactoring. +If this is your first time running dbt, you may want to start with the [Introduction to dbt](/docs/introduction) and the earlier sections of the [quickstart guide](/guides) before diving into refactoring. This step may sound simple, but if you're porting over an existing set of SQL transformations to a new SQL dialect, you will need to consider how your legacy SQL dialect differs from your new SQL flavor, and you may need to modify your legacy code to get it to run at all. From b769c30851fd64b6ed163bb89acbe801f0c77390 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:17:42 -0400 Subject: [PATCH 037/152] updating more broken links --- ...023-04-18-building-a-kimball-dimensional-model-with-dbt.md | 2 +- .../docs/cloud/connect-data-platform/about-connections.md | 2 +- website/docs/docs/core/about-core-setup.md | 2 +- .../docs/core/connect-data-platform/about-core-connections.md | 2 +- .../release-notes/09-April-2023/starburst-trino-ga.md | 2 +- website/sidebars.js | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/website/blog/2023-04-18-building-a-kimball-dimensional-model-with-dbt.md b/website/blog/2023-04-18-building-a-kimball-dimensional-model-with-dbt.md index ffc0369a908..3ca1f6ac2a9 100644 --- a/website/blog/2023-04-18-building-a-kimball-dimensional-model-with-dbt.md +++ b/website/blog/2023-04-18-building-a-kimball-dimensional-model-with-dbt.md @@ -62,7 +62,7 @@ Before you can get started: - You must have Python 3.8 or above installed - You must have dbt version 1.3.0 or above installed - You should have a basic understanding of [SQL](https://www.sqltutorial.org/) -- You should have a basic understanding of [dbt](https://docs.getdbt.com/quickstarts) +- You should have a basic understanding of [dbt](https://docs.getdbt.com/guides) ### Step 2: Clone the repository diff --git a/website/docs/docs/cloud/connect-data-platform/about-connections.md b/website/docs/docs/cloud/connect-data-platform/about-connections.md index 1fe89c7273c..1329d179900 100644 --- a/website/docs/docs/cloud/connect-data-platform/about-connections.md +++ b/website/docs/docs/cloud/connect-data-platform/about-connections.md @@ -23,7 +23,7 @@ You can connect to your database in dbt Cloud by clicking the gear in the top ri -These connection instructions provide the basic fields required for configuring a data platform connection in dbt Cloud. For more detailed guides, which include demo project data, read our [Quickstart guides](https://docs.getdbt.com/quickstarts) +These connection instructions provide the basic fields required for configuring a data platform connection in dbt Cloud. For more detailed guides, which include demo project data, read our [Quickstart guides](https://docs.getdbt.com/guides) ## IP Restrictions diff --git a/website/docs/docs/core/about-core-setup.md b/website/docs/docs/core/about-core-setup.md index a4d5ff09ee3..64e7694b793 100644 --- a/website/docs/docs/core/about-core-setup.md +++ b/website/docs/docs/core/about-core-setup.md @@ -16,4 +16,4 @@ dbt Core is an [open-source](https://github.com/dbt-labs/dbt-core) tool that ena - [Connecting to a data platform](/docs/core/connect-data-platform/profiles.yml) - [How to run your dbt projects](/docs/running-a-dbt-project/run-your-dbt-projects) -If you need a more detailed first-time setup guide for specific data platforms, read our [quickstart guides](https://docs.getdbt.com/quickstarts). +If you need a more detailed first-time setup guide for specific data platforms, read our [quickstart guides](https://docs.getdbt.com/guides). diff --git a/website/docs/docs/core/connect-data-platform/about-core-connections.md b/website/docs/docs/core/connect-data-platform/about-core-connections.md index a85a32cc031..492e5ae878a 100644 --- a/website/docs/docs/core/connect-data-platform/about-core-connections.md +++ b/website/docs/docs/core/connect-data-platform/about-core-connections.md @@ -22,7 +22,7 @@ dbt communicates with a number of different data platforms by using a dedicated Data platforms supported in dbt Core may be verified or unverified, and maintained by dbt Labs, partners, or community members. -These connection instructions provide the basic fields required for configuring a data platform connection in dbt Cloud. For more detailed guides, which include demo project data, read our [Quickstart guides](https://docs.getdbt.com/docs/quickstarts/overview) +These connection instructions provide the basic fields required for configuring a data platform connection in dbt Cloud. For more detailed guides, which include demo project data, read our [Quickstart guides](https://docs.getdbt.com/docs/guides) ## Connection profiles diff --git a/website/docs/docs/dbt-versions/release-notes/09-April-2023/starburst-trino-ga.md b/website/docs/docs/dbt-versions/release-notes/09-April-2023/starburst-trino-ga.md index 613a0c02432..708d51f0a44 100644 --- a/website/docs/docs/dbt-versions/release-notes/09-April-2023/starburst-trino-ga.md +++ b/website/docs/docs/dbt-versions/release-notes/09-April-2023/starburst-trino-ga.md @@ -8,5 +8,5 @@ tags: [Apr-2023] The Starburst (Trino compatible) connection is now generally available in dbt Cloud. This means you can now use dbt Cloud to connect with Starburst Galaxy, Starburst Enterprise, and self-hosted Trino. This feature is powered by the [`dbt-trino`](https://github.com/starburstdata/dbt-trino) adapter. -To learn more, check out our Quickstart guide for [dbt Cloud and Starburst Galaxy](https://docs.getdbt.com/quickstarts/starburst-galaxy). +To learn more, check out our Quickstart guide for [dbt Cloud and Starburst Galaxy](https://docs.getdbt.com/guides/starburst-galaxy). diff --git a/website/sidebars.js b/website/sidebars.js index fc87c1142c7..84cddaebbfa 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -29,8 +29,8 @@ const sidebarSettings = { }, // About dbt Cloud directory { type: "link", - label: "Quickstarts", - href: `/quickstarts`, + label: "Guides", + href: `/guides`, }, { type: "category", From 5b6fd92b1a1a95d4069df19efa0b60c91a0a9241 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:18:43 -0400 Subject: [PATCH 038/152] more more more --- contributing/content-style-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing/content-style-guide.md b/contributing/content-style-guide.md index eaa090a00b6..e17575cc234 100644 --- a/contributing/content-style-guide.md +++ b/contributing/content-style-guide.md @@ -360,7 +360,7 @@ Otherwise, the text will appear squished and provide users with a bad experience - ``: creates 5 columns cards (use sparingly) - You can't create cards with 6 or more columns as that would provide users a poor experience. -Refer to [dbt Cloud features](/docs/cloud/about-cloud/dbt-cloud-features) and [Quickstarts](/docs/quickstarts/overview) as examples. +Refer to [dbt Cloud features](/docs/cloud/about-cloud/dbt-cloud-features) and [Quickstarts](/docs/guides) as examples. ### Create cards From 8002ac16d696f944ea337d45208e7672bcaa6f96 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 11:25:59 -0400 Subject: [PATCH 039/152] update links on the cards --- website/src/components/quickstartGuideCard/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index a8760bcc883..1d29dcefa51 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -8,7 +8,7 @@ export default function QuickstartGuideCard({ frontMatter }) { frontMatter; return ( - + {recently_updated && ( Updated )} @@ -20,7 +20,7 @@ export default function QuickstartGuideCard({ frontMatter }) { {time_to_complete} )} - + Start From 9e5b6adc2870f1332d5a4dfd579ae9f0d882ec53 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 12:05:30 -0400 Subject: [PATCH 040/152] update button color --- website/src/components/quickstartTOC/styles.module.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 492f919f662..8a3885c34ad 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -83,6 +83,10 @@ html[data-theme="dark"] .tocList .active span { .stepWrapper .buttonContainer a:hover { background: var(--color-light-teal); + color: var(--color-white); +} + +html[data-theme="dark"] .stepWrapper .buttonContainer a:hover { color: var(--color-white) !important; } @@ -104,8 +108,12 @@ html[data-theme="dark"] .tocList .active span { margin-left: .4rem; } -.stepWrapper[data-step="1"] .nextButton { +.stepWrapper[data-step="1"] a.nextButton { background: var(--color-light-teal); + color: var(--color-white); +} + +html[data-theme="dark"] .stepWrapper[data-step="1"] a.nextButton { color: var(--color-white) !important; } From 17da4a02d8c0a441cb634818ece3846f1120b4ea Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 13:10:21 -0400 Subject: [PATCH 041/152] update redirects --- website/vercel.json | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/website/vercel.json b/website/vercel.json index 14b3a0a6af0..314f7471158 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -4021,6 +4021,41 @@ "source": "/reference/resource-properties/access", "destination": "/reference/resource-configs/access", "permanent": true + }, + { + "source": "/quickstarts/bigquery", + "destination": "/guides/bigquery", + "permanent": true + }, + { + "source": "/quickstarts/databricks", + "destination": "/guides/databricks", + "permanent": true + }, + { + "source": "/quickstarts/redshift", + "destination": "/guides/redshift", + "permanent": true + }, + { + "source": "/quickstarts/snowflake", + "destination": "/guides/snowflake", + "permanent": true + }, + { + "source": "/quickstarts/starburst-galaxy", + "destination": "/guides/starburst-galaxy", + "permanent": true + }, + { + "source": "/quickstarts/codespace", + "destination": "/guides/codespace", + "permanent": true + }, + { + "source": "/quickstarts/manual-install", + "destination": "/guides/manual-install", + "permanent": true } ] } From 493c47afecbf1a0008d710ad06a931769ed178e6 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 13:23:32 -0400 Subject: [PATCH 042/152] add another redirect --- website/vercel.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/vercel.json b/website/vercel.json index 314f7471158..990794d5ee7 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -4022,6 +4022,11 @@ "destination": "/reference/resource-configs/access", "permanent": true }, + { + "source": "/quickstarts", + "destination": "/guides", + "permanent": true + }, { "source": "/quickstarts/bigquery", "destination": "/guides/bigquery", From 493198c7d605f15254bce239211d54f6cedeed9d Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 14:36:52 -0400 Subject: [PATCH 043/152] remove old quickstart --- website/docs/quickstarts/bigquery-qs.md | 298 ------------------------ 1 file changed, 298 deletions(-) delete mode 100644 website/docs/quickstarts/bigquery-qs.md diff --git a/website/docs/quickstarts/bigquery-qs.md b/website/docs/quickstarts/bigquery-qs.md deleted file mode 100644 index 7960e6819de..00000000000 --- a/website/docs/quickstarts/bigquery-qs.md +++ /dev/null @@ -1,298 +0,0 @@ ---- -title: "Quickstart for dbt Cloud and BigQuery" -id: "bigquery" -time_to_complete: '30 minutes' -platform: 'dbt-cloud' -icon: 'bigquery' -hide_table_of_contents: true -tags: ['BigQuery', 'dbt Cloud'] -level: 'Beginner' -recently_updated: true ---- - -## Introduction - -In this quickstart guide, you'll learn how to use dbt Cloud with BigQuery. It will show you how to: - -- Create a Google Cloud Platform (GCP) project. -- Access sample data in a public dataset. -- Connect dbt Cloud to BigQuery. -- Take a sample query and turn it into a model in your dbt project. A model in dbt is a select statement. -- Add tests to your models. -- Document your models. -- Schedule a job to run. - -:::tip Videos for you -You can check out [dbt Fundamentals](https://courses.getdbt.com/courses/fundamentals) for free if you're interested in course learning with videos. - -::: - -### Prerequisites​ - -- You have a [dbt Cloud account](https://www.getdbt.com/signup/). -- You have a [Google account](https://support.google.com/accounts/answer/27441?hl=en). -- You can use a personal or work account to set up BigQuery through [Google Cloud Platform (GCP)](https://cloud.google.com/free). - -### Related content - -- Learn more with [dbt Courses](https://courses.getdbt.com/collections) -- [CI jobs](/docs/deploy/continuous-integration) -- [Deploy jobs](/docs/deploy/deploy-jobs) -- [Job notifications](/docs/deploy/job-notifications) -- [Source freshness](/docs/deploy/source-freshness) - -## Create a new GCP project​ - -1. Go to the [BigQuery Console](https://console.cloud.google.com/bigquery) after you log in to your Google account. If you have multiple Google accounts, make sure you’re using the correct one. -2. Create a new project from the [Manage resources page](https://console.cloud.google.com/projectcreate?previousPage=%2Fcloud-resource-manager%3Fwalkthrough_id%3Dresource-manager--create-project%26project%3D%26folder%3D%26organizationId%3D%23step_index%3D1&walkthrough_id=resource-manager--create-project). For more information, refer to [Creating a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project) in the Google Cloud docs. GCP automatically populates the Project name field for you. You can change it to be more descriptive for your use. For example, `dbt Learn - BigQuery Setup`. - -## Create BigQuery datasets - -1. From the [BigQuery Console](https://console.cloud.google.com/bigquery), click **Editor**. Make sure to select your newly created project, which is available at the top of the page. -1. Verify that you can run SQL queries. Copy and paste these queries into the Query Editor: - ```sql - select * from `dbt-tutorial.jaffle_shop.customers`; - select * from `dbt-tutorial.jaffle_shop.orders`; - select * from `dbt-tutorial.stripe.payment`; - ``` - - Click **Run**, then check for results from the queries. For example: -
- -
-2. Create new datasets from the [BigQuery Console](https://console.cloud.google.com/bigquery). For more information, refer to [Create datasets](https://cloud.google.com/bigquery/docs/datasets#create-dataset) in the Google Cloud docs. Datasets in BigQuery are equivalent to schemas in a traditional database. On the **Create dataset** page: - - **Dataset ID** — Enter a name that fits the purpose. This name is used like schema in fully qualified references to your database objects such as `database.schema.table`. As an example for this guide, create one for `jaffle_shop` and another one for `stripe` afterward. - - **Data location** — Leave it blank (the default). It determines the GCP location of where your data is stored. The current default location is the US multi-region. All tables within this dataset will share this location. - - **Enable table expiration** — Leave it unselected (the default). The default for the billing table expiration is 60 days. Because billing isn’t enabled for this project, GCP defaults to deprecating tables. - - **Google-managed encryption key** — This option is available under **Advanced options**. Allow Google to manage encryption (the default). -
- -
-3. After you create the `jaffle_shop` dataset, create one for `stripe` with all the same values except for **Dataset ID**. - -## Generate BigQuery credentials {#generate-bigquery-credentials} -In order to let dbt connect to your warehouse, you'll need to generate a keyfile. This is analogous to using a database username and password with most other data warehouses. - -1. Start the [GCP credentials wizard](https://console.cloud.google.com/apis/credentials/wizard). Make sure your new project is selected in the header. If you do not see your account or project, click your profile picture to the right and verify you are using the correct email account. For **Credential Type**: - - From the **Select an API** dropdown, choose **BigQuery API** - - Select **Application data** for the type of data you will be accessing - - Click **Next** to create a new service account. -2. Create a service account for your new project from the [Service accounts page](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project). For more information, refer to [Create a service account](https://developers.google.com/workspace/guides/create-credentials#create_a_service_account) in the Google Cloud docs. As an example for this guide, you can: - - Type `dbt-user` as the **Service account name** - - From the **Select a role** dropdown, choose **BigQuery Admin** and click **Continue** - - Leave the **Grant users access to this service account** fields blank - - Click **Done** -3. Create a service account key for your new project from the [Service accounts page](https://console.cloud.google.com/iam-admin/serviceaccounts?walkthrough_id=iam--create-service-account-keys&start_index=1#step_index=1). For more information, refer to [Create a service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) in the Google Cloud docs. When downloading the JSON file, make sure to use a filename you can easily remember. For example, `dbt-user-creds.json`. For security reasons, dbt Labs recommends that you protect this JSON file like you would your identity credentials; for example, don't check the JSON file into your version control software. - -## Connect dbt Cloud to BigQuery​ -1. Create a new project in [dbt Cloud](https://cloud.getdbt.com/). From **Account settings** (using the gear menu in the top right corner), click **+ New Project**. -2. Enter a project name and click **Continue**. -3. For the warehouse, click **BigQuery** then **Next** to set up your connection. -4. Click **Upload a Service Account JSON File** in settings. -5. Select the JSON file you downloaded in [Generate BigQuery credentials](#generate-bigquery-credentials) and dbt Cloud will fill in all the necessary fields. -6. Click **Test Connection**. This verifies that dbt Cloud can access your BigQuery account. -7. Click **Next** if the test succeeds. If it fails, you might need to go back and regenerate your BigQuery credentials. - - -## Set up a dbt Cloud managed repository - - - -## Initialize your dbt project -Now that you have a repository configured, you can initialize your project and start development in dbt Cloud: - -1. Click **Start developing in the IDE**. It might take a few minutes for your project to spin up for the first time as it establishes your git connection, clones your repo, and tests the connection to the warehouse. -2. Above the file tree to the left, click **Initialize dbt project**. This builds out your folder structure with example models. -3. Make your initial commit by clicking **Commit and sync**. Use the commit message `initial commit` and click **Commit**. This creates the first commit to your managed repo and allows you to open a branch where you can add new dbt code. -4. You can now directly query data from your warehouse and execute `dbt run`. You can try this out now: - - In the command line bar at the bottom, enter `dbt run` and click **Enter**. You should see a `dbt run succeeded` message. - - To confirm the success of the above command, navigate to the BigQuery Console and discover the newly created models. - -## Build your first model -1. Under **Version Control** on the left, click **Create branch**. You can name it `add-customers-model`. You need to create a new branch since the main branch is set to read-only mode. -3. Click the **...** next to the `models` directory, then select **Create file**. -4. Name the file `customers.sql`, then click **Create**. -5. Copy the following query into the file and click **Save**. - -```sql -with customers as ( - - select - id as customer_id, - first_name, - last_name - - from `dbt-tutorial`.jaffle_shop.customers - -), - -orders as ( - - select - id as order_id, - user_id as customer_id, - order_date, - status - - from `dbt-tutorial`.jaffle_shop.orders - -), - -customer_orders as ( - - select - customer_id, - - min(order_date) as first_order_date, - max(order_date) as most_recent_order_date, - count(order_id) as number_of_orders - - from orders - - group by 1 - -), - -final as ( - - select - customers.customer_id, - customers.first_name, - customers.last_name, - customer_orders.first_order_date, - customer_orders.most_recent_order_date, - coalesce(customer_orders.number_of_orders, 0) as number_of_orders - - from customers - - left join customer_orders using (customer_id) - -) - -select * from final -``` - -6. Enter `dbt run` in the command prompt at the bottom of the screen. You should get a successful run and see the three models. - -Later, you can connect your business intelligence (BI) tools to these views and tables so they only read cleaned-up data rather than raw data in your BI tool. - -#### FAQs - - - - - - - -## Change the way your model is materialized - - - -## Delete the example models - - - -## Build models on top of other models - - - -1. Create a new SQL file, `models/stg_customers.sql`, with the SQL from the `customers` CTE in our original query. -2. Create a second new SQL file, `models/stg_orders.sql`, with the SQL from the `orders` CTE in our original query. - - - - ```sql - select - id as customer_id, - first_name, - last_name - - from `dbt-tutorial`.jaffle_shop.customers - ``` - - - - - - ```sql - select - id as order_id, - user_id as customer_id, - order_date, - status - - from `dbt-tutorial`.jaffle_shop.orders - ``` - - - -3. Edit the SQL in your `models/customers.sql` file as follows: - - - - ```sql - with customers as ( - - select * from {{ ref('stg_customers') }} - - ), - - orders as ( - - select * from {{ ref('stg_orders') }} - - ), - - customer_orders as ( - - select - customer_id, - - min(order_date) as first_order_date, - max(order_date) as most_recent_order_date, - count(order_id) as number_of_orders - - from orders - - group by 1 - - ), - - final as ( - - select - customers.customer_id, - customers.first_name, - customers.last_name, - customer_orders.first_order_date, - customer_orders.most_recent_order_date, - coalesce(customer_orders.number_of_orders, 0) as number_of_orders - - from customers - - left join customer_orders using (customer_id) - - ) - - select * from final - - ``` - - - -4. Execute `dbt run`. - - This time, when you performed a `dbt run`, separate views/tables were created for `stg_customers`, `stg_orders`, and `customers`. dbt inferred the order to run these models. Because `customers` depends on `stg_customers` and `stg_orders`, dbt builds `customers` last. You do not need to explicitly define these dependencies. - -#### FAQs {#faq-2} - - - - - - - - - - - From dac2480d1ff16ac4f04a54a703eb99e3fcee6ea9 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 16:12:12 -0400 Subject: [PATCH 044/152] update styles --- .../components/quickstartGuideCard/index.js | 2 +- .../quickstartGuideCard/styles.module.css | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/website/src/components/quickstartGuideCard/index.js b/website/src/components/quickstartGuideCard/index.js index 1d29dcefa51..104bb5cb35b 100644 --- a/website/src/components/quickstartGuideCard/index.js +++ b/website/src/components/quickstartGuideCard/index.js @@ -21,7 +21,7 @@ export default function QuickstartGuideCard({ frontMatter }) { )} - Start + Start {(tags || level) && ( diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index 4c6f17bff79..aa1538cdeaa 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -1,5 +1,5 @@ .quickstartCard { - border: 1px solid #EFF2F3; + outline: 1px solid #EFF2F3; border-radius: var(--border-radius); box-shadow: 0px 11px 24px rgba(138, 138, 138, .1); padding: 2.5rem 2.5rem 1.5rem 2.5rem; @@ -13,6 +13,11 @@ .quickstartCard:hover { transform: translateY(-7px); + outline: 2px solid var( --color-green-blue); +} + +.quickstartCard:hover > .start { + text-decoration: underline; } .quickstartCard .icon { @@ -52,19 +57,16 @@ font-size: 1.125rem; margin-top: auto; padding-top: 2rem; + font-weight: 600; } [data-theme='dark'] .quickstartCard .start { color: #fff; } -[data-theme='dark'] .quickstartCard:hover .start { - text-decoration: underline; -} - -.quickstartCard .start:after { - content: " →"; - margin-left: 5px; +.quickstartCard .start i { + margin-left: 4px; + font-size: .9rem; } .quickstartCard .recently_updated { From badaed7d57b74d8cb5dfa725bf0f5ab6c1969e39 Mon Sep 17 00:00:00 2001 From: john-rock Date: Fri, 27 Oct 2023 16:14:12 -0400 Subject: [PATCH 045/152] update best practice link in nav --- website/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index ddb419ac4f7..546aaeb2e53 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -130,7 +130,7 @@ var siteSettings = { href: 'https://courses.getdbt.com', }, { - label: 'Guides', + label: 'Best Practices', to: '/guides/best-practices', }, { From c39e9cb2d0c2b0d716221f88915f12082bd6b238 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:32:19 -0700 Subject: [PATCH 046/152] adding guides to cool new section --- .../best-practices.md | 3 +- .../best-practices/custom-generic-tests.md | 0 .../best-practices/debugging-errors.md | 0 .../semantic-layer-1-intro.md | 0 .../semantic-layer-2-setup.md | 0 .../semantic-layer-3-build-semantic-models.md | 0 .../semantic-layer-4-build-metrics.md | 0 .../semantic-layer-5-refactor-a-mart.md | 0 .../semantic-layer-6-advanced-metrics.md | 0 .../semantic-layer-7-conclusion.md | 0 .../how-we-mesh/mesh-1-intro.md | 0 .../how-we-mesh/mesh-2-structures.md | 0 .../how-we-mesh/mesh-3-implementation.md | 0 .../how-we-structure/1-guide-overview.md | 0 .../how-we-structure/2-staging.md | 0 .../how-we-structure/3-intermediate.md | 0 .../how-we-structure/4-marts.md | 0 .../5-semantic-layer-marts.md | 0 .../6-the-rest-of-the-project.md | 0 .../0-how-we-style-our-dbt-projects.md | 0 .../1-how-we-style-our-dbt-models.md | 0 .../how-we-style/2-how-we-style-our-sql.md | 0 .../how-we-style/3-how-we-style-our-python.md | 0 .../how-we-style/4-how-we-style-our-jinja.md | 0 .../how-we-style/5-how-we-style-our-yaml.md | 0 .../how-we-style/6-how-we-style-conclusion.md | 0 ...materializations-guide-1-guide-overview.md | 0 ...ions-guide-2-available-materializations.md | 0 ...ns-guide-3-configuring-materializations.md | 0 ...rializations-guide-4-incremental-models.md | 0 ...materializations-guide-5-best-practices.md | 0 ...terializations-guide-6-examining-builds.md | 0 .../materializations-guide-7-conclusion.md | 0 website/docs/guides/airflow-and-dbt-cloud.md | 295 ++++++++++++++++++ .../creating-new-materializations.md | 14 +- .../1-airflow-and-dbt-cloud.md | 55 ---- .../2-setting-up-airflow-and-dbt-cloud.md | 90 ------ .../3-running-airflow-and-dbt-cloud.md | 104 ------ .../4-airflow-and-dbt-cloud-faqs.md | 50 --- website/docs/guides/redshift-qs.md | 2 +- .../docs/guides/{advanced => }/using-jinja.md | 7 + website/docusaurus.config.js | 2 +- website/sidebars.js | 277 ++-------------- 43 files changed, 346 insertions(+), 553 deletions(-) rename website/docs/{guides/legacy => best-practices}/best-practices.md (99%) rename website/docs/{guides => }/best-practices/custom-generic-tests.md (100%) rename website/docs/{guides => }/best-practices/debugging-errors.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-1-intro.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics.md (100%) rename website/docs/{guides => }/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion.md (100%) rename website/docs/{guides => }/best-practices/how-we-mesh/mesh-1-intro.md (100%) rename website/docs/{guides => }/best-practices/how-we-mesh/mesh-2-structures.md (100%) rename website/docs/{guides => }/best-practices/how-we-mesh/mesh-3-implementation.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/1-guide-overview.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/2-staging.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/3-intermediate.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/4-marts.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/5-semantic-layer-marts.md (100%) rename website/docs/{guides => }/best-practices/how-we-structure/6-the-rest-of-the-project.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/0-how-we-style-our-dbt-projects.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/1-how-we-style-our-dbt-models.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/2-how-we-style-our-sql.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/3-how-we-style-our-python.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/4-how-we-style-our-jinja.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/5-how-we-style-our-yaml.md (100%) rename website/docs/{guides => }/best-practices/how-we-style/6-how-we-style-conclusion.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-1-guide-overview.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-2-available-materializations.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-3-configuring-materializations.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-4-incremental-models.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-5-best-practices.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-6-examining-builds.md (100%) rename website/docs/{guides => }/best-practices/materializations/materializations-guide-7-conclusion.md (100%) create mode 100644 website/docs/guides/airflow-and-dbt-cloud.md rename website/docs/guides/{advanced => }/creating-new-materializations.md (98%) delete mode 100644 website/docs/guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud.md delete mode 100644 website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md delete mode 100644 website/docs/guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud.md delete mode 100644 website/docs/guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs.md rename website/docs/guides/{advanced => }/using-jinja.md (98%) diff --git a/website/docs/guides/legacy/best-practices.md b/website/docs/best-practices/best-practices.md similarity index 99% rename from website/docs/guides/legacy/best-practices.md rename to website/docs/best-practices/best-practices.md index 1fbcbc72cc1..70300a5974f 100644 --- a/website/docs/guides/legacy/best-practices.md +++ b/website/docs/best-practices/best-practices.md @@ -1,11 +1,12 @@ --- -title: "Best practices" +title: "Best practices for workflows" id: "best-practices" --- This page contains the collective wisdom of experienced users of dbt on how to best use it in your analytics work. Observing these best practices will help your analytics team work as effectively as possible, while implementing the pro-tips will add some polish to your dbt projects! ## Best practice workflows + ### Version control your dbt project All dbt projects should be managed in version control. Git branches should be created to manage development of new features and bug fixes. All code changes should be reviewed by a colleague (or yourself) in a Pull Request prior to merging into `master`. diff --git a/website/docs/guides/best-practices/custom-generic-tests.md b/website/docs/best-practices/custom-generic-tests.md similarity index 100% rename from website/docs/guides/best-practices/custom-generic-tests.md rename to website/docs/best-practices/custom-generic-tests.md diff --git a/website/docs/guides/best-practices/debugging-errors.md b/website/docs/best-practices/debugging-errors.md similarity index 100% rename from website/docs/guides/best-practices/debugging-errors.md rename to website/docs/best-practices/debugging-errors.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-1-intro.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-1-intro.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-2-setup.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics.md diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion.md similarity index 100% rename from website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion.md rename to website/docs/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion.md diff --git a/website/docs/guides/best-practices/how-we-mesh/mesh-1-intro.md b/website/docs/best-practices/how-we-mesh/mesh-1-intro.md similarity index 100% rename from website/docs/guides/best-practices/how-we-mesh/mesh-1-intro.md rename to website/docs/best-practices/how-we-mesh/mesh-1-intro.md diff --git a/website/docs/guides/best-practices/how-we-mesh/mesh-2-structures.md b/website/docs/best-practices/how-we-mesh/mesh-2-structures.md similarity index 100% rename from website/docs/guides/best-practices/how-we-mesh/mesh-2-structures.md rename to website/docs/best-practices/how-we-mesh/mesh-2-structures.md diff --git a/website/docs/guides/best-practices/how-we-mesh/mesh-3-implementation.md b/website/docs/best-practices/how-we-mesh/mesh-3-implementation.md similarity index 100% rename from website/docs/guides/best-practices/how-we-mesh/mesh-3-implementation.md rename to website/docs/best-practices/how-we-mesh/mesh-3-implementation.md diff --git a/website/docs/guides/best-practices/how-we-structure/1-guide-overview.md b/website/docs/best-practices/how-we-structure/1-guide-overview.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/1-guide-overview.md rename to website/docs/best-practices/how-we-structure/1-guide-overview.md diff --git a/website/docs/guides/best-practices/how-we-structure/2-staging.md b/website/docs/best-practices/how-we-structure/2-staging.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/2-staging.md rename to website/docs/best-practices/how-we-structure/2-staging.md diff --git a/website/docs/guides/best-practices/how-we-structure/3-intermediate.md b/website/docs/best-practices/how-we-structure/3-intermediate.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/3-intermediate.md rename to website/docs/best-practices/how-we-structure/3-intermediate.md diff --git a/website/docs/guides/best-practices/how-we-structure/4-marts.md b/website/docs/best-practices/how-we-structure/4-marts.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/4-marts.md rename to website/docs/best-practices/how-we-structure/4-marts.md diff --git a/website/docs/guides/best-practices/how-we-structure/5-semantic-layer-marts.md b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/5-semantic-layer-marts.md rename to website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md diff --git a/website/docs/guides/best-practices/how-we-structure/6-the-rest-of-the-project.md b/website/docs/best-practices/how-we-structure/6-the-rest-of-the-project.md similarity index 100% rename from website/docs/guides/best-practices/how-we-structure/6-the-rest-of-the-project.md rename to website/docs/best-practices/how-we-structure/6-the-rest-of-the-project.md diff --git a/website/docs/guides/best-practices/how-we-style/0-how-we-style-our-dbt-projects.md b/website/docs/best-practices/how-we-style/0-how-we-style-our-dbt-projects.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/0-how-we-style-our-dbt-projects.md rename to website/docs/best-practices/how-we-style/0-how-we-style-our-dbt-projects.md diff --git a/website/docs/guides/best-practices/how-we-style/1-how-we-style-our-dbt-models.md b/website/docs/best-practices/how-we-style/1-how-we-style-our-dbt-models.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/1-how-we-style-our-dbt-models.md rename to website/docs/best-practices/how-we-style/1-how-we-style-our-dbt-models.md diff --git a/website/docs/guides/best-practices/how-we-style/2-how-we-style-our-sql.md b/website/docs/best-practices/how-we-style/2-how-we-style-our-sql.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/2-how-we-style-our-sql.md rename to website/docs/best-practices/how-we-style/2-how-we-style-our-sql.md diff --git a/website/docs/guides/best-practices/how-we-style/3-how-we-style-our-python.md b/website/docs/best-practices/how-we-style/3-how-we-style-our-python.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/3-how-we-style-our-python.md rename to website/docs/best-practices/how-we-style/3-how-we-style-our-python.md diff --git a/website/docs/guides/best-practices/how-we-style/4-how-we-style-our-jinja.md b/website/docs/best-practices/how-we-style/4-how-we-style-our-jinja.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/4-how-we-style-our-jinja.md rename to website/docs/best-practices/how-we-style/4-how-we-style-our-jinja.md diff --git a/website/docs/guides/best-practices/how-we-style/5-how-we-style-our-yaml.md b/website/docs/best-practices/how-we-style/5-how-we-style-our-yaml.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/5-how-we-style-our-yaml.md rename to website/docs/best-practices/how-we-style/5-how-we-style-our-yaml.md diff --git a/website/docs/guides/best-practices/how-we-style/6-how-we-style-conclusion.md b/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md similarity index 100% rename from website/docs/guides/best-practices/how-we-style/6-how-we-style-conclusion.md rename to website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md b/website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-1-guide-overview.md rename to website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-2-available-materializations.md b/website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-2-available-materializations.md rename to website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-3-configuring-materializations.md b/website/docs/best-practices/materializations/materializations-guide-3-configuring-materializations.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-3-configuring-materializations.md rename to website/docs/best-practices/materializations/materializations-guide-3-configuring-materializations.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-4-incremental-models.md b/website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-4-incremental-models.md rename to website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-5-best-practices.md b/website/docs/best-practices/materializations/materializations-guide-5-best-practices.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-5-best-practices.md rename to website/docs/best-practices/materializations/materializations-guide-5-best-practices.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md b/website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-6-examining-builds.md rename to website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md diff --git a/website/docs/guides/best-practices/materializations/materializations-guide-7-conclusion.md b/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md similarity index 100% rename from website/docs/guides/best-practices/materializations/materializations-guide-7-conclusion.md rename to website/docs/best-practices/materializations/materializations-guide-7-conclusion.md diff --git a/website/docs/guides/airflow-and-dbt-cloud.md b/website/docs/guides/airflow-and-dbt-cloud.md new file mode 100644 index 00000000000..92c97fb2fd3 --- /dev/null +++ b/website/docs/guides/airflow-and-dbt-cloud.md @@ -0,0 +1,295 @@ +--- +title: Airflow and dbt Cloud +id: airflow-and-dbt-cloud +time_to_complete: '30 minutes' +platform: 'dbt-cloud' +icon: 'guides' +hide_table_of_contents: true +tags: ['airflow', 'dbt Cloud', 'orchestration'] +level: 'Intermediate' +recently_updated: true +--- + +## Introduction + +In some cases, [Airflow](https://airflow.apache.org/) may be the preferred orchestrator for your organization over working fully within dbt Cloud. There are a few reasons your team might be considering using Airflow to orchestrate your dbt jobs: + +- Your team is already using Airflow to orchestrate other processes +- Your team needs to ensure that a [dbt job](https://docs.getdbt.com/docs/dbt-cloud/cloud-overview#schedule-and-run-dbt-jobs-in-production) kicks off before or after another process outside of dbt Cloud +- Your team needs flexibility to manage more complex scheduling, such as kicking off one dbt job only after another has completed +- Your team wants to own their own orchestration solution +- You need code to work right now without starting from scratch + +### Prerequisites + +- [dbt Cloud Teams or Enterprise account](https://www.getdbt.com/pricing/) (with [admin access](https://docs.getdbt.com/docs/cloud/manage-access/enterprise-permissions)) in order to create a service token. Permissions for service tokens can be found [here](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens#permissions-for-service-account-tokens). +- A [free Docker account](https://hub.docker.com/signup) in order to sign in to Docker Desktop, which will be installed in the initial setup. +- A local digital scratchpad for temporarily copy-pasting API keys and URLs + +### Airflow + dbt Core + +There are [so many great examples](https://gitlab.com/gitlab-data/analytics/-/blob/master/dags/transformation/dbt_snowplow_backfill.py) from GitLab through their open source data engineering work. This is especially appropriate if you are well-versed in Kubernetes, CI/CD, and docker task management when building your airflow pipelines. If this is you and your team, you’re in good hands reading through more details [here](https://about.gitlab.com/handbook/business-technology/data-team/platform/infrastructure/#airflow) and [here](https://about.gitlab.com/handbook/business-technology/data-team/platform/dbt-guide/). + +### Airflow + dbt Cloud API w/Custom Scripts + +This has served as a bridge until the fabled Astronomer + dbt Labs-built dbt Cloud provider became generally available [here](https://registry.astronomer.io/providers/dbt%20Cloud/versions/latest). + +There are many different permutations of this over time: + +- [Custom Python Scripts](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/archive/dbt_cloud_example.py): This is an airflow DAG based on [custom python API utilities](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/archive/dbt_cloud_utils.py) +- [Make API requests directly through the BashOperator based on the docs](https://docs.getdbt.com/dbt-cloud/api-v2-legacy#operation/triggerRun): You can make cURL requests to invoke dbt Cloud to do what you want +- For more options, check out the [official dbt Docs](/docs/deploy/deployments#airflow) on the various ways teams are running dbt in airflow + +These solutions are great, but can be difficult to trust as your team grows and management for things like: testing, job definitions, secrets, and pipelines increase past your team’s capacity. Roles become blurry (or were never clearly defined at the start!). Both data and analytics engineers start digging through custom logging within each other’s workflows to make heads or tails of where and what the issue really is. Not to mention that when the issue is found, it can be even harder to decide on the best path forward for safely implementing fixes. This complex workflow and unclear delineation on process management results in a lot of misunderstandings and wasted time just trying to get the process to work smoothly! + + +In this guide, you'll learn how to: + +1. Creating a working local Airflow environment +2. Invoking a dbt Cloud job with Airflow (with proof!) +3. Reusing tested and trusted Airflow code for your specific use cases + +You’ll also gain a better understanding of how this will: + +- Reduce the cognitive load when building and maintaining pipelines +- Avoid dependency hell (think: `pip install` conflicts) +- Implement better recoveries from failures +- Define clearer workflows so that data and analytics engineers work better, together ♥️ + + +🙌 Let’s get started! 🙌 + +## Install the Astro CLI + +Astro is a managed software service that includes key features for teams working with Airflow. In order to use Astro, we’ll install the Astro CLI, which will give us access to useful commands for working with Airflow locally. You can read more about Astro [here](https://docs.astronomer.io/astro/). + +In this example, we’re using Homebrew to install Astro CLI. Follow the instructions to install the Astro CLI for your own operating system [here](https://docs.astronomer.io/astro/install-cli). + +```bash +brew install astro +``` + + + +## Install and start Docker Desktop + +Docker allows us to spin up an environment with all the apps and dependencies we need for the example. + +Follow the instructions [here](https://docs.docker.com/desktop/) to install Docker desktop for your own operating system. Once Docker is installed, ensure you have it up and running for the next steps. + + + +## Clone the airflow-dbt-cloud repository + +Open your terminal and clone the [airflow-dbt-cloud repository](https://github.com/sungchun12/airflow-dbt-cloud.git). This contains example Airflow DAGs that you’ll use to orchestrate your dbt Cloud job. Once cloned, navigate into the `airflow-dbt-cloud` project. + +```bash +git clone https://github.com/sungchun12/airflow-dbt-cloud.git +cd airflow-dbt-cloud +``` + + + +## Start the Docker container + +You can initialize an Astronomer project in an empty local directory using a Docker container, and then run your project locally using the `start` command. + +1. Run the following commands to initialize your project and start your local Airflow deployment: + + ```bash + astro dev init + astro dev start + ``` + + When this finishes, you should see a message similar to the following: + + ```bash + Airflow is starting up! This might take a few minutes… + + Project is running! All components are now available. + + Airflow Webserver: http://localhost:8080 + Postgres Database: localhost:5432/postgres + The default Airflow UI credentials are: admin:admin + The default Postrgres DB credentials are: postgres:postgres + ``` + +2. Open the Airflow interface. Launch your web browser and navigate to the address for the **Airflow Webserver** from your output in Step 1. + + This will take you to your local instance of Airflow. You’ll need to log in with the **default credentials**: + + - Username: admin + - Password: admin + + ![Airflow login screen](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-login.png) + + + +## Create a dbt Cloud service token + +Create a service token from within dbt Cloud using the instructions [found here](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens). Ensure that you save a copy of the token, as you won’t be able to access this later. In this example we use `Account Admin`, but you can also use `Job Admin` instead for token permissions. + + + +## Create a dbt Cloud job + +In your dbt Cloud account create a job, paying special attention to the information in the bullets below. Additional information for creating a dbt Cloud job can be found [here](/guides/bigquery). + +- Configure the job with the commands that you want to include when this job kicks off, as Airflow will be referring to the job’s configurations for this rather than being explicitly coded in the Airflow DAG. This job will run a set of commands rather than a single command. +- Ensure that the schedule is turned **off** since we’ll be using Airflow to kick things off. +- Once you hit `save` on the job, make sure you copy the URL and save it for referencing later. The url will look similar to this: + +```html +https://cloud.getdbt.com/#/accounts/{account_id}/projects/{project_id}/jobs/{job_id}/ +``` + + + +## Add your dbt Cloud API token as a secure connection + + + +Now you have all the working pieces to get up and running with Airflow + dbt Cloud. Let’s dive into make this all work together. We will **set up a connection** and **run a DAG in Airflow** that kicks off a dbt Cloud job. + +1. Navigate to Admin and click on **Connections** + + ![Airflow connections menu](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-connections-menu.png) + +2. Click on the `+` sign to add a new connection, then click on the drop down to search for the dbt Cloud Connection Type + + ![Create connection](/img/guides/orchestration/airflow-and-dbt-cloud/create-connection.png) + + ![Connection type](/img/guides/orchestration/airflow-and-dbt-cloud/connection-type.png) + +3. Add in your connection details and your default dbt Cloud account id. This is found in your dbt Cloud URL after the accounts route section (`/accounts/{YOUR_ACCOUNT_ID}`), for example the account with id 16173 would see this in their URL: `https://cloud.getdbt.com/#/accounts/16173/projects/36467/jobs/65767/` + +![https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2](https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2) + +## Add your `job_id` and `account_id` config details to the python file: [dbt_cloud_provider_eltml.py](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/dags/dbt_cloud_provider_eltml.py) + +1. You’ll find these details within the dbt Cloud job URL, see the comments in the code snippet below for an example. + + ```python + # dbt Cloud Job URL: https://cloud.getdbt.com/#/accounts/16173/projects/36467/jobs/65767/ + # account_id: 16173 + #job_id: 65767 + + # line 28 + default_args={"dbt_cloud_conn_id": "dbt_cloud", "account_id": 16173}, + + trigger_dbt_cloud_job_run = DbtCloudRunJobOperator( + task_id="trigger_dbt_cloud_job_run", + job_id=65767, # line 39 + check_interval=10, + timeout=300, + ) + ``` + +2. Turn on the DAG and verify the job succeeded after running. Note: screenshots taken from different job runs, but the user experience is consistent. + + ![https://lh6.googleusercontent.com/p8AqQRy0UGVLjDGPmcuGYmQ_BRodyL0Zis-eQgSmp69EHbKW51o4S-bCl1fXHlOmwpYEBxD0A-O1Q1hwt-VDVMO1wWH-AIeaoelBx06JXRJ0m1OcHaPpFKH0xDiduIhNlQhhbLiy](https://lh6.googleusercontent.com/p8AqQRy0UGVLjDGPmcuGYmQ_BRodyL0Zis-eQgSmp69EHbKW51o4S-bCl1fXHlOmwpYEBxD0A-O1Q1hwt-VDVMO1wWH-AIeaoelBx06JXRJ0m1OcHaPpFKH0xDiduIhNlQhhbLiy) + + ![Airflow DAG](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-dag.png) + + ![Task run instance](/img/guides/orchestration/airflow-and-dbt-cloud/task-run-instance.png) + + ![https://lh6.googleusercontent.com/S9QdGhLAdioZ3x634CChugsJRiSVtTTd5CTXbRL8ADA6nSbAlNn4zV0jb3aC946c8SGi9FRTfyTFXqjcM-EBrJNK5hQ0HHAsR5Fj7NbdGoUfBI7xFmgeoPqnoYpjyZzRZlXkjtxS](https://lh6.googleusercontent.com/S9QdGhLAdioZ3x634CChugsJRiSVtTTd5CTXbRL8ADA6nSbAlNn4zV0jb3aC946c8SGi9FRTfyTFXqjcM-EBrJNK5hQ0HHAsR5Fj7NbdGoUfBI7xFmgeoPqnoYpjyZzRZlXkjtxS) + +## How do I rerun the dbt Cloud job and downstream tasks in my pipeline? + +If you have worked with dbt Cloud before, you have likely encountered cases where a job fails. In those cases, you have likely logged into dbt Cloud, investigated the error, and then manually restarted the job. + +This section of the guide will show you how to restart the job directly from Airflow. This will specifically run *just* the `trigger_dbt_cloud_job_run` and downstream tasks of the Airflow DAG and not the entire DAG. If only the transformation step fails, you don’t need to re-run the extract and load processes. Let’s jump into how to do that in Airflow. + +1. Click on the task + + ![Task DAG view](/img/guides/orchestration/airflow-and-dbt-cloud/task-dag-view.png) + +2. Clear the task instance + + ![Clear task instance](/img/guides/orchestration/airflow-and-dbt-cloud/clear-task-instance.png) + + ![Approve clearing](/img/guides/orchestration/airflow-and-dbt-cloud/approve-clearing.png) + +3. Watch it rerun in real time + + ![Re-run](/img/guides/orchestration/airflow-and-dbt-cloud/re-run.png) + +## Cleaning up + +At the end of this guide, make sure you shut down your docker container. When you’re done using Airflow, use the following command to stop the container: + +```bash +$ astrocloud dev stop + +[+] Running 3/3 + ⠿ Container airflow-dbt-cloud_e3fe3c-webserver-1 Stopped 7.5s + ⠿ Container airflow-dbt-cloud_e3fe3c-scheduler-1 Stopped 3.3s + ⠿ Container airflow-dbt-cloud_e3fe3c-postgres-1 Stopped 0.3s +``` + +To verify that the deployment has stopped, use the following command: + +```bash +astrocloud dev ps +``` + +This should give you an output like this: + +```bash +Name State Ports +airflow-dbt-cloud_e3fe3c-webserver-1 exited +airflow-dbt-cloud_e3fe3c-scheduler-1 exited +airflow-dbt-cloud_e3fe3c-postgres-1 exited +``` + + + +## Frequently asked questions + +### How can we run specific subsections of the dbt DAG in Airflow? + +Because of the way we configured the dbt Cloud job to run in Airflow, you can leave this job to your analytics engineers to define in the job configurations from dbt Cloud. If, for example, we need to run hourly-tagged models every hour and daily-tagged models daily, we can create jobs like `Hourly Run` or `Daily Run` and utilize the commands `dbt run -s tag:hourly` and `dbt run -s tag:daily` within each, respectively. We only need to grab our dbt Cloud `account` and `job id`, configure it in an Airflow DAG with the code provided, and then we can be on your way. See more node selection options: [here](/reference/node-selection/syntax) + +### How can I re-run models from the point of failure? + +You may want to parse the dbt DAG in Airflow to get the benefit of re-running from the point of failure. However, when you have hundreds of models in your DAG expanded out, it becomes useless for diagnosis and rerunning due to the overhead that comes along with creating an expansive Airflow DAG. + +You can’t re-run from failure natively in dbt Cloud today (feature coming!), but you can use a custom rerun parser. + +Using a simple python script coupled with the dbt Cloud provider, you can: + +- Avoid managing artifacts in a separate storage bucket(dbt Cloud does this for you) +- Avoid building your own parsing logic +- Get clear logs on what models you're rerunning in dbt Cloud (without hard coding step override commands) + +Watch the video below to see how it works! + + + +### Should Airflow run one big dbt job or many dbt jobs? + +Overall we recommend being as purposeful and minimalistic as you can. This is because dbt manages all of the dependencies between models and the orchestration of running those dependencies in order, which in turn has benefits in terms of warehouse processing efforts. + +### We want to kick off our dbt jobs after our ingestion tool (such as Fivetran) / data pipelines are done loading data. Any best practices around that? + +Our friends at Astronomer answer this question with this example: [here](https://registry.astronomer.io/dags/fivetran-dbt-cloud-census) + +### How do you set up a CI/CD workflow with Airflow? + +Check out these two resources for accomplishing your own CI/CD pipeline: + +- [Continuous Integration with dbt Cloud](/docs/deploy/continuous-integration) +- [Astronomer's CI/CD Example](https://docs.astronomer.io/software/ci-cd/#example-cicd-workflow) + +### Can dbt dynamically create tasks in the DAG like Airflow can? + +We prefer to keep models bundled vs. unbundled. You can go this route, but if you have hundreds of dbt models, it’s more effective to let the dbt Cloud job handle the models and dependencies. Bundling provides the solution to clear observability when things go wrong - we've seen more success in having the ability to clearly see issues in a bundled dbt Cloud job than combing through the nodes of an expansive Airflow DAG. If you still have a use case for this level of control though, our friends at Astronomer answer this question [here](https://www.astronomer.io/blog/airflow-dbt-1/)! + +### Can you trigger notifications if a dbt job fails with Airflow? Is there any way to access the status of the dbt Job to do that? + +Yes, either through [Airflow's email/slack](https://www.astronomer.io/guides/error-notifications-in-airflow/) functionality by itself or combined with [dbt Cloud's notifications](/docs/deploy/job-notifications), which support email and slack notifications. + +### Are there decision criteria for how to best work with dbt Cloud and airflow? + +Check out this deep dive into planning your dbt Cloud + Airflow implementation [here](https://www.youtube.com/watch?v=n7IIThR8hGk)! diff --git a/website/docs/guides/advanced/creating-new-materializations.md b/website/docs/guides/creating-new-materializations.md similarity index 98% rename from website/docs/guides/advanced/creating-new-materializations.md rename to website/docs/guides/creating-new-materializations.md index d3081ea8e20..42466c843cc 100644 --- a/website/docs/guides/advanced/creating-new-materializations.md +++ b/website/docs/guides/creating-new-materializations.md @@ -4,6 +4,13 @@ id: "creating-new-materializations" description: Learn how to create your own materializations. displayText: Creating new materializations hoverSnippet: Learn how to create your own materializations. +time_to_complete: '30 minutes' +platform: 'dbt-core' +icon: 'guides' +hide_table_of_contents: true +tags: ['materializations', 'dbt Core'] +level: 'Advanced' +recently_updated: true --- ## Overview @@ -110,13 +117,6 @@ Be sure to `commit` the transaction in the `cleanup` phase of the materializatio ### Update the Relation cache - -:::info New in 0.15.0 - -The ability to synchronize the Relation cache is new in dbt v0.15.0 - -::: - Materializations should [return](/reference/dbt-jinja-functions/return) the list of Relations that they have created at the end of execution. dbt will use this list of Relations to update the relation cache in order to reduce the number of queries executed against the database's `information_schema`. If a list of Relations is not returned, then dbt will raise a Deprecation Warning and infer the created relation from the model's configured database, schema, and alias. diff --git a/website/docs/guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud.md b/website/docs/guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud.md deleted file mode 100644 index d6760771b79..00000000000 --- a/website/docs/guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Airflow and dbt Cloud -id: 1-airflow-and-dbt-cloud ---- - -In some cases, [Airflow](https://airflow.apache.org/) may be the preferred orchestrator for your organization over working fully within dbt Cloud. There are a few reasons your team might be considering using Airflow to orchestrate your dbt jobs: - -- Your team is already using Airflow to orchestrate other processes -- Your team needs to ensure that a [dbt job](https://docs.getdbt.com/docs/dbt-cloud/cloud-overview#schedule-and-run-dbt-jobs-in-production) kicks off before or after another process outside of dbt Cloud -- Your team needs flexibility to manage more complex scheduling, such as kicking off one dbt job only after another has completed -- Your team wants to own their own orchestration solution -- You need code to work right now without starting from scratch - -## How are people using Airflow + dbt today? - -### Airflow + dbt Core - -There are [so many great examples](https://gitlab.com/gitlab-data/analytics/-/blob/master/dags/transformation/dbt_snowplow_backfill.py) from GitLab through their open source data engineering work. This is especially appropriate if you are well-versed in Kubernetes, CI/CD, and docker task management when building your airflow pipelines. If this is you and your team, you’re in good hands reading through more details [here](https://about.gitlab.com/handbook/business-technology/data-team/platform/infrastructure/#airflow) and [here](https://about.gitlab.com/handbook/business-technology/data-team/platform/dbt-guide/). - -### Airflow + dbt Cloud API w/Custom Scripts - -This has served as a bridge until the fabled Astronomer + dbt Labs-built dbt Cloud provider became generally available [here](https://registry.astronomer.io/providers/dbt%20Cloud/versions/latest). - -There are many different permutations of this over time: - -- [Custom Python Scripts](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/archive/dbt_cloud_example.py): This is an airflow DAG based on [custom python API utilities](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/archive/dbt_cloud_utils.py) -- [Make API requests directly through the BashOperator based on the docs](https://docs.getdbt.com/dbt-cloud/api-v2-legacy#operation/triggerRun): You can make cURL requests to invoke dbt Cloud to do what you want -- For more options, check out the [official dbt Docs](/docs/deploy/deployments#airflow) on the various ways teams are running dbt in airflow - -## This guide's process - -These solutions are great, but can be difficult to trust as your team grows and management for things like: testing, job definitions, secrets, and pipelines increase past your team’s capacity. Roles become blurry (or were never clearly defined at the start!). Both data and analytics engineers start digging through custom logging within each other’s workflows to make heads or tails of where and what the issue really is. Not to mention that when the issue is found, it can be even harder to decide on the best path forward for safely implementing fixes. This complex workflow and unclear delineation on process management results in a lot of misunderstandings and wasted time just trying to get the process to work smoothly! - -### A better way - -After today’s walkthrough, you’ll get hands-on experience: - -1. Creating a working local Airflow environment -2. Invoking a dbt Cloud job with Airflow (with proof!) -3. Reusing tested and trusted Airflow code for your specific use cases - -While you’re learning the ropes, you’ll also gain a better understanding of how this helps to: - -- Reduce the cognitive load when building and maintaining pipelines -- Avoid dependency hell (think: `pip install` conflicts) -- Implement better recoveries from failures -- Define clearer workflows so that data and analytics engineers work better, together ♥️ - -### Prerequisites - -- [dbt Cloud Teams or Enterprise account](https://www.getdbt.com/pricing/) (with [admin access](https://docs.getdbt.com/docs/cloud/manage-access/enterprise-permissions)) in order to create a service token. Permissions for service tokens can be found [here](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens#permissions-for-service-account-tokens). -- A [free Docker account](https://hub.docker.com/signup) in order to sign in to Docker Desktop, which will be installed in the initial setup. -- A local digital scratchpad for temporarily copy-pasting API keys and URLs - -🙌 Let’s get started! 🙌 diff --git a/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md b/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md deleted file mode 100644 index 01b15440920..00000000000 --- a/website/docs/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: Setting up Airflow and dbt Cloud -id: 2-setting-up-airflow-and-dbt-cloud ---- - -## 1. Install the Astro CLI - -Astro is a managed software service that includes key features for teams working with Airflow. In order to use Astro, we’ll install the Astro CLI, which will give us access to useful commands for working with Airflow locally. You can read more about Astro [here](https://docs.astronomer.io/astro/). - -In this example, we’re using Homebrew to install Astro CLI. Follow the instructions to install the Astro CLI for your own operating system [here](https://docs.astronomer.io/astro/install-cli). - -```bash -brew install astro -``` - - - -## 2. Install and start Docker Desktop - -Docker allows us to spin up an environment with all the apps and dependencies we need for the example. - -Follow the instructions [here](https://docs.docker.com/desktop/) to install Docker desktop for your own operating system. Once Docker is installed, ensure you have it up and running for the next steps. - - - -## 3. Clone the airflow-dbt-cloud repository - -Open your terminal and clone the [airflow-dbt-cloud repository](https://github.com/sungchun12/airflow-dbt-cloud.git). This contains example Airflow DAGs that you’ll use to orchestrate your dbt Cloud job. Once cloned, navigate into the `airflow-dbt-cloud` project. - -```bash -git clone https://github.com/sungchun12/airflow-dbt-cloud.git -cd airflow-dbt-cloud -``` - - - -## 4. Start the Docker container - -You can initialize an Astronomer project in an empty local directory using a Docker container, and then run your project locally using the `start` command. - -1. Run the following commands to initialize your project and start your local Airflow deployment: - - ```bash - astro dev init - astro dev start - ``` - - When this finishes, you should see a message similar to the following: - - ```bash - Airflow is starting up! This might take a few minutes… - - Project is running! All components are now available. - - Airflow Webserver: http://localhost:8080 - Postgres Database: localhost:5432/postgres - The default Airflow UI credentials are: admin:admin - The default Postrgres DB credentials are: postgres:postgres - ``` - -2. Open the Airflow interface. Launch your web browser and navigate to the address for the **Airflow Webserver** from your output in Step 1. - - This will take you to your local instance of Airflow. You’ll need to log in with the **default credentials**: - - - Username: admin - - Password: admin - - ![Airflow login screen](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-login.png) - - - -## 5. Create a dbt Cloud service token - -Create a service token from within dbt Cloud using the instructions [found here](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens). Ensure that you save a copy of the token, as you won’t be able to access this later. In this example we use `Account Admin`, but you can also use `Job Admin` instead for token permissions. - - - -## 6. Create a dbt Cloud job - -In your dbt Cloud account create a job, paying special attention to the information in the bullets below. Additional information for creating a dbt Cloud job can be found [here](/guides/bigquery). - -- Configure the job with the commands that you want to include when this job kicks off, as Airflow will be referring to the job’s configurations for this rather than being explicitly coded in the Airflow DAG. This job will run a set of commands rather than a single command. -- Ensure that the schedule is turned **off** since we’ll be using Airflow to kick things off. -- Once you hit `save` on the job, make sure you copy the URL and save it for referencing later. The url will look similar to this: - -```html -https://cloud.getdbt.com/#/accounts/{account_id}/projects/{project_id}/jobs/{job_id}/ -``` - - diff --git a/website/docs/guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud.md b/website/docs/guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud.md deleted file mode 100644 index d6fd32bdba9..00000000000 --- a/website/docs/guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: Running Airflow and dbt Cloud -id: 3-running-airflow-and-dbt-cloud ---- - - - -Now you have all the working pieces to get up and running with Airflow + dbt Cloud. Let’s dive into make this all work together. We will **set up a connection** and **run a DAG in Airflow** that kicks off a dbt Cloud job. - -## 1. Add your dbt Cloud API token as a secure connection - -1. Navigate to Admin and click on **Connections** - - ![Airflow connections menu](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-connections-menu.png) - -2. Click on the `+` sign to add a new connection, then click on the drop down to search for the dbt Cloud Connection Type - - ![Create connection](/img/guides/orchestration/airflow-and-dbt-cloud/create-connection.png) - - ![Connection type](/img/guides/orchestration/airflow-and-dbt-cloud/connection-type.png) - -3. Add in your connection details and your default dbt Cloud account id. This is found in your dbt Cloud URL after the accounts route section (`/accounts/{YOUR_ACCOUNT_ID}`), for example the account with id 16173 would see this in their URL: `https://cloud.getdbt.com/#/accounts/16173/projects/36467/jobs/65767/` - -![https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2](https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2) - -## 2. Add your `job_id` and `account_id` config details to the python file: [dbt_cloud_provider_eltml.py](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/dags/dbt_cloud_provider_eltml.py) - -1. You’ll find these details within the dbt Cloud job URL, see the comments in the code snippet below for an example. - - ```python - # dbt Cloud Job URL: https://cloud.getdbt.com/#/accounts/16173/projects/36467/jobs/65767/ - # account_id: 16173 - #job_id: 65767 - - # line 28 - default_args={"dbt_cloud_conn_id": "dbt_cloud", "account_id": 16173}, - - trigger_dbt_cloud_job_run = DbtCloudRunJobOperator( - task_id="trigger_dbt_cloud_job_run", - job_id=65767, # line 39 - check_interval=10, - timeout=300, - ) - ``` - -2. Turn on the DAG and verify the job succeeded after running. Note: screenshots taken from different job runs, but the user experience is consistent. - - ![https://lh6.googleusercontent.com/p8AqQRy0UGVLjDGPmcuGYmQ_BRodyL0Zis-eQgSmp69EHbKW51o4S-bCl1fXHlOmwpYEBxD0A-O1Q1hwt-VDVMO1wWH-AIeaoelBx06JXRJ0m1OcHaPpFKH0xDiduIhNlQhhbLiy](https://lh6.googleusercontent.com/p8AqQRy0UGVLjDGPmcuGYmQ_BRodyL0Zis-eQgSmp69EHbKW51o4S-bCl1fXHlOmwpYEBxD0A-O1Q1hwt-VDVMO1wWH-AIeaoelBx06JXRJ0m1OcHaPpFKH0xDiduIhNlQhhbLiy) - - ![Airflow DAG](/img/guides/orchestration/airflow-and-dbt-cloud/airflow-dag.png) - - ![Task run instance](/img/guides/orchestration/airflow-and-dbt-cloud/task-run-instance.png) - - ![https://lh6.googleusercontent.com/S9QdGhLAdioZ3x634CChugsJRiSVtTTd5CTXbRL8ADA6nSbAlNn4zV0jb3aC946c8SGi9FRTfyTFXqjcM-EBrJNK5hQ0HHAsR5Fj7NbdGoUfBI7xFmgeoPqnoYpjyZzRZlXkjtxS](https://lh6.googleusercontent.com/S9QdGhLAdioZ3x634CChugsJRiSVtTTd5CTXbRL8ADA6nSbAlNn4zV0jb3aC946c8SGi9FRTfyTFXqjcM-EBrJNK5hQ0HHAsR5Fj7NbdGoUfBI7xFmgeoPqnoYpjyZzRZlXkjtxS) - -## How do I rerun the dbt Cloud job and downstream tasks in my pipeline? - -If you have worked with dbt Cloud before, you have likely encountered cases where a job fails. In those cases, you have likely logged into dbt Cloud, investigated the error, and then manually restarted the job. - -This section of the guide will show you how to restart the job directly from Airflow. This will specifically run *just* the `trigger_dbt_cloud_job_run` and downstream tasks of the Airflow DAG and not the entire DAG. If only the transformation step fails, you don’t need to re-run the extract and load processes. Let’s jump into how to do that in Airflow. - -1. Click on the task - - ![Task DAG view](/img/guides/orchestration/airflow-and-dbt-cloud/task-dag-view.png) - -2. Clear the task instance - - ![Clear task instance](/img/guides/orchestration/airflow-and-dbt-cloud/clear-task-instance.png) - - ![Approve clearing](/img/guides/orchestration/airflow-and-dbt-cloud/approve-clearing.png) - -3. Watch it rerun in real time - - ![Re-run](/img/guides/orchestration/airflow-and-dbt-cloud/re-run.png) - -## Cleaning up - -At the end of this guide, make sure you shut down your docker container. When you’re done using Airflow, use the following command to stop the container: - -```bash -$ astrocloud dev stop - -[+] Running 3/3 - ⠿ Container airflow-dbt-cloud_e3fe3c-webserver-1 Stopped 7.5s - ⠿ Container airflow-dbt-cloud_e3fe3c-scheduler-1 Stopped 3.3s - ⠿ Container airflow-dbt-cloud_e3fe3c-postgres-1 Stopped 0.3s -``` - -To verify that the deployment has stopped, use the following command: - -```bash -astrocloud dev ps -``` - -This should give you an output like this: - -```bash -Name State Ports -airflow-dbt-cloud_e3fe3c-webserver-1 exited -airflow-dbt-cloud_e3fe3c-scheduler-1 exited -airflow-dbt-cloud_e3fe3c-postgres-1 exited -``` - - diff --git a/website/docs/guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs.md b/website/docs/guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs.md deleted file mode 100644 index 5766d8c0b79..00000000000 --- a/website/docs/guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Airflow and dbt Cloud FAQs -id: 4-airflow-and-dbt-cloud-faqs ---- -## 1. How can we run specific subsections of the dbt DAG in Airflow? - -Because of the way we configured the dbt Cloud job to run in Airflow, you can leave this job to your analytics engineers to define in the job configurations from dbt Cloud. If, for example, we need to run hourly-tagged models every hour and daily-tagged models daily, we can create jobs like `Hourly Run` or `Daily Run` and utilize the commands `dbt run -s tag:hourly` and `dbt run -s tag:daily` within each, respectively. We only need to grab our dbt Cloud `account` and `job id`, configure it in an Airflow DAG with the code provided, and then we can be on your way. See more node selection options: [here](/reference/node-selection/syntax) - -## 2. How can I re-run models from the point of failure? - -You may want to parse the dbt DAG in Airflow to get the benefit of re-running from the point of failure. However, when you have hundreds of models in your DAG expanded out, it becomes useless for diagnosis and rerunning due to the overhead that comes along with creating an expansive Airflow DAG. - -You can’t re-run from failure natively in dbt Cloud today (feature coming!), but you can use a custom rerun parser. - -Using a simple python script coupled with the dbt Cloud provider, you can: - -- Avoid managing artifacts in a separate storage bucket(dbt Cloud does this for you) -- Avoid building your own parsing logic -- Get clear logs on what models you're rerunning in dbt Cloud (without hard coding step override commands) - -Watch the video below to see how it works! - - - -## 3. Should Airflow run one big dbt job or many dbt jobs? - -Overall we recommend being as purposeful and minimalistic as you can. This is because dbt manages all of the dependencies between models and the orchestration of running those dependencies in order, which in turn has benefits in terms of warehouse processing efforts. - -## 4. We want to kick off our dbt jobs after our ingestion tool (such as Fivetran) / data pipelines are done loading data. Any best practices around that? - -Our friends at Astronomer answer this question with this example: [here](https://registry.astronomer.io/dags/fivetran-dbt-cloud-census) - -## 5. How do you set up a CI/CD workflow with Airflow? - -Check out these two resources for accomplishing your own CI/CD pipeline: - -- [Continuous Integration with dbt Cloud](/docs/deploy/continuous-integration) -- [Astronomer's CI/CD Example](https://docs.astronomer.io/software/ci-cd/#example-cicd-workflow) - -## 6. Can dbt dynamically create tasks in the DAG like Airflow can? - -We prefer to keep models bundled vs. unbundled. You can go this route, but if you have hundreds of dbt models, it’s more effective to let the dbt Cloud job handle the models and dependencies. Bundling provides the solution to clear observability when things go wrong - we've seen more success in having the ability to clearly see issues in a bundled dbt Cloud job than combing through the nodes of an expansive Airflow DAG. If you still have a use case for this level of control though, our friends at Astronomer answer this question [here](https://www.astronomer.io/blog/airflow-dbt-1/)! - -## 7. Can you trigger notifications if a dbt job fails with Airflow? Is there any way to access the status of the dbt Job to do that? - -Yes, either through [Airflow's email/slack](https://www.astronomer.io/guides/error-notifications-in-airflow/) functionality by itself or combined with [dbt Cloud's notifications](/docs/deploy/job-notifications), which support email and slack notifications. - -## 8. Are there decision criteria for how to best work with dbt Cloud and airflow? - -Check out this deep dive into planning your dbt Cloud + Airflow implementation [here](https://www.youtube.com/watch?v=n7IIThR8hGk)! diff --git a/website/docs/guides/redshift-qs.md b/website/docs/guides/redshift-qs.md index 04b21cf8d57..b635b86a693 100644 --- a/website/docs/guides/redshift-qs.md +++ b/website/docs/guides/redshift-qs.md @@ -5,7 +5,7 @@ platform: 'dbt-cloud' icon: 'redshift' hide_table_of_contents: true tags: ['Redshift', 'dbt Cloud'] -level: 'Expert' +level: 'Advanced' --- ## Introduction diff --git a/website/docs/guides/advanced/using-jinja.md b/website/docs/guides/using-jinja.md similarity index 98% rename from website/docs/guides/advanced/using-jinja.md rename to website/docs/guides/using-jinja.md index 1cbe88dc9ca..1b2b7d7fd59 100644 --- a/website/docs/guides/advanced/using-jinja.md +++ b/website/docs/guides/using-jinja.md @@ -1,6 +1,13 @@ --- title: "Using Jinja" id: "using-jinja" +time_to_complete: '30 minutes' +platform: 'dbt-core' +icon: 'guides' +hide_table_of_contents: true +tags: ['jinja', 'dbt Core'] +level: 'Advanced' +recently_updated: true --- In this guide, we're going to take a common pattern used in SQL, and then use Jinja to improve our code. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 546aaeb2e53..ee593e568f4 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -131,7 +131,7 @@ var siteSettings = { }, { label: 'Best Practices', - to: '/guides/best-practices', + to: '/best-practices', }, { label: "Guides", diff --git a/website/sidebars.js b/website/sidebars.js index 70339631d84..3c9dfbc6536 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -939,7 +939,7 @@ const sidebarSettings = { items: ["reference/snowflake-permissions"], }, ], - guides: [ + bestpractices: [ { type: "category", label: "Best practices", @@ -948,7 +948,7 @@ const sidebarSettings = { title: "Best practice guides", description: "Learn how dbt Labs approaches building projects through our current viewpoints on structure, style, and setup.", - slug: "/guides/best-practices", + slug: "best-practices", }, items: [ { @@ -956,14 +956,14 @@ const sidebarSettings = { label: "How we structure our dbt projects", link: { type: "doc", - id: "guides/best-practices/how-we-structure/1-guide-overview", + id: "best-practices/how-we-structure/1-guide-overview", }, items: [ - "guides/best-practices/how-we-structure/2-staging", - "guides/best-practices/how-we-structure/3-intermediate", - "guides/best-practices/how-we-structure/4-marts", - "guides/best-practices/how-we-structure/5-semantic-layer-marts", - "guides/best-practices/how-we-structure/6-the-rest-of-the-project", + "best-practices/how-we-structure/2-staging", + "best-practices/how-we-structure/3-intermediate", + "best-practices/how-we-structure/4-marts", + "best-practices/how-we-structure/5-semantic-layer-marts", + "best-practices/how-we-structure/6-the-rest-of-the-project", ], }, { @@ -971,15 +971,15 @@ const sidebarSettings = { label: "How we style our dbt projects", link: { type: "doc", - id: "guides/best-practices/how-we-style/0-how-we-style-our-dbt-projects", + id: "best-practices/how-we-style/0-how-we-style-our-dbt-projects", }, items: [ - "guides/best-practices/how-we-style/1-how-we-style-our-dbt-models", - "guides/best-practices/how-we-style/2-how-we-style-our-sql", - "guides/best-practices/how-we-style/3-how-we-style-our-python", - "guides/best-practices/how-we-style/4-how-we-style-our-jinja", - "guides/best-practices/how-we-style/5-how-we-style-our-yaml", - "guides/best-practices/how-we-style/6-how-we-style-conclusion", + "best-practices/how-we-style/1-how-we-style-our-dbt-models", + "best-practices/how-we-style/2-how-we-style-our-sql", + "best-practices/how-we-style/3-how-we-style-our-python", + "best-practices/how-we-style/4-how-we-style-our-jinja", + "best-practices/how-we-style/5-how-we-style-our-yaml", + "best-practices/how-we-style/6-how-we-style-conclusion", ], }, { @@ -987,15 +987,14 @@ const sidebarSettings = { label: "How we build our metrics", link: { type: "doc", - id: "guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro", + id: "best-practices/how-we-build-our-metrics/semantic-layer-1-intro", }, items: [ - "guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup", - "guides/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models", - "guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics", - "guides/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart", - "guides/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics", - "guides/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion", + "best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models", + "best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics", + "best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart", + "best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics", + "best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion", ], }, { @@ -1003,11 +1002,11 @@ const sidebarSettings = { label: "How we build our dbt Mesh projects", link: { type: "doc", - id: "guides/best-practices/how-we-mesh/mesh-1-intro", + id: "best-practices/how-we-mesh/mesh-1-intro", }, items: [ - "guides/best-practices/how-we-mesh/mesh-2-structures", - "guides/best-practices/how-we-mesh/mesh-3-implementation", + "best-practices/how-we-mesh/mesh-2-structures", + "best-practices/how-we-mesh/mesh-3-implementation", ], }, { @@ -1015,229 +1014,19 @@ const sidebarSettings = { label: "Materialization best practices", link: { type: "doc", - id: "guides/best-practices/materializations/materializations-guide-1-guide-overview", + id: "best-practices/materializations/materializations-guide-1-guide-overview", }, items: [ - "guides/best-practices/materializations/materializations-guide-2-available-materializations", - "guides/best-practices/materializations/materializations-guide-3-configuring-materializations", - "guides/best-practices/materializations/materializations-guide-4-incremental-models", - "guides/best-practices/materializations/materializations-guide-5-best-practices", - "guides/best-practices/materializations/materializations-guide-6-examining-builds", - "guides/best-practices/materializations/materializations-guide-7-conclusion", + "best-practices/materializations/materializations-guide-2-available-materializations", + "best-practices/materializations/materializations-guide-3-configuring-materializations", + "best-practices/materializations/materializations-guide-4-incremental-models", + "best-practices/materializations/materializations-guide-5-best-practices", + "best-practices/materializations/materializations-guide-6-examining-builds", + "best-practices/materializations/materializations-guide-7-conclusion", ], }, - "guides/best-practices/debugging-errors", - "guides/best-practices/writing-custom-generic-tests", - ], - }, - { - type: "category", - label: "Orchestration", - link: { - type: "generated-index", - title: "Orchestration guides", - description: - "Learn how to orchestrate your data transformations in dbt, using dbt Cloud, a variety of popular tools, or both working together.", - slug: "/guides/orchestration", - }, - items: [ - { - type: "category", - label: "Airflow and dbt Cloud", - link: { - type: "doc", - id: "guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud", - }, - items: [ - "guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud", - "guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud", - "guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs", - ], - }, - { - type: "category", - label: "Set up Continuous Integration", - link: { - type: "doc", - id: "guides/orchestration/set-up-ci/introduction", - }, - items: [ - "guides/orchestration/set-up-ci/quick-setup", - "guides/orchestration/set-up-ci/run-dbt-project-evaluator", - "guides/orchestration/set-up-ci/lint-on-push", - "guides/orchestration/set-up-ci/multiple-checks", - ], - }, - { - type: "category", - label: "Custom Continuous Deployment Workflows", - link: { - type: "doc", - id: "guides/orchestration/custom-cicd-pipelines/1-cicd-background", - }, - items: [ - "guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge", - "guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr", - "guides/orchestration/custom-cicd-pipelines/5-something-to-consider", - ], - }, - { - type: "category", - label: "Webhooks with dbt Cloud and SaaS apps", - link: { - type: "generated-index", - title: "Use dbt Cloud's webhooks with other SaaS apps", - description: - "Learn how to use webhooks to trigger actions in other tools by using Zapier or a serverless platform.", - slug: "/guides/orchestration/webhooks", - }, - items: [ - { - type: "autogenerated", - dirName: "guides/orchestration/webhooks", - }, - ], - }, - "guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs", - ], - }, - { - type: "category", - label: "Migration", - items: [ - "guides/migration/sl-migration", - { - type: "category", - label: "Versions", - link: { - type: "generated-index", - title: "Version migration guides", - description: - "Learn how to upgrade to the latest version of dbt Core.", - slug: "/guides/migration/versions", - }, - items: [ - { - type: "autogenerated", - dirName: "guides/migration/versions", - }, - ], - }, - { - type: "category", - label: "Tools", - link: { - type: "generated-index", - title: "Tool migration guides", - description: - "Learn how to migrate to dbt from other tools and platforms.", - slug: "/guides/migration/tools", - }, - items: [ - { - type: "category", - label: "Migrating from stored procedures", - link: { - type: "doc", - id: "guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures", - }, - items: [ - "guides/migration/tools/migrating-from-stored-procedures/2-inserts", - "guides/migration/tools/migrating-from-stored-procedures/3-updates", - "guides/migration/tools/migrating-from-stored-procedures/4-deletes", - "guides/migration/tools/migrating-from-stored-procedures/5-merges", - "guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion", - ], - }, - "guides/migration/tools/migrating-from-spark-to-databricks", - "guides/migration/tools/refactoring-legacy-sql", - ], - }, - ], - }, - { - type: "category", - label: "dbt Ecosystem", - link: { - type: "generated-index", - title: "dbt Ecosystem guides", - description: "Learn about the dbt ecosystem and how to build with dbt.", - slug: "/guides/dbt-ecosystem/", - }, - items: [ - { - type: "category", - label: "Adapter development", - link: { - type: "doc", - id: "guides/dbt-ecosystem/adapter-development/1-what-are-adapters", - }, - items: [ - "guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter", - "guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter", - ], - }, - { - type: "category", - label: "dbt Python Snowpark", - link: { - type: "doc", - id: "guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark", - }, - items: [ - "guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration", - "guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source", - "guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt", - "guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name", - "guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure", - "guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure", - "guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging", - "guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations", - "guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations", - "guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep", - "guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-prediction", - "guides/dbt-ecosystem/dbt-python-snowpark/13-testing", - "guides/dbt-ecosystem/dbt-python-snowpark/14-documentation", - "guides/dbt-ecosystem/dbt-python-snowpark/15-deployment", - ], - }, - { - type: "category", - label: "Databricks and dbt", - link: { - type: "doc", - id: "guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project", - }, - items: [ - "guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices", - "guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks", - "guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project", - ], - }, - "guides/dbt-ecosystem/sl-partner-integration-guide", - ], - }, - { - type: "category", - label: "Advanced", - items: [ - "guides/advanced/creating-new-materializations", - "guides/advanced/using-jinja", - ], - }, - { - type: "category", - label: "Legacy", - items: [ - "guides/legacy/debugging-schema-names", - "guides/legacy/best-practices", - "guides/legacy/building-packages", - "guides/legacy/videos", + "best-practices/debugging-errors", + "best-practices/writing-custom-generic-tests", ], }, ], From 7cd0cf91b7b8eb13bd256ff44dd9600791117d62 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:43:49 -0700 Subject: [PATCH 047/152] fixing best-practice links --- website/blog/2021-02-05-dbt-project-checklist.md | 2 +- ...2-08-12-how-we-shaved-90-minutes-off-long-running-model.md | 2 +- .../docs/guides/migration/versions/08-upgrading-to-v1.0.md | 2 +- website/docs/reference/node-selection/syntax.md | 4 ++-- website/docs/sql-reference/clauses/sql-limit.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/website/blog/2021-02-05-dbt-project-checklist.md b/website/blog/2021-02-05-dbt-project-checklist.md index dbe2c10f408..afca49af4b4 100644 --- a/website/blog/2021-02-05-dbt-project-checklist.md +++ b/website/blog/2021-02-05-dbt-project-checklist.md @@ -156,7 +156,7 @@ This post is the checklist I created to guide our internal work, and I’m shari **Useful links** -* [Version control](/guides/legacy/best-practices#version-control-your-dbt-project) +* [Version control](/best-practices/best-practices#version-control-your-dbt-project) * [dbt Labs' PR Template](/blog/analytics-pull-request-template) ## ✅ Documentation diff --git a/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md b/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md index 020a48c763f..4e4b667dc9d 100644 --- a/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md +++ b/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md @@ -286,7 +286,7 @@ Developing an analytic code base is an ever-evolving process. What worked well w 4. **Test on representative data** - Testing on a [subset of data](https://docs.getdbt.com/guides/legacy/best-practices#limit-the-data-processed-when-in-development) is a great general practice. It allows you to iterate quickly, and doesn’t waste resources. However, there are times when you need to test on a larger dataset for problems like disk spillage to come to the fore. Testing on large data is hard and expensive, so make sure you have a good idea of the solution before you commit to this step. + Testing on a [subset of data](https://docs.getdbt.com/best-practices/best-practices#limit-the-data-processed-when-in-development) is a great general practice. It allows you to iterate quickly, and doesn’t waste resources. However, there are times when you need to test on a larger dataset for problems like disk spillage to come to the fore. Testing on large data is hard and expensive, so make sure you have a good idea of the solution before you commit to this step. 5. **Repeat** diff --git a/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md b/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md index 9fc7991c087..0ef9b8029d6 100644 --- a/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md +++ b/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md @@ -68,5 +68,5 @@ Several under-the-hood changes from past minor versions, tagged with deprecation - [Parsing](/reference/parsing): partial parsing and static parsing have been turned on by default. - [Global configs](/reference/global-configs/about-global-configs) have been standardized. Related updates to [global CLI flags](/reference/global-cli-flags) and [`profiles.yml`](/docs/core/connect-data-platform/profiles.yml). - [The `init` command](/reference/commands/init) has a whole new look and feel. It's no longer just for first-time users. -- Add `result:` subselectors for smarter reruns when dbt models have errors and tests fail. See examples: [Pro-tips for Workflows](/guides/legacy/best-practices#pro-tips-for-workflows) +- Add `result:` subselectors for smarter reruns when dbt models have errors and tests fail. See examples: [Pro-tips for Workflows](/best-practices/best-practices#pro-tips-for-workflows) - Secret-prefixed [env vars](/reference/dbt-jinja-functions/env_var) are now allowed only in `profiles.yml` + `packages.yml` diff --git a/website/docs/reference/node-selection/syntax.md b/website/docs/reference/node-selection/syntax.md index bb2aeefd742..34085d339e6 100644 --- a/website/docs/reference/node-selection/syntax.md +++ b/website/docs/reference/node-selection/syntax.md @@ -96,7 +96,7 @@ by comparing code in the current project against the state manifest. - [Deferring](/reference/node-selection/defer) to another environment, whereby dbt can identify upstream, unselected resources that don't exist in your current environment and instead "defer" their references to the environment provided by the state manifest. - The [`dbt clone` command](/reference/commands/clone), whereby dbt can clone nodes based on their location in the manifest provided to the `--state` flag. -Together, the `state:` selector and deferral enable ["slim CI"](/guides/legacy/best-practices#run-only-modified-models-to-test-changes-slim-ci). We expect to add more features in future releases that can leverage artifacts passed to the `--state` flag. +Together, the `state:` selector and deferral enable ["slim CI"](/best-practices/best-practices#run-only-modified-models-to-test-changes-slim-ci). We expect to add more features in future releases that can leverage artifacts passed to the `--state` flag. ### Establishing state @@ -190,7 +190,7 @@ dbt build --select "source_status:fresher+" ``` -For more example commands, refer to [Pro-tips for workflows](/guides/legacy/best-practices.md#pro-tips-for-workflows). +For more example commands, refer to [Pro-tips for workflows](/best-practices/best-practices.md#pro-tips-for-workflows). ### The "source_status" status diff --git a/website/docs/sql-reference/clauses/sql-limit.md b/website/docs/sql-reference/clauses/sql-limit.md index 74cc2e12123..d17affdef45 100644 --- a/website/docs/sql-reference/clauses/sql-limit.md +++ b/website/docs/sql-reference/clauses/sql-limit.md @@ -51,7 +51,7 @@ This simple query using the [Jaffle Shop’s](https://github.com/dbt-labs/jaffle After ensuring that this is the result you want from this query, you can omit the LIMIT in your final data model. :::tip Save money and time by limiting data in development -You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](https://docs.getdbt.com/guides/legacy/best-practices#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. +You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](https://docs.getdbt.com/best-practices/best-practices#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. ::: ## LIMIT syntax in Snowflake, Databricks, BigQuery, and Redshift From 8274381e6483cdca0e22fd244270f60c6aea928a Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 30 Oct 2023 11:19:42 -0400 Subject: [PATCH 048/152] update colors --- .../components/quickstartTOC/styles.module.css | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 8a3885c34ad..04d62188e95 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -34,13 +34,13 @@ height: 30px; text-align: center; line-height: 27px; - color: var(--color-light-teal); - border: solid 1px var(--color-light-teal); + color: var(--color-green-blue); + border: solid 1px var(--color-green-blue); margin-bottom: auto; } .tocList .active span { - background: var(--color-light-teal); + background: var( --color-green-blue); color: var(--color-white); } @@ -54,7 +54,7 @@ html[data-theme="dark"] .tocList li span { } html[data-theme="dark"] .tocList .active span { - border-color: var(--color-light-teal); + border-color: var(--color-green-blue); } .tocItem { @@ -75,14 +75,15 @@ html[data-theme="dark"] .tocList .active span { transition-property: color, background, border-color; transition-duration: var(--ifm-button-transition-duration); transition-timing-function: var(--ifm-transition-timing-default); - border: 2px solid var(--color-light-teal); + border: 2px solid var(--color-green-blue); + color: var(--color-green-blue); border-radius: 5px; width: 125px; text-align: center; } .stepWrapper .buttonContainer a:hover { - background: var(--color-light-teal); + background: var(--color-green-blue); color: var(--color-white); } @@ -109,7 +110,7 @@ html[data-theme="dark"] .stepWrapper .buttonContainer a:hover { } .stepWrapper[data-step="1"] a.nextButton { - background: var(--color-light-teal); + background: var(--color-green-blue); color: var(--color-white); } From 42689310b6088dad97bd9ae7c7f93de17b2e4df8 Mon Sep 17 00:00:00 2001 From: john-rock Date: Mon, 30 Oct 2023 11:44:54 -0400 Subject: [PATCH 049/152] update styles --- .../src/components/quickstartGuideCard/styles.module.css | 2 +- website/src/components/quickstartTOC/styles.module.css | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/website/src/components/quickstartGuideCard/styles.module.css b/website/src/components/quickstartGuideCard/styles.module.css index aa1538cdeaa..5df40c8479e 100644 --- a/website/src/components/quickstartGuideCard/styles.module.css +++ b/website/src/components/quickstartGuideCard/styles.module.css @@ -1,6 +1,6 @@ .quickstartCard { outline: 1px solid #EFF2F3; - border-radius: var(--border-radius); + border-radius: 10px; box-shadow: 0px 11px 24px rgba(138, 138, 138, .1); padding: 2.5rem 2.5rem 1.5rem 2.5rem; flex: 0 0 30%; diff --git a/website/src/components/quickstartTOC/styles.module.css b/website/src/components/quickstartTOC/styles.module.css index 04d62188e95..892e6f73be6 100644 --- a/website/src/components/quickstartTOC/styles.module.css +++ b/website/src/components/quickstartTOC/styles.module.css @@ -10,15 +10,14 @@ flex-shrink: 0; padding-right: 4rem; margin-right: 4rem; - border-right: solid 5px #E0E3E8; + border-right: solid 4px #EFF2F3; } .tocList li { padding: 1rem; display: block; - border: 2px solid #EFF2F3; - box-shadow: 0px 10px 16px 0px rgba(31, 41, 55, 0.20); - border-radius: 10px; + box-shadow: 0px 10px 16px 0px rgba(31, 41, 55, 0.10); + border-radius: 8px; margin-bottom: 1rem; display: grid; grid-template-columns: 1fr 5fr; From 98a5c3b21adfd89994fd4f5ca440df17b47f6fca Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:45:55 -0700 Subject: [PATCH 050/152] moving more guides --- .../blog/2021-02-05-dbt-project-checklist.md | 6 +- ...nfigure-your-dbt-repository-one-or-many.md | 2 +- .../2021-11-23-how-to-upgrade-dbt-versions.md | 2 +- ...11-26-welcome-to-the-dbt-developer-blog.md | 2 +- ...1-11-29-dbt-airflow-spiritual-alignment.md | 4 +- ...-05-17-stakeholder-friendly-model-names.md | 2 +- website/blog/2022-06-30-lower-sql-function.md | 2 +- .../2022-07-19-migrating-from-stored-procs.md | 2 +- website/blog/2022-07-26-pre-commit-dbt.md | 2 +- ...haved-90-minutes-off-long-running-model.md | 2 +- website/blog/2022-08-22-narrative-modeling.md | 2 +- ...2022-09-08-konmari-your-query-migration.md | 2 +- ...022-11-22-move-spreadsheets-to-your-dwh.md | 4 +- .../blog/2022-11-30-dbt-project-evaluator.md | 6 +- ...ractices.md => best-practice-workflows.md} | 4 +- .../semantic-layer-5-refactor-a-mart.md | 2 +- .../how-we-structure/2-staging.md | 4 +- .../5-semantic-layer-marts.md | 4 +- .../how-we-style/6-how-we-style-conclusion.md | 2 +- ...ions-guide-2-available-materializations.md | 2 +- ...rializations-guide-4-incremental-models.md | 2 +- ...materializations-guide-5-best-practices.md | 2 +- ...terializations-guide-6-examining-builds.md | 4 +- .../materializations-guide-7-conclusion.md | 2 +- .../docs/community/resources/getting-help.md | 2 +- website/docs/docs/build/jinja-macros.md | 2 +- website/docs/docs/build/projects.md | 2 +- website/docs/docs/build/python-models.md | 4 +- website/docs/docs/build/tests.md | 2 +- .../cloud/dbt-cloud-ide/dbt-cloud-tips.md | 2 +- .../docs/cloud/dbt-cloud-ide/lint-format.md | 2 +- .../docs/collaborate/govern/model-access.md | 2 +- .../govern/project-dependencies.md | 4 +- website/docs/docs/dbt-cloud-environments.md | 2 +- .../03-Oct-2023/product-docs-sept-rn.md | 2 +- .../04-Sept-2023/product-docs-summer-rn.md | 2 +- .../docs/docs/deploy/deploy-environments.md | 4 +- website/docs/docs/environments-in-dbt.md | 2 +- website/docs/docs/introduction.md | 2 +- website/docs/faqs/Jinja/jinja-whitespace.md | 2 +- .../faqs/Models/available-materializations.md | 2 +- .../Project/multiple-resource-yml-files.md | 2 +- .../docs/faqs/Project/resource-yml-name.md | 2 +- .../docs/faqs/Project/structure-a-project.md | 2 +- .../docs/faqs/Project/why-not-write-dml.md | 2 +- .../docs/faqs/Tests/custom-test-thresholds.md | 2 +- .../Warehouse/db-connection-dbt-compile.md | 2 +- website/docs/guides/airflow-and-dbt-cloud.md | 2 +- .../guides/{legacy => }/building-packages.md | 48 ++++--- .../guides/creating-new-materializations.md | 2 +- .../dbt-unity-catalog-best-practices.md | 8 +- ...w-to-set-up-your-databricks-dbt-project.md | 2 +- ...ow_to_optimize_dbt_models_on_databricks.md | 4 +- ...uctionizing-your-dbt-databricks-project.md | 2 +- .../dbt-python-snowpark/7-folder-structure.md | 4 +- .../8-sources-and-staging.md | 2 +- .../{legacy => }/debugging-schema-names.md | 29 +++-- .../creating-date-partitioned-tables.md | 117 ------------------ website/docs/guides/legacy/videos.md | 13 -- .../1-migrating-from-stored-procedures.md | 2 +- .../migration/tools/refactoring-legacy-sql.md | 2 +- .../versions/08-upgrading-to-v1.0.md | 4 +- .../versions/10-upgrading-to-v0.20.md | 2 +- .../11-Older versions/upgrading-to-0-15-0.md | 2 +- .../orchestration/set-up-ci/2-quick-setup.md | 2 +- .../orchestration/set-up-ci/4-lint-on-push.md | 2 +- .../set-up-ci/5-multiple-checks.md | 2 +- .../dbt-jinja-functions/run_query.md | 2 +- website/docs/reference/events-logging.md | 2 +- .../docs/reference/node-selection/syntax.md | 4 +- .../reference/resource-configs/contract.md | 2 +- .../reference/resource-properties/tests.md | 2 +- .../aggregate-functions/sql-array-agg.md | 2 +- .../aggregate-functions/sql-avg.md | 2 +- .../aggregate-functions/sql-round.md | 2 +- .../docs/sql-reference/clauses/sql-limit.md | 2 +- .../sql-reference/clauses/sql-order-by.md | 2 +- .../sql-reference/joins/sql-inner-join.md | 2 +- .../docs/sql-reference/joins/sql-left-join.md | 2 +- .../docs/sql-reference/joins/sql-self-join.md | 4 +- .../docs/sql-reference/operators/sql-not.md | 2 +- website/docs/sql-reference/other/sql-cast.md | 4 +- .../docs/sql-reference/other/sql-comments.md | 2 +- .../sql-reference/statements/sql-select.md | 4 +- .../string-functions/sql-lower.md | 2 +- .../string-functions/sql-trim.md | 2 +- .../string-functions/sql-upper.md | 2 +- website/docs/terms/dag.md | 4 +- website/docs/terms/data-lineage.md | 4 +- website/docs/terms/data-wrangling.md | 4 +- website/docs/terms/dimensional-modeling.md | 6 +- website/docs/terms/dry.md | 2 +- website/docs/terms/idempotent.md | 2 +- website/docs/terms/view.md | 2 +- website/sidebars.js | 1 + .../environment-setup/many-branch-git.png | Bin .../many-deployments-table.png | Bin .../environment-setup/one-branch-git.png | Bin .../one-deployment-table.png | Bin .../how-we-structure/narrowing-dag.png | Bin .../how-we-structure/widening-dag.png | Bin .../materializations/dbt-build-output.png | Bin .../materializations/incremental-diagram.png | Bin .../materializations/model-timing-diagram.png | Bin .../snowflake-query-timing.png | Bin .../materializations/tables-and-views.png | Bin .../semantic-layer/orders_erd.png | Bin website/vercel.json | 5 + 108 files changed, 176 insertions(+), 273 deletions(-) rename website/docs/best-practices/{best-practices.md => best-practice-workflows.md} (99%) rename website/docs/guides/{legacy => }/building-packages.md (88%) rename website/docs/guides/{legacy => }/debugging-schema-names.md (84%) delete mode 100644 website/docs/guides/legacy/creating-date-partitioned-tables.md delete mode 100644 website/docs/guides/legacy/videos.md rename website/static/img/{guides => }/best-practices/environment-setup/many-branch-git.png (100%) rename website/static/img/{guides => }/best-practices/environment-setup/many-deployments-table.png (100%) rename website/static/img/{guides => }/best-practices/environment-setup/one-branch-git.png (100%) rename website/static/img/{guides => }/best-practices/environment-setup/one-deployment-table.png (100%) rename website/static/img/{guides => }/best-practices/how-we-structure/narrowing-dag.png (100%) rename website/static/img/{guides => }/best-practices/how-we-structure/widening-dag.png (100%) rename website/static/img/{guides => }/best-practices/materializations/dbt-build-output.png (100%) rename website/static/img/{guides => }/best-practices/materializations/incremental-diagram.png (100%) rename website/static/img/{guides => }/best-practices/materializations/model-timing-diagram.png (100%) rename website/static/img/{guides => }/best-practices/materializations/snowflake-query-timing.png (100%) rename website/static/img/{guides => }/best-practices/materializations/tables-and-views.png (100%) rename website/static/img/{guides => }/best-practices/semantic-layer/orders_erd.png (100%) diff --git a/website/blog/2021-02-05-dbt-project-checklist.md b/website/blog/2021-02-05-dbt-project-checklist.md index afca49af4b4..9820c279b0f 100644 --- a/website/blog/2021-02-05-dbt-project-checklist.md +++ b/website/blog/2021-02-05-dbt-project-checklist.md @@ -139,7 +139,7 @@ This post is the checklist I created to guide our internal work, and I’m shari * [Sources](/docs/build/sources/) * [Refs](/reference/dbt-jinja-functions/ref/) * [tags](/reference/resource-configs/tags/) -* [Jinja docs](/guides/advanced/using-jinja) +* [Jinja docs](/guides/using-jinja) ## ✅ Testing & Continuous Integration ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -156,7 +156,7 @@ This post is the checklist I created to guide our internal work, and I’m shari **Useful links** -* [Version control](/best-practices/best-practices#version-control-your-dbt-project) +* [Version control](/best-practices/best-practice-workflows#version-control-your-dbt-project) * [dbt Labs' PR Template](/blog/analytics-pull-request-template) ## ✅ Documentation @@ -252,7 +252,7 @@ Thanks to Christine Berger for her DAG diagrams! **Useful links** -* [How we structure our dbt Project](/guides/best-practices/how-we-structure/1-guide-overview) +* [How we structure our dbt Project](/best-practices/how-we-structure/1-guide-overview) * [Coalesce DAG Audit Talk](https://www.youtube.com/watch?v=5W6VrnHVkCA&t=2s) * [Modular Data Modeling Technique](https://getdbt.com/analytics-engineering/modular-data-modeling-technique/) * [Understanding Threads](/docs/running-a-dbt-project/using-threads) diff --git a/website/blog/2021-02-09-how-to-configure-your-dbt-repository-one-or-many.md b/website/blog/2021-02-09-how-to-configure-your-dbt-repository-one-or-many.md index 50d09625436..8a986a12f27 100644 --- a/website/blog/2021-02-09-how-to-configure-your-dbt-repository-one-or-many.md +++ b/website/blog/2021-02-09-how-to-configure-your-dbt-repository-one-or-many.md @@ -159,4 +159,4 @@ All of the above configurations “work”. And as detailed, they each solve for 2. Figure out what may be a pain point in the future and try to plan for it from the beginning. 3. Don’t over-complicate things until you have the right reason. As I said in my Coalesce talk: **don’t drag your skeletons from one closet to another** 💀! -**Note:** Our attempt in writing guides like this and [How we structure our dbt projects](/guides/best-practices/how-we-structure/1-guide-overview) aren’t to try to convince you that our way is right; it is to hopefully save you the hundreds of hours it has taken us to form those opinions! +**Note:** Our attempt in writing guides like this and [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) aren’t to try to convince you that our way is right; it is to hopefully save you the hundreds of hours it has taken us to form those opinions! diff --git a/website/blog/2021-11-23-how-to-upgrade-dbt-versions.md b/website/blog/2021-11-23-how-to-upgrade-dbt-versions.md index 87b3ea7bd1e..ac7946c59bc 100644 --- a/website/blog/2021-11-23-how-to-upgrade-dbt-versions.md +++ b/website/blog/2021-11-23-how-to-upgrade-dbt-versions.md @@ -156,7 +156,7 @@ Once your compilation issues are resolved, it's time to run your job for real, t After that, make sure that your CI environment in dbt Cloud or your orchestrator is on the right dbt version, then open a PR. -If you're using [Slim CI](https://docs.getdbt.com/docs/guides/best-practices#run-only-modified-models-to-test-changes-slim-ci), keep in mind that artifacts aren't necessarily compatible from one version to another, so you won't be able to use it until the job you defer to has completed a run with the upgraded dbt version. This doesn't impact our example because support for Slim CI didn't come out until 0.18.0. +If you're using [Slim CI](https://docs.getdbt.com/docs/best-practices#run-only-modified-models-to-test-changes-slim-ci), keep in mind that artifacts aren't necessarily compatible from one version to another, so you won't be able to use it until the job you defer to has completed a run with the upgraded dbt version. This doesn't impact our example because support for Slim CI didn't come out until 0.18.0. ## Step 7. Merge and communicate diff --git a/website/blog/2021-11-26-welcome-to-the-dbt-developer-blog.md b/website/blog/2021-11-26-welcome-to-the-dbt-developer-blog.md index c6fff54b465..8db2407afdb 100644 --- a/website/blog/2021-11-26-welcome-to-the-dbt-developer-blog.md +++ b/website/blog/2021-11-26-welcome-to-the-dbt-developer-blog.md @@ -26,7 +26,7 @@ So let’s all commit to sharing our hard won knowledge with each other—and in The purpose of this blog is to double down on our long running commitment to contributing to the knowledge loop. -From early posts like ‘[The Startup Founders Guide to Analytics’](https://thinkgrowth.org/the-startup-founders-guide-to-analytics-1d2176f20ac1) to foundational guides like [‘How We Structure Our dbt Projects](/guides/best-practices/how-we-structure/1-guide-overview)’, we’ve had a long standing goal of working with the community to create practical, hands-on tutorials and guides which distill the knowledge we’ve been able to collectively gather. +From early posts like ‘[The Startup Founders Guide to Analytics’](https://thinkgrowth.org/the-startup-founders-guide-to-analytics-1d2176f20ac1) to foundational guides like [‘How We Structure Our dbt Projects](/best-practices/how-we-structure/1-guide-overview)’, we’ve had a long standing goal of working with the community to create practical, hands-on tutorials and guides which distill the knowledge we’ve been able to collectively gather. dbt as a product is based around the philosophy that even the most complicated problems can be broken down into modular, reusable components, then mixed and matched to create something novel. diff --git a/website/blog/2021-11-29-dbt-airflow-spiritual-alignment.md b/website/blog/2021-11-29-dbt-airflow-spiritual-alignment.md index fd1a11c41cf..b179c0f5c7c 100644 --- a/website/blog/2021-11-29-dbt-airflow-spiritual-alignment.md +++ b/website/blog/2021-11-29-dbt-airflow-spiritual-alignment.md @@ -91,7 +91,7 @@ The common skills needed for implementing any flavor of dbt (Core or Cloud) are: * SQL: ‘nuff said * YAML: required to generate config files for [writing tests on data models](/docs/build/tests) -* [Jinja](/guides/advanced/using-jinja): allows you to write DRY code (using [macros](/docs/build/jinja-macros), for loops, if statements, etc) +* [Jinja](/guides/using-jinja): allows you to write DRY code (using [macros](/docs/build/jinja-macros), for loops, if statements, etc) YAML + Jinja can be learned pretty quickly, but SQL is the non-negotiable you’ll need to get started. @@ -176,7 +176,7 @@ Instead you can now use the following command: `dbt build –select result:error+ –defer –state ` … and that’s it! -You can see more examples [here](https://docs.getdbt.com/docs/guides/best-practices#run-only-modified-models-to-test-changes-slim-ci). +You can see more examples [here](https://docs.getdbt.com/docs/best-practices#run-only-modified-models-to-test-changes-slim-ci). This means that whether you’re actively developing or you simply want to rerun a scheduled job (because of, say, permission errors or timeouts in your database), you now have a unified approach to doing both. diff --git a/website/blog/2022-05-17-stakeholder-friendly-model-names.md b/website/blog/2022-05-17-stakeholder-friendly-model-names.md index 0e0ccad5c96..39107035465 100644 --- a/website/blog/2022-05-17-stakeholder-friendly-model-names.md +++ b/website/blog/2022-05-17-stakeholder-friendly-model-names.md @@ -157,7 +157,7 @@ These 3 parts go from least granular (general) to most granular (specific) so yo ### Coming up... -In this part of the series, we talked about why the model name is the center of understanding for the purpose and content within a model. In the in the upcoming ["How We Structure Our dbt Projects"](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) guide, you can explore how to use this naming pattern with more specific examples in different parts of your dbt DAG that cover regular use cases: +In this part of the series, we talked about why the model name is the center of understanding for the purpose and content within a model. In the in the upcoming ["How We Structure Our dbt Projects"](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) guide, you can explore how to use this naming pattern with more specific examples in different parts of your dbt DAG that cover regular use cases: - How would you name a model that is filtered on some columns - Do we recommend naming snapshots in a specific way diff --git a/website/blog/2022-06-30-lower-sql-function.md b/website/blog/2022-06-30-lower-sql-function.md index c50af5f3fb3..3f7cff44ccb 100644 --- a/website/blog/2022-06-30-lower-sql-function.md +++ b/website/blog/2022-06-30-lower-sql-function.md @@ -75,7 +75,7 @@ After running this query, the `customers` table will look a little something lik Now, all characters in the `first_name` and `last_name` columns are lowercase. > **Where do you lower?** -> Changing all string columns to lowercase to create uniformity across data sources typically happens in our dbt project’s [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lowercasing, should ideally happen in staging models to create downstream uniformity. It’s also more performant in downstream models that join on string values to join on strings that are of all the same casing versus having to join and perform lowercasing at the same time. +> Changing all string columns to lowercase to create uniformity across data sources typically happens in our dbt project’s [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lowercasing, should ideally happen in staging models to create downstream uniformity. It’s also more performant in downstream models that join on string values to join on strings that are of all the same casing versus having to join and perform lowercasing at the same time. ## Why we love it diff --git a/website/blog/2022-07-19-migrating-from-stored-procs.md b/website/blog/2022-07-19-migrating-from-stored-procs.md index 691284a49e9..e2afdbfcd66 100644 --- a/website/blog/2022-07-19-migrating-from-stored-procs.md +++ b/website/blog/2022-07-19-migrating-from-stored-procs.md @@ -54,7 +54,7 @@ With dbt, we work towards creating simpler, more transparent data pipelines like ![Diagram of what data flows look like with dbt. It's easier to trace lineage in this setup.](/img/blog/2022-07-19-migrating-from-stored-procs/dbt-diagram.png) -Tight [version control integration](https://docs.getdbt.com/docs/guides/best-practices#version-control-your-dbt-project) is an added benefit of working with dbt. By leveraging the power of git-based tools, dbt enables you to integrate and test changes to transformation pipelines much faster than you can with other approaches. We often see teams who work in stored procedures making changes to their code without any notion of tracking those changes over time. While that’s more of an issue with the team’s chosen workflow than a problem with stored procedures per se, it does reflect how legacy tooling makes analytics work harder than necessary. +Tight [version control integration](https://docs.getdbt.com/docs/best-practices#version-control-your-dbt-project) is an added benefit of working with dbt. By leveraging the power of git-based tools, dbt enables you to integrate and test changes to transformation pipelines much faster than you can with other approaches. We often see teams who work in stored procedures making changes to their code without any notion of tracking those changes over time. While that’s more of an issue with the team’s chosen workflow than a problem with stored procedures per se, it does reflect how legacy tooling makes analytics work harder than necessary. ## Methodologies for migrating from stored procedures to dbt diff --git a/website/blog/2022-07-26-pre-commit-dbt.md b/website/blog/2022-07-26-pre-commit-dbt.md index fc100897ff0..e75bd622293 100644 --- a/website/blog/2022-07-26-pre-commit-dbt.md +++ b/website/blog/2022-07-26-pre-commit-dbt.md @@ -12,7 +12,7 @@ is_featured: true *Editor's note — since the creation of this post, the package pre-commit-dbt's ownership has moved to another team and it has been renamed to [dbt-checkpoint](https://github.com/dbt-checkpoint/dbt-checkpoint). A redirect has been set up, meaning that the code example below will still work. It is also possible to replace `repo: https://github.com/offbi/pre-commit-dbt` with `repo: https://github.com/dbt-checkpoint/dbt-checkpoint` in your `.pre-commit-config.yaml` file.* -At dbt Labs, we have [best practices](https://docs.getdbt.com/docs/guides/best-practices) we like to follow for the development of dbt projects. One of them, for example, is that all models should have at least `unique` and `not_null` tests on their primary key. But how can we enforce rules like this? +At dbt Labs, we have [best practices](https://docs.getdbt.com/docs/best-practices) we like to follow for the development of dbt projects. One of them, for example, is that all models should have at least `unique` and `not_null` tests on their primary key. But how can we enforce rules like this? That question becomes difficult to answer in large dbt projects. Developers might not follow the same conventions. They might not be aware of past decisions, and reviewing pull requests in git can become more complex. When dbt projects have hundreds of models, it's hard to know which models do not have any tests defined and aren't enforcing your conventions. diff --git a/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md b/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md index 4e4b667dc9d..e6a8b943051 100644 --- a/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md +++ b/website/blog/2022-08-12-how-we-shaved-90-minutes-off-long-running-model.md @@ -286,7 +286,7 @@ Developing an analytic code base is an ever-evolving process. What worked well w 4. **Test on representative data** - Testing on a [subset of data](https://docs.getdbt.com/best-practices/best-practices#limit-the-data-processed-when-in-development) is a great general practice. It allows you to iterate quickly, and doesn’t waste resources. However, there are times when you need to test on a larger dataset for problems like disk spillage to come to the fore. Testing on large data is hard and expensive, so make sure you have a good idea of the solution before you commit to this step. + Testing on a [subset of data](https://docs.getdbt.com/best-practices/best-practice-workflows#limit-the-data-processed-when-in-development) is a great general practice. It allows you to iterate quickly, and doesn’t waste resources. However, there are times when you need to test on a larger dataset for problems like disk spillage to come to the fore. Testing on large data is hard and expensive, so make sure you have a good idea of the solution before you commit to this step. 5. **Repeat** diff --git a/website/blog/2022-08-22-narrative-modeling.md b/website/blog/2022-08-22-narrative-modeling.md index a5418ccded1..a74c73fdbd1 100644 --- a/website/blog/2022-08-22-narrative-modeling.md +++ b/website/blog/2022-08-22-narrative-modeling.md @@ -177,7 +177,7 @@ To that final point, if presented with the DAG from the narrative modeling appro ### Users can tie business concepts to source data -- While the schema structure above is focused on business entities, there are still ample use cases for [staging and intermediate tables](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +- While the schema structure above is focused on business entities, there are still ample use cases for [staging and intermediate tables](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). - After cleaning up source data with staging tables, use the same “what happened” approach to more technical events, creating a three-node dependency from `stg_snowplow_events` to `int_page_click_captured` to `user_refreshed_cart` and thus answering the question “where do we get online user behavior information?” in a quick visit to the DAG in dbt docs. # Should your team use it? diff --git a/website/blog/2022-09-08-konmari-your-query-migration.md b/website/blog/2022-09-08-konmari-your-query-migration.md index f7d7cc74ead..c1472058150 100644 --- a/website/blog/2022-09-08-konmari-your-query-migration.md +++ b/website/blog/2022-09-08-konmari-your-query-migration.md @@ -108,7 +108,7 @@ Here are a few things to look for: ## Steps 4 & 5: Tidy by category and follow the right order—upstream to downstream -We are ready to unpack our kitchen. Use your design as a guideline for [modularization](/guides/best-practices/how-we-structure/1-guide-overview). +We are ready to unpack our kitchen. Use your design as a guideline for [modularization](/best-practices/how-we-structure/1-guide-overview). - Build your staging tables first, and then your intermediate tables in your pre-planned buckets. - Important, reusable joins that are performed in the final query should be moved upstream into their own modular models, as well as any joins that are repeated in your query. diff --git a/website/blog/2022-11-22-move-spreadsheets-to-your-dwh.md b/website/blog/2022-11-22-move-spreadsheets-to-your-dwh.md index ba5dddcae19..93cf91efeed 100644 --- a/website/blog/2022-11-22-move-spreadsheets-to-your-dwh.md +++ b/website/blog/2022-11-22-move-spreadsheets-to-your-dwh.md @@ -102,7 +102,7 @@ Instead of syncing all cells in a sheet, you create a [named range](https://five -Beware of inconsistent data types though—if someone types text into a column that was originally numeric, Fivetran will automatically convert the column to a string type which might cause issues in your downstream transformations. [The recommended workaround](https://fivetran.com/docs/files/google-sheets#typetransformationsandmapping) is to explicitly cast your types in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging) to ensure that any undesirable records are converted to null. +Beware of inconsistent data types though—if someone types text into a column that was originally numeric, Fivetran will automatically convert the column to a string type which might cause issues in your downstream transformations. [The recommended workaround](https://fivetran.com/docs/files/google-sheets#typetransformationsandmapping) is to explicitly cast your types in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) to ensure that any undesirable records are converted to null. #### Good fit for: @@ -192,4 +192,4 @@ Databricks also supports [pulling in data, such as spreadsheets, from external c Beyond the options we’ve already covered, there’s an entire world of other tools that can load data from your spreadsheets into your data warehouse. This is a living document, so if your preferred method isn't listed then please [open a PR](https://github.com/dbt-labs/docs.getdbt.com) and I'll check it out. -The most important things to consider are your files’ origins and formats—if you need your colleagues to upload files on a regular basis then try to provide them with a more user-friendly process; but if you just need two computers to talk to each other, or it’s a one-off file that will hardly ever change, then a more technical integration is totally appropriate. \ No newline at end of file +The most important things to consider are your files’ origins and formats—if you need your colleagues to upload files on a regular basis then try to provide them with a more user-friendly process; but if you just need two computers to talk to each other, or it’s a one-off file that will hardly ever change, then a more technical integration is totally appropriate. diff --git a/website/blog/2022-11-30-dbt-project-evaluator.md b/website/blog/2022-11-30-dbt-project-evaluator.md index 558d8877d72..3ea7a459c35 100644 --- a/website/blog/2022-11-30-dbt-project-evaluator.md +++ b/website/blog/2022-11-30-dbt-project-evaluator.md @@ -34,7 +34,7 @@ Throughout these engagements, we began to take note of the common issues many an Maybe your team is facing some of these issues right now 👀 And that’s okay! We know that building an effective, scalable dbt project takes a lot of effort and brain power. Maybe you’ve inherited a legacy dbt project with a mountain of tech debt. Maybe you’re starting from scratch. Either way it can be difficult to know the best way to set your team up for success. Don’t worry, you’re in the right place! -Through solving these problems over and over, the Professional Services team began to hone our best practices for working with dbt and how analytics engineers could improve their dbt project. We added “solutions reviews'' to our list of service offerings — client engagements in which we evaluate a given dbt project and provide specific recommendations to improve performance, save developer time, and prevent misuse of dbt’s features. And in an effort to share these best practices with the wider dbt community, we developed a *lot* of content. We wrote articles on the Developer Blog (see [1](https://docs.getdbt.com/blog/on-the-importance-of-naming), [2](https://discourse.getdbt.com/t/your-essential-dbt-project-checklist/1377), and [3](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview)), gave [Coalesce talks](https://www.getdbt.com/coalesce-2020/auditing-model-layers-and-modularity-with-your-dag/), and created [training courses](https://courses.getdbt.com/courses/refactoring-sql-for-modularity). +Through solving these problems over and over, the Professional Services team began to hone our best practices for working with dbt and how analytics engineers could improve their dbt project. We added “solutions reviews'' to our list of service offerings — client engagements in which we evaluate a given dbt project and provide specific recommendations to improve performance, save developer time, and prevent misuse of dbt’s features. And in an effort to share these best practices with the wider dbt community, we developed a *lot* of content. We wrote articles on the Developer Blog (see [1](https://docs.getdbt.com/blog/on-the-importance-of-naming), [2](https://discourse.getdbt.com/t/your-essential-dbt-project-checklist/1377), and [3](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview)), gave [Coalesce talks](https://www.getdbt.com/coalesce-2020/auditing-model-layers-and-modularity-with-your-dag/), and created [training courses](https://courses.getdbt.com/courses/refactoring-sql-for-modularity). TIme and time again, we found that when teams are aligned with these best practices, their projects are more: @@ -63,10 +63,10 @@ Currently, the dbt_project_evaluator package covers five main categories: | Category | Example Best Practices | | --- | --- | -| Modeling | - Every [raw source](https://docs.getdbt.com/docs/build/sources) has a one-to-one relationship with a [staging model](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) to centralize data cleanup.
- Every model can be traced back to a declared source in the dbt project (i.e. no "root" models).
- End-of-DAG fanout remains under a specified threshold. | +| Modeling | - Every [raw source](https://docs.getdbt.com/docs/build/sources) has a one-to-one relationship with a [staging model](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) to centralize data cleanup.
- Every model can be traced back to a declared source in the dbt project (i.e. no "root" models).
- End-of-DAG fanout remains under a specified threshold. | | Testing | - Every model has a that is appropriately tested.
- The percentage of models that have minimum 1 test applied is greater than or equal to a specified threshold. | | Documentation | - Every model has a [description](https://docs.getdbt.com/reference/resource-properties/description).
- The percentage of models that have a description is greater than or equal to a specified threshold. | -| Structure | - All models are named with the appropriate prefix aligned according to their model types (e.g. staging models are prefixed with `stg_`).
- The sql file for each model is in the subdirectory aligned with the model type (e.g. intermediate models are in an [intermediate subdirectory](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate)).
- Each models subdirectory contains one .yml file that includes tests and documentation for all models within the given subdirectory. | +| Structure | - All models are named with the appropriate prefix aligned according to their model types (e.g. staging models are prefixed with `stg_`).
- The sql file for each model is in the subdirectory aligned with the model type (e.g. intermediate models are in an [intermediate subdirectory](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate)).
- Each models subdirectory contains one .yml file that includes tests and documentation for all models within the given subdirectory. | | Performance | - Every model that directly feeds into an [exposure](https://docs.getdbt.com/docs/build/exposures) is materialized as a .
- No models are dependent on chains of "non-physically-materialized" models greater than a specified threshold. | For the full up-to-date list of covered rules, check out the package’s [README](https://github.com/dbt-labs/dbt-project-evaluator#rules-1), which outlines for each misalignment of a best practice: diff --git a/website/docs/best-practices/best-practices.md b/website/docs/best-practices/best-practice-workflows.md similarity index 99% rename from website/docs/best-practices/best-practices.md rename to website/docs/best-practices/best-practice-workflows.md index 70300a5974f..4760aeff782 100644 --- a/website/docs/best-practices/best-practices.md +++ b/website/docs/best-practices/best-practice-workflows.md @@ -1,6 +1,6 @@ --- title: "Best practices for workflows" -id: "best-practices" +id: "best-practice-workflows" --- This page contains the collective wisdom of experienced users of dbt on how to best use it in your analytics work. Observing these best practices will help your analytics team work as effectively as possible, while implementing the pro-tips will add some polish to your dbt projects! @@ -58,7 +58,7 @@ All subsequent data models should be built on top of these models, reducing the Earlier versions of this documentation recommended implementing “base models” as the first layer of transformation, and gave advice on the SQL within these models. We realized that while the reasons behind this convention were valid, the specific advice around "base models" represented an opinion, so we moved it out of the official documentation. -You can instead find our opinions on [how we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +You can instead find our opinions on [how we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). ::: diff --git a/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md index b2efb39e9fc..dfdba2941e9 100644 --- a/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md +++ b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart.md @@ -72,7 +72,7 @@ So far we've been working in new pointing at a staging model to simplify things Now, let's tackle a thornier situation. Products and supplies both have dimensions and measures but no time dimension. Products has a one-to-one relationship with `order_items`, enriching that table, which is itself just a mapping table of products to orders. Additionally, products have a one-to-many relationship with supplies. The high-level ERD looks like the diagram below. - + So to calculate, for instance, the cost of ingredients and supplies for a given order, we'll need to do some joining and aggregating, but again we **lack a time dimension for products and supplies**. This is the signal to us that we'll **need to build a logical mart** and point our semantic model at that. diff --git a/website/docs/best-practices/how-we-structure/2-staging.md b/website/docs/best-practices/how-we-structure/2-staging.md index bcb589508e5..8eb91ff5b7b 100644 --- a/website/docs/best-practices/how-we-structure/2-staging.md +++ b/website/docs/best-practices/how-we-structure/2-staging.md @@ -12,7 +12,7 @@ We'll use an analogy for working with dbt throughout this guide: thinking modula ### Staging: Files and folders -Let's zoom into the staging directory from our `models` file tree [in the overview](/guides/best-practices/how-we-structure/1-guide-overview) and walk through what's going on here. +Let's zoom into the staging directory from our `models` file tree [in the overview](/best-practices/how-we-structure/1-guide-overview) and walk through what's going on here. ```shell models/staging @@ -106,7 +106,7 @@ select * from renamed - ❌ **Aggregations** — aggregations entail grouping, and we're not doing that at this stage. Remember - staging models are your place to create the building blocks you’ll use all throughout the rest of your project — if we start changing the grain of our tables by grouping in this layer, we’ll lose access to source data that we’ll likely need at some point. We just want to get our individual concepts cleaned and ready for use, and will handle aggregating values downstream. - ✅ **Materialized as views.** Looking at a partial view of our `dbt_project.yml` below, we can see that we’ve configured the entire staging directory to be materialized as views. As they’re not intended to be final artifacts themselves, but rather building blocks for later models, staging models should typically be materialized as views for two key reasons: - - Any downstream model (discussed more in [marts](/guides/best-practices/how-we-structure/4-marts)) referencing our staging models will always get the freshest data possible from all of the component views it’s pulling together and materializing + - Any downstream model (discussed more in [marts](/best-practices/how-we-structure/4-marts)) referencing our staging models will always get the freshest data possible from all of the component views it’s pulling together and materializing - It avoids wasting space in the warehouse on models that are not intended to be queried by data consumers, and thus do not need to perform as quickly or efficiently ```yaml diff --git a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md index adebc4a63c7..aca0ca3f283 100644 --- a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md +++ b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md @@ -3,7 +3,7 @@ title: "Marts for the Semantic Layer" id: "5-semantic-layer-marts" --- -The Semantic Layer alters some fundamental principles of how you organize your project. Using dbt without the Semantic Layer necessitates creating the most useful combinations of your building block components into wide, denormalized marts. On the other hand, the Semantic Layer leverages MetricFlow to denormalize every possible combination of components we've encoded dynamically. As such we're better served to bring more normalized models through from the logical layer into the Semantic Layer to maximize flexibility. This section will assume familiarity with the best practices laid out in the [How we build our metrics](https://docs.getdbt.com/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) guide, so check that out first for a more hands-on introduction to the Semantic Layer. +The Semantic Layer alters some fundamental principles of how you organize your project. Using dbt without the Semantic Layer necessitates creating the most useful combinations of your building block components into wide, denormalized marts. On the other hand, the Semantic Layer leverages MetricFlow to denormalize every possible combination of components we've encoded dynamically. As such we're better served to bring more normalized models through from the logical layer into the Semantic Layer to maximize flexibility. This section will assume familiarity with the best practices laid out in the [How we build our metrics](https://docs.getdbt.com/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) guide, so check that out first for a more hands-on introduction to the Semantic Layer. ## Semantic Layer: Files and folders @@ -39,7 +39,7 @@ models ## When to make a mart - ❓ If we can go directly to staging models and it's better to serve normalized models to the Semantic Layer, then when, where, and why would we make a mart? - - 🕰️ We have models that have measures but no time dimension to aggregate against. The details of this are laid out in the [Semantic Layer guide](https://docs.getdbt.com/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) but in short, we need a time dimension to aggregate against in MetricFlow. Dimensional tables that + - 🕰️ We have models that have measures but no time dimension to aggregate against. The details of this are laid out in the [Semantic Layer guide](https://docs.getdbt.com/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) but in short, we need a time dimension to aggregate against in MetricFlow. Dimensional tables that - 🧱 We want to **materialize** our model in various ways. - 👯 We want to **version** our model. - 🛒 We have various related models that make more sense as **one wider component**. diff --git a/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md b/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md index a6402e46870..75551f095d3 100644 --- a/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md +++ b/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md @@ -31,7 +31,7 @@ Our models (typically) fit into two main categories:\ Things to note: - There are different types of models that typically exist in each of the above categories. See [Model Layers](#model-layers) for more information. -- Read [How we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) for an example and more details around organization. +- Read [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) for an example and more details around organization. ## Model Layers diff --git a/website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md b/website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md index 54110b46385..9910e5f8269 100644 --- a/website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md +++ b/website/docs/best-practices/materializations/materializations-guide-2-available-materializations.md @@ -19,7 +19,7 @@ Views and tables and incremental models, oh my! In this section we’ll start ge **Views and Tables are the two basic categories** of object that we can create across warehouses. They exist natively as types of objects in the warehouse, as you can see from this screenshot of Snowflake (depending on your warehouse the interface will look a little different). **Incremental models** and other materializations types are a little bit different. They tell dbt to **construct tables in a special way**. -![Tables and views in the browser on Snowflake.](/img/guides/best-practices/materializations/tables-and-views.png) +![Tables and views in the browser on Snowflake.](/img/best-practices/materializations/tables-and-views.png) ### Views diff --git a/website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md b/website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md index 603cbc8cda1..ec613e39997 100644 --- a/website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md +++ b/website/docs/best-practices/materializations/materializations-guide-4-incremental-models.md @@ -76,7 +76,7 @@ So we’ve found a way to isolate the new rows we need to process. How then do w - 🌍  Lastly, if we’re building into a new environment and there’s **no previous run to reference**, or we need to **build the model from scratch.** Put another way, we’ll want a means to skip the incremental logic and transform all of our input data like a regular table if needed. - 😎 **Visualized below**, we’ve figured out how to get the red ‘new records’ portion selected, but we need to sort out the step to the right, where we stick those on to our model. -![Diagram visualizing how incremental models work](/img/guides/best-practices/materializations/incremental-diagram.png) +![Diagram visualizing how incremental models work](/img/best-practices/materializations/incremental-diagram.png) :::info 😌 Incremental models can be confusing at first, **take your time reviewing** this visual and the previous steps until you have a **clear mental model.** Be patient with yourself. This materialization will become second nature soon, but it’s tough at first. If you’re feeling confused the [dbt Community is here for you on the Forum and Slack](community/join). diff --git a/website/docs/best-practices/materializations/materializations-guide-5-best-practices.md b/website/docs/best-practices/materializations/materializations-guide-5-best-practices.md index a2cb22d5755..268a326eed0 100644 --- a/website/docs/best-practices/materializations/materializations-guide-5-best-practices.md +++ b/website/docs/best-practices/materializations/materializations-guide-5-best-practices.md @@ -58,7 +58,7 @@ models: As we’ve learned, views store only the logic of the transformation in the warehouse, so our runs take only a couple seconds per model (or less). What happens when we go to query the data though? -![Long query time from Snowflake](/img/guides/best-practices/materializations/snowflake-query-timing.png) +![Long query time from Snowflake](/img/best-practices/materializations/snowflake-query-timing.png) Our marts are slow to query! diff --git a/website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md b/website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md index 8c936bfcf52..0b18518d0bd 100644 --- a/website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md +++ b/website/docs/best-practices/materializations/materializations-guide-6-examining-builds.md @@ -18,7 +18,7 @@ hoverSnippet: Read this guide to understand how to examine your builds in dbt. That’s where dbt Cloud’s Model Timing visualization comes in extremely handy. If we’ve set up a [Job](/guides/bigquery) in dbt Cloud to run our models, we can use the Model Timing tab to pinpoint our longest-running models. -![dbt Cloud's Model Timing diagram](/img/guides/best-practices/materializations/model-timing-diagram.png) +![dbt Cloud's Model Timing diagram](/img/best-practices/materializations/model-timing-diagram.png) - 🧵 This view lets us see our **mapped out in threads** (up to 64 threads, we’re currently running with 4, so we get 4 tracks) over time. You can think of **each thread as a lane on a highway**. - ⌛ We can see above that `customer_status_histories` is **taking by far the most time**, so we may want to go ahead and **make that incremental**. @@ -29,7 +29,7 @@ If you aren’t using dbt Cloud, that’s okay! We don’t get a fancy visualiza If you’ve ever run dbt, whether `build`, `test`, `run` or something else, you’ve seen some output like below. Let’s take a closer look at how to read this. -![CLI output from a dbt build command](/img/guides/best-practices/materializations/dbt-build-output.png) +![CLI output from a dbt build command](/img/best-practices/materializations/dbt-build-output.png) - There are two entries per model, the **start** of a model’s build and the **completion**, which will include **how long** the model took to run. The **type** of model is included as well. For example: diff --git a/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md b/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md index 119563b9a50..c0c4e023a55 100644 --- a/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md +++ b/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md @@ -9,6 +9,6 @@ hoverSnippet: Read this conclusion to our guide on using materializations in dbt You're now following best practices in your project, and have optimized the materializations of your DAG. You’re equipped with the 3 main materializations that cover almost any analytics engineering situation! -There are more configs and materializations available, as well as specific materializations for certain platforms and adapters — and like everything with dbt, materializations are extensible, meaning you can create your own [custom materializations](/guides/advanced/creating-new-materializations) for your needs. So this is just the beginning of what you can do with these powerful configurations. +There are more configs and materializations available, as well as specific materializations for certain platforms and adapters — and like everything with dbt, materializations are extensible, meaning you can create your own [custom materializations](/guides/creating-new-materializations) for your needs. So this is just the beginning of what you can do with these powerful configurations. For the vast majority of users and companies though, tables, views, and incremental models will handle everything you can throw at them. Develop your intuition and expertise for these materializations, and you’ll be well on your way to tackling advanced analytics engineering problems. diff --git a/website/docs/community/resources/getting-help.md b/website/docs/community/resources/getting-help.md index 953d04434c4..658f7d154db 100644 --- a/website/docs/community/resources/getting-help.md +++ b/website/docs/community/resources/getting-help.md @@ -9,7 +9,7 @@ dbt is open source, and has a generous community behind it. Asking questions wel #### Search the existing documentation The docs site you're on is highly searchable, make sure to explore for the answer here as a first step. If you're new to dbt, try working through the [quickstart guide](/guides) first to get a firm foundation on the essential concepts. #### Try to debug the issue yourself -We have a handy guide on [debugging errors](/guides/best-practices/debugging-errors) to help out! This guide also helps explain why errors occur, and which docs you might need to search for help. +We have a handy guide on [debugging errors](/best-practices/debugging-errors) to help out! This guide also helps explain why errors occur, and which docs you might need to search for help. #### Search for answers using your favorite search engine We're committed to making more errors searchable, so it's worth checking if there's a solution already out there! Further, some errors related to installing dbt, the SQL in your models, or getting YAML right, are errors that are not-specific to dbt, so there may be other resources to check. diff --git a/website/docs/docs/build/jinja-macros.md b/website/docs/docs/build/jinja-macros.md index c5fd6b2e111..135db740f75 100644 --- a/website/docs/docs/build/jinja-macros.md +++ b/website/docs/docs/build/jinja-macros.md @@ -27,7 +27,7 @@ Jinja can be used in any SQL in a dbt project, including [models](/docs/build/sq :::info Ready to get started with Jinja and macros? -Check out the [tutorial on using Jinja](/guides/advanced/using-jinja) for a step-by-step example of using Jinja in a model, and turning it into a macro! +Check out the [tutorial on using Jinja](/guides/using-jinja) for a step-by-step example of using Jinja in a model, and turning it into a macro! ::: diff --git a/website/docs/docs/build/projects.md b/website/docs/docs/build/projects.md index eeed8e52f90..a54f6042cce 100644 --- a/website/docs/docs/build/projects.md +++ b/website/docs/docs/build/projects.md @@ -91,6 +91,6 @@ If you want to see what a mature, production project looks like, check out the [ ## Related docs -* [Best practices: How we structure our dbt projects](/guides/best-practices/how-we-structure/1-guide-overview) +* [Best practices: How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) * [Quickstarts for dbt Cloud](/guides) * [Quickstart for dbt Core](/guides/manual-install) diff --git a/website/docs/docs/build/python-models.md b/website/docs/docs/build/python-models.md index bff65362d06..3fe194a4cb7 100644 --- a/website/docs/docs/build/python-models.md +++ b/website/docs/docs/build/python-models.md @@ -67,7 +67,7 @@ models: - not_null tests: # Write your own validation logic (in SQL) for Python results - - [custom_generic_test](/guides/best-practices/writing-custom-generic-tests) + - [custom_generic_test](/best-practices/writing-custom-generic-tests) ```
@@ -716,4 +716,4 @@ You can also install packages at cluster creation time by [defining cluster prop - \ No newline at end of file + diff --git a/website/docs/docs/build/tests.md b/website/docs/docs/build/tests.md index 266f302f7df..3d86dc6a81b 100644 --- a/website/docs/docs/build/tests.md +++ b/website/docs/docs/build/tests.md @@ -112,7 +112,7 @@ You can find more information about these tests, and additional configurations ( ### More generic tests -Those four tests are enough to get you started. You'll quickly find you want to use a wider variety of tests—a good thing! You can also install generic tests from a package, or write your own, to use (and reuse) across your dbt project. Check out the [guide on custom generic tests](/guides/best-practices/writing-custom-generic-tests) for more information. +Those four tests are enough to get you started. You'll quickly find you want to use a wider variety of tests—a good thing! You can also install generic tests from a package, or write your own, to use (and reuse) across your dbt project. Check out the [guide on custom generic tests](/best-practices/writing-custom-generic-tests) for more information. :::info There are generic tests defined in some open source packages, such as [dbt-utils](https://hub.getdbt.com/dbt-labs/dbt_utils/latest/) and [dbt-expectations](https://hub.getdbt.com/calogica/dbt_expectations/latest/) — skip ahead to the docs on [packages](/docs/build/packages) to learn more! diff --git a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md index ad0d5466714..b90ac1bce01 100644 --- a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md +++ b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md @@ -46,7 +46,7 @@ There are default keyboard shortcuts that can help make development more product - Use [severity](/reference/resource-configs/severity) thresholds to set an acceptable number of failures for a test. - Use [incremental_strategy](/docs/build/incremental-models#about-incremental_strategy) in your incremental model config to implement the most effective behavior depending on the volume of your data and reliability of your unique keys. - Set `vars` in your `dbt_project.yml` to define global defaults for certain conditions, which you can then override using the `--vars` flag in your commands. -- Use [for loops](/guides/advanced/using-jinja#use-a-for-loop-in-models-for-repeated-sql) in Jinja to [DRY](https://docs.getdbt.com/terms/dry) up repetitive logic, such as selecting a series of columns that all require the same transformations and naming patterns to be applied. +- Use [for loops](/guides/using-jinja#use-a-for-loop-in-models-for-repeated-sql) in Jinja to [DRY](https://docs.getdbt.com/terms/dry) up repetitive logic, such as selecting a series of columns that all require the same transformations and naming patterns to be applied. - Instead of relying on post-hooks, use the [grants config](/reference/resource-configs/grants) to apply permission grants in the warehouse resiliently. - Define [source-freshness](/docs/build/sources#snapshotting-source-data-freshness) thresholds on your sources to avoid running transformations on data that has already been processed. - Use the `+` operator on the left of a model `dbt build --select +model_name` to run a model and all of its upstream dependencies. Use the `+` operator on the right of the model `dbt build --select model_name+` to run a model and everything downstream that depends on it. diff --git a/website/docs/docs/cloud/dbt-cloud-ide/lint-format.md b/website/docs/docs/cloud/dbt-cloud-ide/lint-format.md index 6a86f1aa14b..f145e76df11 100644 --- a/website/docs/docs/cloud/dbt-cloud-ide/lint-format.md +++ b/website/docs/docs/cloud/dbt-cloud-ide/lint-format.md @@ -127,7 +127,7 @@ group_by_and_order_by_style = implicit ``` -For more info on styling best practices, refer to [How we style our SQL](/guides/best-practices/how-we-style/2-how-we-style-our-sql). +For more info on styling best practices, refer to [How we style our SQL](/best-practices/how-we-style/2-how-we-style-our-sql). ::: diff --git a/website/docs/docs/collaborate/govern/model-access.md b/website/docs/docs/collaborate/govern/model-access.md index 765e833ac0c..76eb8bd6f6d 100644 --- a/website/docs/docs/collaborate/govern/model-access.md +++ b/website/docs/docs/collaborate/govern/model-access.md @@ -35,7 +35,7 @@ Why define model `groups`? There are two reasons: - It turns implicit relationships into an explicit grouping, with a defined owner. By thinking about the interface boundaries _between_ groups, you can have a cleaner (less entangled) DAG. In the future, those interface boundaries could be appropriate as the interfaces between separate projects. - It enables you to designate certain models as having "private" access—for use exclusively within that group. Other models will be restricted from referencing (taking a dependency on) those models. In the future, they won't be visible to other teams taking a dependency on your project—only "public" models will be. -If you follow our [best practices for structuring a dbt project](/guides/best-practices/how-we-structure/1-guide-overview), you're probably already using subdirectories to organize your dbt project. It's easy to apply a `group` label to an entire subdirectory at once: +If you follow our [best practices for structuring a dbt project](/best-practices/how-we-structure/1-guide-overview), you're probably already using subdirectories to organize your dbt project. It's easy to apply a `group` label to an entire subdirectory at once: diff --git a/website/docs/docs/collaborate/govern/project-dependencies.md b/website/docs/docs/collaborate/govern/project-dependencies.md index 9a1d8b59b68..bafc9bb04cf 100644 --- a/website/docs/docs/collaborate/govern/project-dependencies.md +++ b/website/docs/docs/collaborate/govern/project-dependencies.md @@ -113,7 +113,7 @@ with monthly_revenue as ( **Cycle detection:** Currently, "project" dependencies can only go in one direction, meaning that the `jaffle_finance` project could not add a new model that depends, in turn, on `jaffle_marketing.roi_by_channel`. dbt will check for cycles across projects and raise errors if any are detected. We are considering support for this pattern in the future, whereby dbt would still check for node-level cycles while allowing cycles at the project level. -For more guidance on how to use dbt Mesh, refer to the dedicated [dbt Mesh guide](/guides/best-practices/how-we-mesh/mesh-1-intro). +For more guidance on how to use dbt Mesh, refer to the dedicated [dbt Mesh guide](/best-practices/how-we-mesh/mesh-1-intro). ### Comparison @@ -139,4 +139,4 @@ If you're using private packages with the [git token method](/docs/build/package ## Related docs -- Refer to the [dbt Mesh](/guides/best-practices/how-we-mesh/mesh-1-intro) guide for more guidance on how to use dbt Mesh. +- Refer to the [dbt Mesh](/best-practices/how-we-mesh/mesh-1-intro) guide for more guidance on how to use dbt Mesh. diff --git a/website/docs/docs/dbt-cloud-environments.md b/website/docs/docs/dbt-cloud-environments.md index 8fa4522d47c..ac51a953cb7 100644 --- a/website/docs/docs/dbt-cloud-environments.md +++ b/website/docs/docs/dbt-cloud-environments.md @@ -45,4 +45,4 @@ To use the dbt Cloud IDE or dbt Cloud CLI, each developer will need to set up [p Deployment environments in dbt Cloud are necessary to execute scheduled jobs and use other features. A dbt Cloud project can have multiple deployment environments, allowing for flexibility and customization. However, a dbt Cloud project can only have one deployment environment that represents the production source of truth. -To learn more about dbt Cloud deployment environments and how to configure them, visit the [Deployment environments](/docs/deploy/deploy-environments) page. For our best practices guide, read [dbt Cloud environment best practices](https://docs.getdbt.com/guides/best-practices/environment-setup/1-env-guide-overview) for more info. +To learn more about dbt Cloud deployment environments and how to configure them, visit the [Deployment environments](/docs/deploy/deploy-environments) page. For our best practices guide, read [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview) for more info. diff --git a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md index 42a2c8daba1..3fdaa0eafe8 100644 --- a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md +++ b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/product-docs-sept-rn.md @@ -31,7 +31,7 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/): ## New 📚 Guides, ✏️ blog posts, and FAQs -Added a [style guide template](/guides/best-practices/how-we-style/6-how-we-style-conclusion#style-guide-template) that you can copy & paste to make sure you adhere to best practices when styling dbt projects! +Added a [style guide template](/best-practices/how-we-style/6-how-we-style-conclusion#style-guide-template) that you can copy & paste to make sure you adhere to best practices when styling dbt projects! ## Upcoming changes diff --git a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md index a647bb5f585..15891975e1d 100644 --- a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md +++ b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md @@ -40,4 +40,4 @@ You can provide feedback by opening a pull request or issue in [our repo](https: ## New 📚 Guides, ✏️ blog posts, and FAQs * Check out how these community members use the dbt community in the [Community spotlight](/community/spotlight). * Blog posts published this summer include [Optimizing Materialized Views with dbt](/blog/announcing-materialized-views), [Data Vault 2.0 with dbt Cloud](/blog/data-vault-with-dbt-cloud), and [Create dbt Documentation and Tests 10x faster with ChatGPT](/blog/create-dbt-documentation-10x-faster-with-chatgpt) -* We now have two new best practice guides: [How we build our metrics](/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) and [Set up Continuous Integration](/guides/orchestration/set-up-ci/overview). +* We now have two new best practice guides: [How we build our metrics](/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) and [Set up Continuous Integration](/guides/orchestration/set-up-ci/overview). diff --git a/website/docs/docs/deploy/deploy-environments.md b/website/docs/docs/deploy/deploy-environments.md index bdcf36b7a30..00d5c071444 100644 --- a/website/docs/docs/deploy/deploy-environments.md +++ b/website/docs/docs/deploy/deploy-environments.md @@ -13,7 +13,7 @@ Deployment environments in dbt Cloud are crucial for deploying dbt jobs in produ A dbt Cloud project can have multiple deployment environments, providing you the flexibility and customization to tailor the execution of dbt jobs. You can use deployment environments to [create and schedule jobs](/docs/deploy/deploy-jobs#create-and-schedule-jobs), [enable continuous integration](/docs/deploy/continuous-integration), or more based on your specific needs or requirements. :::tip Learn how to manage dbt Cloud environments -To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](https://docs.getdbt.com/guides/best-practices/environment-setup/1-env-guide-overview). +To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview). ::: This page reviews the different types of environments and how to configure your deployment environment in dbt Cloud. @@ -190,7 +190,7 @@ This section allows you to determine the credentials that should be used when co ## Related docs -- [dbt Cloud environment best practices](https://docs.getdbt.com/guides/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview) - [Deploy jobs](/docs/deploy/deploy-jobs) - [CI jobs](/docs/deploy/continuous-integration) - [Delete a job or environment in dbt Cloud](/faqs/Environments/delete-environment-job) diff --git a/website/docs/docs/environments-in-dbt.md b/website/docs/docs/environments-in-dbt.md index 70bc096cf4f..0361a272c4f 100644 --- a/website/docs/docs/environments-in-dbt.md +++ b/website/docs/docs/environments-in-dbt.md @@ -33,7 +33,7 @@ Configure environments to tell dbt Cloud or dbt Core how to build and execute yo ## Related docs -- [dbt Cloud environment best practices](https://docs.getdbt.com/guides/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview) - [Deployment environments](/docs/deploy/deploy-environments) - [About dbt Core versions](/docs/dbt-versions/core) - [Set Environment variables in dbt Cloud](/docs/build/environment-variables#special-environment-variables) diff --git a/website/docs/docs/introduction.md b/website/docs/docs/introduction.md index 5eeec43c0d5..61cda6e1d3e 100644 --- a/website/docs/docs/introduction.md +++ b/website/docs/docs/introduction.md @@ -63,6 +63,6 @@ As a dbt user, your main focus will be on writing models (i.e. select queries) t ### Related docs - [Quickstarts for dbt](/guides) -- [Best practice guides](/guides/best-practices) +- [Best practice guides](/best-practices) - [What is a dbt Project?](/docs/build/projects) - [dbt run](/docs/running-a-dbt-project/run-your-dbt-projects) diff --git a/website/docs/faqs/Jinja/jinja-whitespace.md b/website/docs/faqs/Jinja/jinja-whitespace.md index 49ced7183b7..5e1ec3dc7ac 100644 --- a/website/docs/faqs/Jinja/jinja-whitespace.md +++ b/website/docs/faqs/Jinja/jinja-whitespace.md @@ -7,6 +7,6 @@ id: jinja-whitespace This is known as "whitespace control". -Use a minus sign (`-`, e.g. `{{- ... -}}`, `{%- ... %}`, `{#- ... -#}`) at the start or end of a block to strip whitespace before or after the block (more docs [here](https://jinja.palletsprojects.com/page/templates/#whitespace-control)). Check out the [tutorial on using Jinja](/guides/advanced/using-jinja#use-whitespace-control-to-tidy-up-compiled-code) for an example. +Use a minus sign (`-`, e.g. `{{- ... -}}`, `{%- ... %}`, `{#- ... -#}`) at the start or end of a block to strip whitespace before or after the block (more docs [here](https://jinja.palletsprojects.com/page/templates/#whitespace-control)). Check out the [tutorial on using Jinja](/guides/using-jinja#use-whitespace-control-to-tidy-up-compiled-code) for an example. Take caution: it's easy to fall down a rabbit hole when it comes to whitespace control! diff --git a/website/docs/faqs/Models/available-materializations.md b/website/docs/faqs/Models/available-materializations.md index 011d3ba3fb0..fcb3e3a9d26 100644 --- a/website/docs/faqs/Models/available-materializations.md +++ b/website/docs/faqs/Models/available-materializations.md @@ -8,4 +8,4 @@ id: available-materializations dbt ships with five materializations: `view`, `table`, `incremental`, `ephemeral` and `materialized_view`. Check out the documentation on [materializations](/docs/build/materializations) for more information on each of these options. -You can also create your own [custom materializations](/guides/advanced/creating-new-materializations), if required however this is an advanced feature of dbt. +You can also create your own [custom materializations](/guides/creating-new-materializations), if required however this is an advanced feature of dbt. diff --git a/website/docs/faqs/Project/multiple-resource-yml-files.md b/website/docs/faqs/Project/multiple-resource-yml-files.md index 422b7beb702..a60c198de5d 100644 --- a/website/docs/faqs/Project/multiple-resource-yml-files.md +++ b/website/docs/faqs/Project/multiple-resource-yml-files.md @@ -9,4 +9,4 @@ It's up to you: - Some folks find it useful to have one file per model (or source / snapshot / seed etc) - Some find it useful to have one per directory, documenting and testing multiple models in one file -Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). diff --git a/website/docs/faqs/Project/resource-yml-name.md b/website/docs/faqs/Project/resource-yml-name.md index 8a6ebe96134..78d541cbd54 100644 --- a/website/docs/faqs/Project/resource-yml-name.md +++ b/website/docs/faqs/Project/resource-yml-name.md @@ -10,4 +10,4 @@ It's up to you! Here's a few options: - Use the same name as your directory (assuming you're using sensible names for your directories) - If you test and document one model (or seed, snapshot, macro etc.) per file, you can give it the same name as the model (or seed, snapshot, macro etc.) -Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). diff --git a/website/docs/faqs/Project/structure-a-project.md b/website/docs/faqs/Project/structure-a-project.md index 5d73f9f25ba..136c5b188bf 100644 --- a/website/docs/faqs/Project/structure-a-project.md +++ b/website/docs/faqs/Project/structure-a-project.md @@ -8,4 +8,4 @@ id: structure-a-project There's no one best way to structure a project! Every organization is unique. -If you're just getting started, check out how we (dbt Labs) [structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +If you're just getting started, check out how we (dbt Labs) [structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). diff --git a/website/docs/faqs/Project/why-not-write-dml.md b/website/docs/faqs/Project/why-not-write-dml.md index fd2cea7d3ad..349fc2c5c74 100644 --- a/website/docs/faqs/Project/why-not-write-dml.md +++ b/website/docs/faqs/Project/why-not-write-dml.md @@ -30,4 +30,4 @@ You can test your models, generate documentation, create snapshots, and more! SQL dialects tend to diverge the most in DML and DDL (rather than in `select` statements) — check out the example [here](/faqs/models/sql-dialect). By writing less SQL, it can make a migration to a new database technology easier. -If you do need to write custom DML, there are ways to do this in dbt using [custom materializations](/guides/advanced/creating-new-materializations). +If you do need to write custom DML, there are ways to do this in dbt using [custom materializations](/guides/creating-new-materializations). diff --git a/website/docs/faqs/Tests/custom-test-thresholds.md b/website/docs/faqs/Tests/custom-test-thresholds.md index 7155b39d25e..34d2eec7494 100644 --- a/website/docs/faqs/Tests/custom-test-thresholds.md +++ b/website/docs/faqs/Tests/custom-test-thresholds.md @@ -11,4 +11,4 @@ As of `v0.20.0`, you can use the `error_if` and `warn_if` configs to set custom For dbt `v0.19.0` and earlier, you could try these possible solutions: * Setting the [severity](/reference/resource-properties/tests#severity) to `warn`, or: -* Writing a [custom generic test](/guides/best-practices/writing-custom-generic-tests) that accepts a threshold argument ([example](https://discourse.getdbt.com/t/creating-an-error-threshold-for-schema-tests/966)) +* Writing a [custom generic test](/best-practices/writing-custom-generic-tests) that accepts a threshold argument ([example](https://discourse.getdbt.com/t/creating-an-error-threshold-for-schema-tests/966)) diff --git a/website/docs/faqs/Warehouse/db-connection-dbt-compile.md b/website/docs/faqs/Warehouse/db-connection-dbt-compile.md index d8e58155b10..be46f1a1d8c 100644 --- a/website/docs/faqs/Warehouse/db-connection-dbt-compile.md +++ b/website/docs/faqs/Warehouse/db-connection-dbt-compile.md @@ -22,7 +22,7 @@ To generate the compiled SQL for many models, dbt needs to run introspective que These introspective queries include: -- Populating the [relation cache](/guides/advanced/creating-new-materializations#update-the-relation-cache). Caching speeds up the metadata checks, including whether an [incremental model](/docs/build/incremental-models) already exists in the data platform. +- Populating the [relation cache](/guides/creating-new-materializations#update-the-relation-cache). Caching speeds up the metadata checks, including whether an [incremental model](/docs/build/incremental-models) already exists in the data platform. - Resolving [macros](/docs/build/jinja-macros#macros), such as `run_query` or `dbt_utils.get_column_values` that you're using to template out your SQL. This is because dbt needs to run those queries during model SQL compilation. Without a data platform connection, dbt can't perform these introspective queries and won't be able to generate the compiled SQL needed for the next steps in the dbt workflow. You can [`parse`](/reference/commands/parse) a project and use the [`list`](/reference/commands/list) resources in the project, without an internet or data platform connection. Parsing a project is enough to produce a [manifest](/reference/artifacts/manifest-json), however, keep in mind that the written-out manifest won't include compiled SQL. diff --git a/website/docs/guides/airflow-and-dbt-cloud.md b/website/docs/guides/airflow-and-dbt-cloud.md index 92c97fb2fd3..329fe729038 100644 --- a/website/docs/guides/airflow-and-dbt-cloud.md +++ b/website/docs/guides/airflow-and-dbt-cloud.md @@ -1,7 +1,7 @@ --- title: Airflow and dbt Cloud id: airflow-and-dbt-cloud -time_to_complete: '30 minutes' +time_to_complete: '60 minutes' platform: 'dbt-cloud' icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/legacy/building-packages.md b/website/docs/guides/building-packages.md similarity index 88% rename from website/docs/guides/legacy/building-packages.md rename to website/docs/guides/building-packages.md index 2a6803334d4..167eed47137 100644 --- a/website/docs/guides/legacy/building-packages.md +++ b/website/docs/guides/building-packages.md @@ -1,26 +1,39 @@ --- -title: "Building a dbt package" # to do: update this to creating +title: "Building dbt packages" # to do: update this to creating id: "building-packages" +description: When you have dbt code that might help others, you can create a package for dbt using a GitHub repository. +displayText: Building dbt packages +hoverSnippet: Learn how to create packages for dbt. +time_to_complete: '60 minutes' +platform: 'dbt-core' +icon: 'guides' +hide_table_of_contents: true +tags: ['packages', 'dbt Core', 'legacy'] +level: 'Advanced' +recently_updated: true --- -## Assumed knowledge -This article assumes you are familiar with: +## Introduction + +Creating packages is an **advanced use of dbt**. If you're new to the tool, we recommend that you first use the product for your own analytics before attempting to create a package for others. + +### Prerequisites + +A strong understanding of: - [packages](/docs/build/packages) - administering a repository on GitHub - [semantic versioning](https://semver.org/) -Heads up — developing a package is an **advanced use of dbt**. If you're new to the tool, we recommend that you first use the product for your own company's analytics before attempting to create a package. - -## 1. Assess whether a package is the right solution +### Assess whether a package is the right solution Packages typically contain either: - macros that solve a particular analytics engineering problem — for example, [auditing the results of a query](https://hub.getdbt.com/dbt-labs/audit_helper/latest/), [generating code](https://hub.getdbt.com/dbt-labs/codegen/latest/), or [adding additional schema tests to a dbt project](https://hub.getdbt.com/calogica/dbt_expectations/latest/). - models for a common dataset — for example a dataset for software products like [MailChimp](https://hub.getdbt.com/fivetran/mailchimp/latest/) or [Snowplow](https://hub.getdbt.com/dbt-labs/snowplow/latest/), or even models for metadata about your data stack like [Snowflake query spend](https://hub.getdbt.com/gitlabhq/snowflake_spend/latest/) and [the artifacts produced by `dbt run`](https://hub.getdbt.com/tailsdotcom/dbt_artifacts/latest/). In general, there should be a shared set of industry-standard metrics that you can model (e.g. email open rate). Packages are _not_ a good fit for sharing models that contain business-specific logic, for example, writing code for marketing attribution, or monthly recurring revenue. Instead, consider sharing a blog post and a link to a sample repo, rather than bundling this code as a package (here's our blog post on [marketing attribution](https://blog.getdbt.com/modeling-marketing-attribution/) as an example). -## 2. Create your new project -:::note Using the CLI for package development -We tend to use the CLI for package development. The development workflow often involves installing a local copy of your package in another dbt project — at present dbt Cloud is not designed for this workflow. +## Create your new project +:::note Using the command line for package development +We tend to use the command line interface for package development. The development workflow often involves installing a local copy of your package in another dbt project — at present dbt Cloud is not designed for this workflow. ::: 1. Use the [dbt init](/reference/commands/init) command to create a new dbt project, which will be your package: @@ -33,15 +46,15 @@ $ dbt init [package_name] ¹Currently, our package registry only supports packages that are hosted in GitHub. -## 3. Develop your package +## Develop your package We recommend that first-time package authors first develop macros and models for use in their own dbt project. Once your new package is created, you can get to work on moving them across, implementing some additional package-specific design patterns along the way. When working on your package, we often find it useful to install a local copy of the package in another dbt project — this workflow is described [here](https://discourse.getdbt.com/t/contributing-to-an-external-dbt-package/657). -### Follow our best practices +### Follow best practices _Modeling packages only_ -Use our [dbt coding conventions](https://github.com/dbt-labs/corp/blob/main/dbt_style_guide.md), our article on [how we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview), and our [best practices](best-practices) for all of our advice on how to build your dbt project. +Use our [dbt coding conventions](https://github.com/dbt-labs/corp/blob/main/dbt_style_guide.md), our article on [how we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview), and our [best practices](best-practices) for all of our advice on how to build your dbt project. This is where it comes in especially handy to have worked on your own dbt project previously. @@ -103,7 +116,7 @@ Over time, we've developed a set of useful GitHub artifacts that make administer - Descriptions of the main models included in the package ([example](https://github.com/dbt-labs/snowplow)) - GitHub templates, including PR templates and issue templates ([example](https://github.com/dbt-labs/dbt-audit-helper/tree/master/.github)) -## 4. Add integration tests +## Add integration tests _Optional_ We recommend that you implement integration tests to confirm that the package works as expected — this is an even _more_ advanced step, so you may find that you build up to this. @@ -125,7 +138,7 @@ packages: -4. Add resources to the package (seeds, models, tests) so that you can successfully run your project, and compare the output with what you expect. The exact appraoch here will vary depending on your packages. In general you will find that you need to: +4. Add resources to the package (seeds, models, tests) so that you can successfully run your project, and compare the output with what you expect. The exact approach here will vary depending on your packages. In general you will find that you need to: - Add mock data via a [seed](/docs/build/seeds) with a few sample (anonymized) records. Configure the `integration_tests` project to point to the seeds instead of raw data tables. - Add more seeds that represent the expected output of your models, and use the [dbt_utils.equality](https://github.com/dbt-labs/dbt-utils#equality-source) test to confirm the output of your package, and the expected output matches. @@ -134,7 +147,7 @@ packages: 5. (Optional) Use a CI tool, like CircleCI or GitHub Actions, to automate running your dbt project when you open a new Pull Request. For inspiration, check out one of our [CircleCI configs](https://github.com/dbt-labs/snowplow/blob/main/.circleci/config.yml), which runs tests against our four main warehouses. Note: this is an advanced step — if you are going down this path, you may find it useful to say hi on [dbt Slack](https://community.getdbt.com/). -## 5. Deploy the docs for your package +## Deploy the docs for your package _Optional_ A dbt docs site can help a prospective user of your package understand the code you've written. As such, we recommend that you deploy the site generated by `dbt docs generate` and link to the deployed site from your package. @@ -147,12 +160,13 @@ The easiest way we've found to do this is to use [GitHub Pages](https://pages.gi 4. Enable GitHub pages on the repo in the settings tab, and point it to the “docs” subdirectory 4. GitHub should then deploy the docs at `.github.io/`, like so: [fivetran.github.io/dbt_ad_reporting](https://fivetran.github.io/dbt_ad_reporting/) -## 6. Release your package +## Release your package Create a new [release](https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository) once you are ready for others to use your work! Be sure to use [semantic versioning](https://semver.org/) when naming your release. In particular, if new changes will cause errors for users of earlier versions of the package, be sure to use _at least_ a minor release (e.g. go from `0.1.1` to `0.2.0`). The release notes should contain an overview of the changes introduced in the new version. Be sure to call out any changes that break the existing interface! -## 7. Add the package to hub.getdbt.com +## Add the package to hub.getdbt.com + Our package registry, [hub.getdbt.com](https://hub.getdbt.com/), gets updated by the [hubcap script](https://github.com/dbt-labs/hubcap). To add your package to hub.getdbt.com, create a PR on the [hubcap repository](https://github.com/dbt-labs/hubcap) to include it in the `hub.json` file. diff --git a/website/docs/guides/creating-new-materializations.md b/website/docs/guides/creating-new-materializations.md index 42466c843cc..963141dc335 100644 --- a/website/docs/guides/creating-new-materializations.md +++ b/website/docs/guides/creating-new-materializations.md @@ -13,7 +13,7 @@ level: 'Advanced' recently_updated: true --- -## Overview +## Introduction The model materializations you're familiar with, `table`, `view`, and `incremental` are implemented as macros in a package that's distributed along with dbt. You can check out the [source code for these materializations](https://github.com/dbt-labs/dbt-core/tree/main/core/dbt/include/global_project/macros/materializations). If you need to create your own materializations, reading these files is a good place to start. Continue reading below for a deep-dive into dbt materializations. diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md b/website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md index 8713938db86..b60e1b912c7 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md @@ -53,9 +53,9 @@ Ready to start transforming your Unity Catalog datasets with dbt? Check out the resources below for guides, tips, and best practices: -- [How we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) +- [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) - [Self-paced dbt fundamentals training videos](https://courses.getdbt.com/courses/fundamentals) - [Customizing CI/CD](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/1-cicd-background) & [SQL linting](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/2-lint-on-push) -- [Debugging errors](https://docs.getdbt.com/guides/best-practices/debugging-errors) -- [Writing custom generic tests](https://docs.getdbt.com/guides/best-practices/writing-custom-generic-tests) -- [dbt packages hub](https://hub.getdbt.com/) \ No newline at end of file +- [Debugging errors](https://docs.getdbt.com/best-practices/debugging-errors) +- [Writing custom generic tests](https://docs.getdbt.com/best-practices/writing-custom-generic-tests) +- [dbt packages hub](https://hub.getdbt.com/) diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md index ba66bba60d1..ad12bdca725 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md @@ -7,7 +7,7 @@ In this guide, we discuss how to set up your dbt project on the Databricks Lakeh ## Configuring the Databricks Environments -To get started, we will use Databricks’s Unity Catalog. Without it, we would not be able to design separate [environments](https://docs.getdbt.com/docs/collaborate/environments) for development and production per our [best practices](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). It also allows us to ensure the proper access controls have been applied using SQL. You will need to be using the dbt-databricks adapter to use it (as opposed to the dbt-spark adapter). +To get started, we will use Databricks’s Unity Catalog. Without it, we would not be able to design separate [environments](https://docs.getdbt.com/docs/collaborate/environments) for development and production per our [best practices](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). It also allows us to ensure the proper access controls have been applied using SQL. You will need to be using the dbt-databricks adapter to use it (as opposed to the dbt-spark adapter). We will set up two different *catalogs* in Unity Catalog: **dev** and **prod**. A catalog is a top-level container for *schemas* (previously known as databases in Databricks), which in turn contain tables and views. diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md b/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md index b5389645258..cb7390fa799 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md @@ -35,7 +35,7 @@ Because of the ability of serverless warehouses to spin up in a matter of second Now that we have a solid sense of the infrastructure components, we can shift our focus to best practices and design patterns on pipeline development.  We recommend the staging/intermediate/mart approach which is analogous to the medallion architecture bronze/silver/gold approach that’s recommended by Databricks. Let’s dissect each stage further. -dbt has guidelines on how you can [structure your dbt project](/guides/best-practices/how-we-structure/1-guide-overview) which you can learn more about. +dbt has guidelines on how you can [structure your dbt project](/best-practices/how-we-structure/1-guide-overview) which you can learn more about. ### Bronze / Staging Layer: @@ -49,7 +49,7 @@ The main benefit of leveraging `COPY INTO` is that it's an incremental operation Now that we have our bronze table taken care of, we can proceed with the silver layer. -For cost and performance reasons, many customers opt to implement an incremental pipeline approach. The main benefit with this approach is that you process a lot less data when you insert new records into the silver layer, rather than re-create the table each time with all the data from the bronze layer. However it should be noted that by default, [dbt recommends using views and tables](/guides/best-practices/materializations/1-guide-overview) to start out with and then moving to incremental as you require more performance optimization. +For cost and performance reasons, many customers opt to implement an incremental pipeline approach. The main benefit with this approach is that you process a lot less data when you insert new records into the silver layer, rather than re-create the table each time with all the data from the bronze layer. However it should be noted that by default, [dbt recommends using views and tables](/best-practices/materializations/1-guide-overview) to start out with and then moving to incremental as you require more performance optimization. dbt has an [incremental model materialization](/reference/resource-configs/spark-configs#the-merge-strategy) to facilitate this framework. How this works at a high level is that Databricks will create a temp view with a snapshot of data and then merge that snapshot into the silver table. You can customize the time range of the snapshot to suit your specific use case by configuring the `where` conditional in your `is_incremental` logic. The most straightforward implementation is to merge data using a timestamp that’s later than the current max timestamp in the silver table, but there are certainly valid use cases for increasing the temporal range of the source snapshot. diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md b/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md index 35c5d852d74..9100ca8c3ce 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md +++ b/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md @@ -122,7 +122,7 @@ The five key steps for troubleshooting dbt Cloud issues are: 3. Isolate the problem by running one model at a time in the IDE or undoing the code that caused the issue. 4. Check for problems in compiled files and logs. -Consult the [Debugging errors documentation](/guides/best-practices/debugging-errors) for a comprehensive list of error types and diagnostic methods. +Consult the [Debugging errors documentation](/best-practices/debugging-errors) for a comprehensive list of error types and diagnostic methods. To troubleshoot issues with a dbt Cloud job, navigate to the "Deploy > Run History" tab in your dbt Cloud project and select the failed run. Then, expand the run steps to view [console and debug logs](/docs/deploy/run-visibility#access-logs) to review the detailed log messages. To obtain additional information, open the Artifacts tab and download the compiled files associated with the run. diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md index a47a3b54d48..c4e3e94b03a 100644 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md +++ b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md @@ -3,7 +3,7 @@ title: "Folder structure" id: "7-folder-structure" description: "Folder structure" --- -dbt Labs has developed a [project structure guide](/guides/best-practices/how-we-structure/1-guide-overview/) that contains a number of recommendations for how to build the folder structure for your project. Do check out that guide if you want to learn more. Right now we are going to create some folders to organize our files: +dbt Labs has developed a [project structure guide](/best-practices/how-we-structure/1-guide-overview/) that contains a number of recommendations for how to build the folder structure for your project. Do check out that guide if you want to learn more. Right now we are going to create some folders to organize our files: - Sources — This is our Formula 1 dataset and it will be defined in a source YAML file. - Staging models — These models have a 1:1 with their source table. @@ -24,4 +24,4 @@ dbt Labs has developed a [project structure guide](/guides/best-practices/how-we -Remember you can always reference the entire project in [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1) to view the complete folder and file strucutre. \ No newline at end of file +Remember you can always reference the entire project in [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1) to view the complete folder and file strucutre. diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md index 22e49c8a30b..c56284f5168 100644 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md +++ b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md @@ -10,7 +10,7 @@ Sources allow us to create a dependency between our source database object and o Staging models are the base of our project, where we bring all the individual components we're going to use to build our more complex and useful models into the project. -Since we want to focus on dbt and Python in this workshop, check out our [sources](/docs/build/sources) and [staging](/guides/best-practices/how-we-structure/2-staging) docs if you want to learn more (or take our [dbt Fundamentals](https://courses.getdbt.com/collections) course which covers all of our core functionality). +Since we want to focus on dbt and Python in this workshop, check out our [sources](/docs/build/sources) and [staging](/best-practices/how-we-structure/2-staging) docs if you want to learn more (or take our [dbt Fundamentals](https://courses.getdbt.com/collections) course which covers all of our core functionality). ## Create sources diff --git a/website/docs/guides/legacy/debugging-schema-names.md b/website/docs/guides/debugging-schema-names.md similarity index 84% rename from website/docs/guides/legacy/debugging-schema-names.md rename to website/docs/guides/debugging-schema-names.md index dee2bc57293..1778d25d0b4 100644 --- a/website/docs/guides/legacy/debugging-schema-names.md +++ b/website/docs/guides/debugging-schema-names.md @@ -1,7 +1,20 @@ --- title: Debugging schema names +id: debugging-schema-names +description: Learn how to debug schema names when models build under unexpected schemas +displayText: Debugging schema names +hoverSnippet: Learn how to debug schema names dbt. +time_to_complete: '45 minutes' +platform: 'dbt-core' +icon: 'guides' +hide_table_of_contents: true +tags: ['schema names', 'dbt Core', 'legacy'] +level: 'Advanced' +recently_updated: true --- +## Introduction + If a model uses the [`schema` config](/reference/resource-properties/schema) but builds under an unexpected schema, here are some steps for debugging the issue. :::info @@ -12,10 +25,10 @@ You can also follow along via this video: -### 1. Search for a macro named `generate_schema_name` +## Search for a macro named `generate_schema_name` Do a file search to check if you have a macro named `generate_schema_name` in the `macros` directory of your project. -#### I do not have a macro named `generate_schema_name` in my project +### I do not have a macro named `generate_schema_name` in my project This means that you are using dbt's default implementation of the macro, as defined [here](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/include/global_project/macros/get_custom_name/get_custom_schema.sql#L47C1-L60) ```sql @@ -37,7 +50,7 @@ This means that you are using dbt's default implementation of the macro, as defi Note that this logic is designed so that two dbt users won't accidentally overwrite each other's work by writing to the same schema. -#### I have a `generate_schema_name` macro in my project that calls another macro +### I have a `generate_schema_name` macro in my project that calls another macro If your `generate_schema_name` macro looks like so: ```sql {% macro generate_schema_name(custom_schema_name, node) -%} @@ -61,22 +74,22 @@ Your project is switching out the `generate_schema_name` macro for another macro {%- endmacro %} ``` -#### I have a `generate_schema_name` macro with custom logic +### I have a `generate_schema_name` macro with custom logic If this is the case — it might be a great idea to reach out to the person who added this macro to your project, as they will have context here — you can use [GitHub's blame feature](https://docs.github.com/en/free-pro-team@latest/github/managing-files-in-a-repository/tracking-changes-in-a-file) to do this. In all cases take a moment to read through the Jinja to see if you can follow the logic. -### 2. Confirm your `schema` config +## Confirm your `schema` config Check if you are using the [`schema` config](/reference/resource-properties/schema) in your model, either via a `{{ config() }}` block, or from `dbt_project.yml`. In both cases, dbt passes this value as the `custom_schema_name` parameter of the `generate_schema_name` macro. -### 3. Confirm your target values +## Confirm your target values Most `generate_schema_name` macros incorporate logic from the [`target` variable](/reference/dbt-jinja-functions/target), in particular `target.schema` and `target.name`. Use the docs [here](/reference/dbt-jinja-functions/target) to help you find the values of each key in this dictionary. -### 4. Put the two together +## Put the two together Now, re-read through the logic of your `generate_schema_name` macro, and mentally plug in your `customer_schema_name` and `target` values. @@ -86,7 +99,7 @@ You should find that the schema dbt is constructing for your model matches the o Note that snapshots do not follow this behavior, check out the docs on [target_schema](/reference/resource-configs/target_schema) instead. ::: -### 5. Adjust as necessary +## Adjust as necessary Now that you understand how a model's schema is being generated, you can adjust as necessary: - You can adjust the logic in your `generate_schema_name` macro (or add this macro to your project if you don't yet have one and adjust from there) diff --git a/website/docs/guides/legacy/creating-date-partitioned-tables.md b/website/docs/guides/legacy/creating-date-partitioned-tables.md deleted file mode 100644 index 8c461dbe4a8..00000000000 --- a/website/docs/guides/legacy/creating-date-partitioned-tables.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: "BigQuery: Creating date-partitioned tables" -id: "creating-date-partitioned-tables" ---- - - -:::caution Deprecated - -The functionality described below was introduced in dbt Core v0.10 (March 2018). In v1.0 (December 2021), it was deprecated in favor of [column-based partitioning](/reference/resource-configs/bigquery-configs#partition-clause) and [incremental modeling](/docs/build/incremental-models). - -::: - -dbt supports the creation of [date partitioned tables](https://cloud.google.com/bigquery/docs/partitioned-tables) in BigQuery. - -To configure a dbt model as a date partitioned , use the `materialized='table'` model configuration in conjunction with a list of `partitions`. dbt will execute your model query once for each specified partition. For example: - - - -```sql -{{ - config( - materialized='table', - partitions=[20180101, 20180102], - verbose=True - ) -}} - -/* - -dbt will interpolate each `partition` wherever it finds [DBT__PARTITION_DATE] -in your model code. This model will create a single table with two partitions: - 1. 20180101 - 2. 20180102 - -These partitions will be created by running the following query against -each of the following date-sharded tables: - - 1. `snowplow`.`events_20180101` - 2. `snowplow`.`events_20180102` - -*/ - -select * -from `snowplow`.`events_[DBT__PARTITION_DATE]` -``` - - - -To make this model more dynamic, we can use the `dbt.partition_range` macro to generate a list of 8-digit dates in a specified range. Further, dbt provides a handy macro, `date_sharded_table`, for getting a date-sharded by its prefix for a given date. Together, this looks like: - - - -```sql -{{ - config( - materialized='table', - partitions=dbt.partition_range('20180101, 20180201'), - verbose=True - ) -}} - --- This model creates a date-partitioned table. There will be one --- partition for each day between 20180101 and 20180201, inclusive. --- The `date_sharded_table` macro below is sugar around [DBT__PARTITION_DATE] - -select * -from `snowplow`.`{{ date_sharded_table('events_') }}` -``` - - - -Finally, it's frequently desirable to only update a date partitioned table for the last day of received data. This can be implemented using the above configurations in conjunction with a clever macro and some [command line variables](/docs/build/project-variables). - -First, the macro: - - - -```sql -{% macro yesterday() %} - - {% set today = modules.datetime.date.today() %} - {% set one_day = modules.datetime.timedelta(days=1) %} - {% set yesterday = (today - one_day) %} - - {{ return(yesterday.strftime("%Y%m%d")) }} - -{% endmacro %} -``` - - - -Next, use it in the model: - - - -```sql -{{ - config( - materialized='table', - partitions=dbt.partition_range(var('dates', default=yesterday())), - verbose=True - ) -}} - -select * -from `snowplow`.`{{ date_sharded_table('events_') }}` -``` - - - -If a `dates` variable is provided (eg. on the command line with `--vars`), then dbt will create the partitions for that date range. Otherwise, dbt will create a partition for `yesterday`, overwriting it if it already exists. - -Here's an example of running this model for the first 3 days of 2018 as a part of a backfill: - -``` -dbt run --select partitioned_yesterday --vars 'dates: "20180101, 20180103"' -``` diff --git a/website/docs/guides/legacy/videos.md b/website/docs/guides/legacy/videos.md deleted file mode 100644 index 863029ff6d9..00000000000 --- a/website/docs/guides/legacy/videos.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Videos 🎥" -id: "videos" ---- - -Check out some cool videos about using and deploying dbt! - -## dbt tutorial (February, 2017) - - - -## dbt docs demo with GitLab (September, 2018) - diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md index aae8b373b2c..c16df789939 100644 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md +++ b/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md @@ -13,7 +13,7 @@ If your supports `SHOW CREATE TABLE`, that can be a As for ensuring that you have the right column types, since models materialized by dbt generally use `CREATE TABLE AS SELECT` or `CREATE VIEW AS SELECT` as the driver for object creation, tables can end up with unintended column types if the queries aren’t explicit. For example, if you care about `INT` versus `DECIMAL` versus `NUMERIC`, it’s generally going to be best to be explicit. The good news is that this is easy with dbt: you just cast the column to the type you intend. -We also generally recommend that column renaming and type casting happen as close to the source tables as possible, typically in a layer of staging transformations, which helps ensure that future dbt modelers will know where to look for those transformations! See [How we structure our dbt projects](/guides/best-practices/how-we-structure/1-guide-overview) for more guidance on overall project structure. +We also generally recommend that column renaming and type casting happen as close to the source tables as possible, typically in a layer of staging transformations, which helps ensure that future dbt modelers will know where to look for those transformations! See [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) for more guidance on overall project structure. ### Operations we need to map diff --git a/website/docs/guides/migration/tools/refactoring-legacy-sql.md b/website/docs/guides/migration/tools/refactoring-legacy-sql.md index 7026e4e14aa..0c03942889c 100644 --- a/website/docs/guides/migration/tools/refactoring-legacy-sql.md +++ b/website/docs/guides/migration/tools/refactoring-legacy-sql.md @@ -206,7 +206,7 @@ This allows anyone after us to easily step through the CTEs when troubleshooting ## Port CTEs to individual data models Rather than keep our SQL code confined to one long SQL file, we'll now start splitting it into modular + reusable [dbt data models](https://docs.getdbt.com/docs/build/models). -Internally at dbt Labs, we follow roughly this [data modeling technique](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) and we [structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) accordingly. +Internally at dbt Labs, we follow roughly this [data modeling technique](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) and we [structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) accordingly. We'll follow those structures in this walkthrough, but your team's conventions may of course differ from ours. diff --git a/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md b/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md index 0ef9b8029d6..669262dcafb 100644 --- a/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md +++ b/website/docs/guides/migration/versions/08-upgrading-to-v1.0.md @@ -64,9 +64,9 @@ Several under-the-hood changes from past minor versions, tagged with deprecation ## New features and changed documentation - Add [metrics](/docs/build/metrics), a new node type -- [Generic tests](/guides/best-practices/writing-custom-generic-tests) can be defined in `tests/generic` (new), in addition to `macros/` (as before) +- [Generic tests](/best-practices/writing-custom-generic-tests) can be defined in `tests/generic` (new), in addition to `macros/` (as before) - [Parsing](/reference/parsing): partial parsing and static parsing have been turned on by default. - [Global configs](/reference/global-configs/about-global-configs) have been standardized. Related updates to [global CLI flags](/reference/global-cli-flags) and [`profiles.yml`](/docs/core/connect-data-platform/profiles.yml). - [The `init` command](/reference/commands/init) has a whole new look and feel. It's no longer just for first-time users. -- Add `result:` subselectors for smarter reruns when dbt models have errors and tests fail. See examples: [Pro-tips for Workflows](/best-practices/best-practices#pro-tips-for-workflows) +- Add `result:` subselectors for smarter reruns when dbt models have errors and tests fail. See examples: [Pro-tips for Workflows](/best-practices/best-practice-workflows#pro-tips-for-workflows) - Secret-prefixed [env vars](/reference/dbt-jinja-functions/env_var) are now allowed only in `profiles.yml` + `packages.yml` diff --git a/website/docs/guides/migration/versions/10-upgrading-to-v0.20.md b/website/docs/guides/migration/versions/10-upgrading-to-v0.20.md index 8b33bfa3879..8bd20faaadf 100644 --- a/website/docs/guides/migration/versions/10-upgrading-to-v0.20.md +++ b/website/docs/guides/migration/versions/10-upgrading-to-v0.20.md @@ -28,7 +28,7 @@ dbt Core v0.20 has reached the end of critical support. No new patch versions wi - [Test Configs](/reference/test-configs) - [Test properties](/reference/resource-properties/tests) - [Node Selection](/reference/node-selection/syntax) (with updated [test selection examples](/reference/node-selection/test-selection-examples)) -- [Writing custom generic tests](/guides/best-practices/writing-custom-generic-tests) +- [Writing custom generic tests](/best-practices/writing-custom-generic-tests) ### Elsewhere in Core - [Parsing](/reference/parsing): rework of partial parsing, introduction of experimental parser diff --git a/website/docs/guides/migration/versions/11-Older versions/upgrading-to-0-15-0.md b/website/docs/guides/migration/versions/11-Older versions/upgrading-to-0-15-0.md index 02ab297c07a..693c80ac73d 100644 --- a/website/docs/guides/migration/versions/11-Older versions/upgrading-to-0-15-0.md +++ b/website/docs/guides/migration/versions/11-Older versions/upgrading-to-0-15-0.md @@ -20,7 +20,7 @@ expect this field will now return errors. See the latest ### Custom materializations -All materializations must now [manage dbt's Relation cache](/guides/advanced/creating-new-materializations#update-the-relation-cache). +All materializations must now [manage dbt's Relation cache](/guides/creating-new-materializations#update-the-relation-cache). ### dbt Server diff --git a/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md b/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md index 9b6d46fe2b2..0dcfba7eb06 100644 --- a/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md +++ b/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md @@ -7,7 +7,7 @@ description: Find issues before they are deployed to production with dbt Cloud's In this guide, we're going to add a **CI environment**, where proposed changes can be validated in the context of the entire project without impacting production systems. We will use a single set of deployment credentials (like the Prod environment), but models are built in a separate location to avoid impacting others (like the Dev environment). Your git flow will look like this: - + ## Prerequisites diff --git a/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md b/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md index 1932ffe1019..bc080922ab5 100644 --- a/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md +++ b/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md @@ -6,7 +6,7 @@ description: Enforce your organization's SQL style guide with by running SQLFluf By [linting](/docs/cloud/dbt-cloud-ide/lint-format#lint) your project during CI, you can ensure that code styling standards are consistently enforced, without spending human time nitpicking comma placement. -The steps below create an action/pipeline which uses [SQLFluff](https://docs.sqlfluff.com/en/stable/) to scan your code and look for linting errors. If you don't already have SQLFluff rules defined, check out [our recommended config file](/guides/best-practices/how-we-style/2-how-we-style-our-sql). +The steps below create an action/pipeline which uses [SQLFluff](https://docs.sqlfluff.com/en/stable/) to scan your code and look for linting errors. If you don't already have SQLFluff rules defined, check out [our recommended config file](/best-practices/how-we-style/2-how-we-style-our-sql). ### 1. Create a YAML file to define your pipeline diff --git a/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md b/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md index 4bfe2d936d4..2c48e453c2c 100644 --- a/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md +++ b/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md @@ -15,7 +15,7 @@ The team at Sunrun maintained a SOX-compliant deployment in dbt while reducing t In this section, we will add a new **QA** environment. New features will branch off from and be merged back into the associated `qa` branch, and a member of your team (the "Release Manager") will create a PR against `main` to be validated in the CI environment before going live. The git flow will look like this: - + ## Prerequisites diff --git a/website/docs/reference/dbt-jinja-functions/run_query.md b/website/docs/reference/dbt-jinja-functions/run_query.md index cdd65a7b4dc..87970e024ed 100644 --- a/website/docs/reference/dbt-jinja-functions/run_query.md +++ b/website/docs/reference/dbt-jinja-functions/run_query.md @@ -15,7 +15,7 @@ Returns a [Table](https://agate.readthedocs.io/page/api/table.html) object with **Note:** The `run_query` macro will not begin a transaction automatically - if you wish to run your query inside of a transaction, please use `begin` and `commit ` statements as appropriate. :::info Using run_query for the first time? -Check out the section of the Getting Started guide on [using Jinja](/guides/advanced/using-jinja#dynamically-retrieve-the-list-of-payment-methods) for an example of working with the results of the `run_query` macro! +Check out the section of the Getting Started guide on [using Jinja](/guides/using-jinja#dynamically-retrieve-the-list-of-payment-methods) for an example of working with the results of the `run_query` macro! ::: **Example Usage:** diff --git a/website/docs/reference/events-logging.md b/website/docs/reference/events-logging.md index dec1dafcb8e..94b865fad0d 100644 --- a/website/docs/reference/events-logging.md +++ b/website/docs/reference/events-logging.md @@ -4,7 +4,7 @@ title: "Events and logs" As dbt runs, it generates events. The most common way to see those events is as log messages, written in real time to two places: - The command line terminal (`stdout`), to provide interactive feedback while running dbt. -- The debug log file (`logs/dbt.log`), to enable detailed [debugging of errors](/guides/best-practices/debugging-errors) when they occur. The text-formatted log messages in this file include all `DEBUG`-level events, as well as contextual information, such as log level and thread name. The location of this file can be configured via [the `log_path` config](/reference/project-configs/log-path). +- The debug log file (`logs/dbt.log`), to enable detailed [debugging of errors](/best-practices/debugging-errors) when they occur. The text-formatted log messages in this file include all `DEBUG`-level events, as well as contextual information, such as log level and thread name. The location of this file can be configured via [the `log_path` config](/reference/project-configs/log-path). diff --git a/website/docs/reference/node-selection/syntax.md b/website/docs/reference/node-selection/syntax.md index 34085d339e6..d0ea4a9acd8 100644 --- a/website/docs/reference/node-selection/syntax.md +++ b/website/docs/reference/node-selection/syntax.md @@ -96,7 +96,7 @@ by comparing code in the current project against the state manifest. - [Deferring](/reference/node-selection/defer) to another environment, whereby dbt can identify upstream, unselected resources that don't exist in your current environment and instead "defer" their references to the environment provided by the state manifest. - The [`dbt clone` command](/reference/commands/clone), whereby dbt can clone nodes based on their location in the manifest provided to the `--state` flag. -Together, the `state:` selector and deferral enable ["slim CI"](/best-practices/best-practices#run-only-modified-models-to-test-changes-slim-ci). We expect to add more features in future releases that can leverage artifacts passed to the `--state` flag. +Together, the `state:` selector and deferral enable ["slim CI"](/best-practices/best-practice-workflows#run-only-modified-models-to-test-changes-slim-ci). We expect to add more features in future releases that can leverage artifacts passed to the `--state` flag. ### Establishing state @@ -190,7 +190,7 @@ dbt build --select "source_status:fresher+" ``` -For more example commands, refer to [Pro-tips for workflows](/best-practices/best-practices.md#pro-tips-for-workflows). +For more example commands, refer to [Pro-tips for workflows](/best-practices/best-practice-workflows#pro-tips-for-workflows). ### The "source_status" status diff --git a/website/docs/reference/resource-configs/contract.md b/website/docs/reference/resource-configs/contract.md index 59cc511890b..ccc10099a12 100644 --- a/website/docs/reference/resource-configs/contract.md +++ b/website/docs/reference/resource-configs/contract.md @@ -48,7 +48,7 @@ models: -When dbt compares data types, it will not compare granular details such as size, precision, or scale. We don't think you should sweat the difference between `varchar(256)` and `varchar(257)`, because it doesn't really affect the experience of downstream queriers. You can accomplish a more-precise assertion by [writing or using a custom test](/guides/best-practices/writing-custom-generic-tests). +When dbt compares data types, it will not compare granular details such as size, precision, or scale. We don't think you should sweat the difference between `varchar(256)` and `varchar(257)`, because it doesn't really affect the experience of downstream queriers. You can accomplish a more-precise assertion by [writing or using a custom test](/best-practices/writing-custom-generic-tests). Note that you need to specify a varchar size or numeric scale, otherwise dbt relies on default values. For example, if a `numeric` type defaults to a precision of 38 and a scale of 0, then the numeric column stores 0 digits to the right of the decimal (it only stores whole numbers), which might cause it to fail contract enforcement. To avoid this implicit coercion, specify your `data_type` with a nonzero scale, like `numeric(38, 6)`. dbt Core 1.7 and higher provides a warning if you don't specify precision and scale when providing a numeric data type. diff --git a/website/docs/reference/resource-properties/tests.md b/website/docs/reference/resource-properties/tests.md index 6e2c02c6bc5..0fe86ccc57d 100644 --- a/website/docs/reference/resource-properties/tests.md +++ b/website/docs/reference/resource-properties/tests.md @@ -298,7 +298,7 @@ models: -Check out the guide on writing a [custom generic test](/guides/best-practices/writing-custom-generic-tests) for more information. +Check out the guide on writing a [custom generic test](/best-practices/writing-custom-generic-tests) for more information. ### Custom test name diff --git a/website/docs/sql-reference/aggregate-functions/sql-array-agg.md b/website/docs/sql-reference/aggregate-functions/sql-array-agg.md index 430be4b4316..9f4af7ca1fc 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-array-agg.md +++ b/website/docs/sql-reference/aggregate-functions/sql-array-agg.md @@ -59,4 +59,4 @@ Looking at the query results—this makes sense! We’d expect newer orders to l There are definitely too many use cases to list out for using the ARRAY_AGG function in your dbt models, but it’s very likely that ARRAY_AGG is used pretty downstream in your since you likely don’t want your data so bundled up earlier in your DAG to improve modularity and dryness. A few downstream use cases for ARRAY_AGG: - In [`export_` models](https://www.getdbt.com/open-source-data-culture/reverse-etl-playbook) that are used to send data to platforms using a tool to pair down multiple rows into a single row. Some downstream platforms, for example, require certain values that we’d usually keep as separate rows to be one singular row per customer or user. ARRAY_AGG is handy to bring multiple column values together by a singular id, such as creating an array of all items a user has ever purchased and sending that array downstream to an email platform to create a custom email campaign. -- Similar to export models, you may see ARRAY_AGG used in [mart tables](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) to create final aggregate arrays per a singular dimension; performance concerns of ARRAY_AGG in these likely larger tables can potentially be bypassed with use of [incremental models in dbt](https://docs.getdbt.com/docs/build/incremental-models). +- Similar to export models, you may see ARRAY_AGG used in [mart tables](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to create final aggregate arrays per a singular dimension; performance concerns of ARRAY_AGG in these likely larger tables can potentially be bypassed with use of [incremental models in dbt](https://docs.getdbt.com/docs/build/incremental-models). diff --git a/website/docs/sql-reference/aggregate-functions/sql-avg.md b/website/docs/sql-reference/aggregate-functions/sql-avg.md index d7d2fccc3c4..afb766f12e2 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-avg.md +++ b/website/docs/sql-reference/aggregate-functions/sql-avg.md @@ -48,7 +48,7 @@ Snowflake, Databricks, Google BigQuery, and Amazon Redshift all support the abil ## AVG function use cases We most commonly see the AVG function used in data work to calculate: -- The average of key metrics (ex. Average CSAT, average lead time, average order amount) in downstream [fact or dim models](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) +- The average of key metrics (ex. Average CSAT, average lead time, average order amount) in downstream [fact or dim models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) - Rolling or moving averages (ex. 7-day, 30-day averages for key metrics) using window functions - Averages in [dbt metrics](https://docs.getdbt.com/docs/build/metrics) diff --git a/website/docs/sql-reference/aggregate-functions/sql-round.md b/website/docs/sql-reference/aggregate-functions/sql-round.md index 053a2ebdd8e..7652c881789 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-round.md +++ b/website/docs/sql-reference/aggregate-functions/sql-round.md @@ -57,7 +57,7 @@ Google BigQuery, Amazon Redshift, Snowflake, and Databricks all support the abil ## ROUND function use cases -If you find yourself rounding numeric data, either in data models or ad-hoc analyses, you’re probably rounding to improve the readability and usability of your data using downstream [intermediate](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate) or [mart models](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts). Specifically, you’ll likely use the ROUND function to: +If you find yourself rounding numeric data, either in data models or ad-hoc analyses, you’re probably rounding to improve the readability and usability of your data using downstream [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) or [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts). Specifically, you’ll likely use the ROUND function to: - Make numeric calculations using division or averages a little cleaner and easier to understand - Create concrete buckets of data for a cleaner distribution of values during ad-hoc analysis diff --git a/website/docs/sql-reference/clauses/sql-limit.md b/website/docs/sql-reference/clauses/sql-limit.md index d17affdef45..a2c49866592 100644 --- a/website/docs/sql-reference/clauses/sql-limit.md +++ b/website/docs/sql-reference/clauses/sql-limit.md @@ -51,7 +51,7 @@ This simple query using the [Jaffle Shop’s](https://github.com/dbt-labs/jaffle After ensuring that this is the result you want from this query, you can omit the LIMIT in your final data model. :::tip Save money and time by limiting data in development -You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](https://docs.getdbt.com/best-practices/best-practices#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. +You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](https://docs.getdbt.com/best-practices/best-practice-workflows#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. ::: ## LIMIT syntax in Snowflake, Databricks, BigQuery, and Redshift diff --git a/website/docs/sql-reference/clauses/sql-order-by.md b/website/docs/sql-reference/clauses/sql-order-by.md index 660794adc14..39337de1e48 100644 --- a/website/docs/sql-reference/clauses/sql-order-by.md +++ b/website/docs/sql-reference/clauses/sql-order-by.md @@ -57,7 +57,7 @@ Since the ORDER BY clause is a SQL fundamental, data warehouses, including Snowf ## ORDER BY use cases We most commonly see the ORDER BY clause used in data work to: -- Analyze data for both initial exploration of raw data sources and ad hoc querying of [mart datasets](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) +- Analyze data for both initial exploration of raw data sources and ad hoc querying of [mart datasets](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) - Identify the top 5/10/50/100 of a dataset when used in pair with a [LIMIT](/sql-reference/limit) - (For Snowflake) Optimize the performance of large incremental models that use both a `cluster_by` [configuration](https://docs.getdbt.com/reference/resource-configs/snowflake-configs#using-cluster_by) and ORDER BY statement - Control the ordering of window function partitions (ex. `row_number() over (partition by user_id order by updated_at)`) diff --git a/website/docs/sql-reference/joins/sql-inner-join.md b/website/docs/sql-reference/joins/sql-inner-join.md index 0cf8a3894bd..e1c2d6151c8 100644 --- a/website/docs/sql-reference/joins/sql-inner-join.md +++ b/website/docs/sql-reference/joins/sql-inner-join.md @@ -66,5 +66,5 @@ Because there’s no `user_id` = 4 in Table A and no `user_id` = 2 in Table B, r ## SQL inner join use cases -There are probably countless scenarios where you’d want to inner join multiple tables together—perhaps you have some really nicely structured tables with the exact same primary keys that should really just be one larger, wider table or you’re joining two tables together don’t want any null or missing column values if you used a left or right join—it’s all pretty dependent on your source data and end use cases. Where you will not (and should not) see inner joins is in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) to improve modularity and DAG cleanliness. +There are probably countless scenarios where you’d want to inner join multiple tables together—perhaps you have some really nicely structured tables with the exact same primary keys that should really just be one larger, wider table or you’re joining two tables together don’t want any null or missing column values if you used a left or right join—it’s all pretty dependent on your source data and end use cases. Where you will not (and should not) see inner joins is in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to improve modularity and DAG cleanliness. diff --git a/website/docs/sql-reference/joins/sql-left-join.md b/website/docs/sql-reference/joins/sql-left-join.md index 841edc41cdd..24fbb2bfa0c 100644 --- a/website/docs/sql-reference/joins/sql-left-join.md +++ b/website/docs/sql-reference/joins/sql-left-join.md @@ -73,4 +73,4 @@ Left joins are a fundamental in data modeling and analytics engineering work—t Something to note if you use left joins: if there are multiple records for an individual key in the left join database object, be aware that duplicates can potentially be introduced in the final query result. This is where dbt tests, such as testing for uniqueness and [equal row count](https://github.com/dbt-labs/dbt-utils#equal_rowcount-source) across upstream source tables and downstream child models, can help you identify faulty data modeling logic and improve data quality. ::: -Where you will not (and should not) see left joins is in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) to improve modularity and cleanliness. \ No newline at end of file +Where you will not (and should not) see left joins is in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to improve modularity and cleanliness. diff --git a/website/docs/sql-reference/joins/sql-self-join.md b/website/docs/sql-reference/joins/sql-self-join.md index 0eef0fcab7c..bb4237319f0 100644 --- a/website/docs/sql-reference/joins/sql-self-join.md +++ b/website/docs/sql-reference/joins/sql-self-join.md @@ -66,6 +66,6 @@ This query utilizing a self join adds the `parent_name` of skus that have non-nu ## SQL self join use cases -Again, self joins are probably rare in your dbt project and will most often be utilized in tables that contain a hierarchical structure, such as consisting of a column which is a foreign key to the primary key of the same table. If you do have use cases for self joins, such as in the example above, you’ll typically want to perform that self join early upstream in your , such as in a [staging](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging) or [intermediate](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate) model; if your raw, unjoined table is going to need to be accessed further downstream sans self join, that self join should happen in a modular intermediate model. +Again, self joins are probably rare in your dbt project and will most often be utilized in tables that contain a hierarchical structure, such as consisting of a column which is a foreign key to the primary key of the same table. If you do have use cases for self joins, such as in the example above, you’ll typically want to perform that self join early upstream in your , such as in a [staging](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) or [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) model; if your raw, unjoined table is going to need to be accessed further downstream sans self join, that self join should happen in a modular intermediate model. -You can also use self joins to create a cartesian product (aka a cross join) of a table against itself. Again, slim use cases, but still there for you if you need it 😉 \ No newline at end of file +You can also use self joins to create a cartesian product (aka a cross join) of a table against itself. Again, slim use cases, but still there for you if you need it 😉 diff --git a/website/docs/sql-reference/operators/sql-not.md b/website/docs/sql-reference/operators/sql-not.md index e9156cb9720..fcfa7627c0b 100644 --- a/website/docs/sql-reference/operators/sql-not.md +++ b/website/docs/sql-reference/operators/sql-not.md @@ -55,4 +55,4 @@ This simple query using the sample dataset [Jaffle Shop’s](https://github.com/ ## NOT operator example use cases -There are probably many scenarios where you’d want to use the NOT operators in your WHERE clauses or case statements, but we commonly see NOT operators used to remove nulls or boolean-identifed deleted rows in source data in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging). This removal of unnecessary rows can potentially help the performance of downstream [intermediate](https://docs.getdbt.com/guides/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts). \ No newline at end of file +There are probably many scenarios where you’d want to use the NOT operators in your WHERE clauses or case statements, but we commonly see NOT operators used to remove nulls or boolean-identifed deleted rows in source data in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging). This removal of unnecessary rows can potentially help the performance of downstream [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts). diff --git a/website/docs/sql-reference/other/sql-cast.md b/website/docs/sql-reference/other/sql-cast.md index cf24a12706e..9d41400e825 100644 --- a/website/docs/sql-reference/other/sql-cast.md +++ b/website/docs/sql-reference/other/sql-cast.md @@ -50,7 +50,7 @@ After running this query, the `orders` table will look a little something like t Let’s be clear: the resulting data from this query looks exactly the same as the upstream `orders` model. However, the `order_id` and `customer_id` fields are now strings, meaning you could easily concat different string variables to them. -> Casting columns to their appropriate types typically happens in our dbt project’s [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging). A few reasons for that: data cleanup and standardization, such as aliasing, casting, and lower or upper casing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. +> Casting columns to their appropriate types typically happens in our dbt project’s [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging). A few reasons for that: data cleanup and standardization, such as aliasing, casting, and lower or upper casing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. ## SQL CAST function syntax in Snowflake, Databricks, BigQuery, and Redshift @@ -66,4 +66,4 @@ You know at one point you’re going to need to cast a column to a different dat - tools [defaulting to certain data types](https://airbytehq.github.io/integrations/sources/google-sheets/) - BI tools require certain fields to be specific data types -A key thing to remember when you’re casting data is the user experience in your end BI tool: are business users expecting `customer_id` to be filtered on 1 or '1'? What is more intuitive for them? If one `id` field is an integer, all `id` fields should be integers. Just like all data modeling, consistency and standardization is key when determining when and what to cast. \ No newline at end of file +A key thing to remember when you’re casting data is the user experience in your end BI tool: are business users expecting `customer_id` to be filtered on 1 or '1'? What is more intuitive for them? If one `id` field is an integer, all `id` fields should be integers. Just like all data modeling, consistency and standardization is key when determining when and what to cast. diff --git a/website/docs/sql-reference/other/sql-comments.md b/website/docs/sql-reference/other/sql-comments.md index 811f2b4339e..7fe5e970a85 100644 --- a/website/docs/sql-reference/other/sql-comments.md +++ b/website/docs/sql-reference/other/sql-comments.md @@ -53,7 +53,7 @@ We recommend leveraging inline comments in the following situations: - Explain complex code logic that if you had to scratch your head at, someone else will have to scratch their head at - Explain niche, unique-to-your-business logic -- Separate out field types (ex. Ids, booleans, strings, dates, numerics, and timestamps) in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging) to create more readable, organized, and formulaic models +- Separate out field types (ex. Ids, booleans, strings, dates, numerics, and timestamps) in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) to create more readable, organized, and formulaic models - Clearly label tech debt (`-- [TODO]: TECH DEBT`) in queries or models diff --git a/website/docs/sql-reference/statements/sql-select.md b/website/docs/sql-reference/statements/sql-select.md index 49132524096..0b914d9c1da 100644 --- a/website/docs/sql-reference/statements/sql-select.md +++ b/website/docs/sql-reference/statements/sql-select.md @@ -42,8 +42,8 @@ You may also commonly see queries that `select * from table_name`. The asterisk Leverage [dbt utils’ star macro](/blog/star-sql-love-letter) to be able to both easily select many and specifically exclude certain columns. ::: -In a dbt project, analytics engineers will typically write models that contain multiple CTEs that build to one greater query. For folks that are newer to analytics engineering or dbt, we recommend they check out the [“How we structure our dbt projects” guide](/guides/best-practices/how-we-structure/1-guide-overview) to better understand why analytics folks like modular data modeling and CTEs. +In a dbt project, analytics engineers will typically write models that contain multiple CTEs that build to one greater query. For folks that are newer to analytics engineering or dbt, we recommend they check out the [“How we structure our dbt projects” guide](/best-practices/how-we-structure/1-guide-overview) to better understand why analytics folks like modular data modeling and CTEs. ## SELECT statement syntax in Snowflake, Databricks, BigQuery, and Redshift -While we know the data warehouse players like to have their own slightly different flavors and syntax for SQL, they have conferred together that the SELECT statement is sacred and unchangeable. As a result, writing the actual `select…from` statement across Snowflake, Databricks, Google BigQuery, and Amazon Redshift would look the same. However, the actual SQL manipulation of data within the SELECT statement (ex. adding dates, casting columns) might look slightly different between each data warehouse. \ No newline at end of file +While we know the data warehouse players like to have their own slightly different flavors and syntax for SQL, they have conferred together that the SELECT statement is sacred and unchangeable. As a result, writing the actual `select…from` statement across Snowflake, Databricks, Google BigQuery, and Amazon Redshift would look the same. However, the actual SQL manipulation of data within the SELECT statement (ex. adding dates, casting columns) might look slightly different between each data warehouse. diff --git a/website/docs/sql-reference/string-functions/sql-lower.md b/website/docs/sql-reference/string-functions/sql-lower.md index 8c8622bb77a..7b1a5a2c2b3 100644 --- a/website/docs/sql-reference/string-functions/sql-lower.md +++ b/website/docs/sql-reference/string-functions/sql-lower.md @@ -54,7 +54,7 @@ After running this query, the `customers` table will look a little something lik Now, all characters in the `first_name` and `last_name` columns are lowercase. -> Changing all string columns to lowercase to create uniformity across data sources typically happens in our [dbt project’s staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lowercasing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. +> Changing all string columns to lowercase to create uniformity across data sources typically happens in our [dbt project’s staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lowercasing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. ## SQL LOWER function syntax in Snowflake, Databricks, BigQuery, and Redshift diff --git a/website/docs/sql-reference/string-functions/sql-trim.md b/website/docs/sql-reference/string-functions/sql-trim.md index ad54a015437..b9555feb630 100644 --- a/website/docs/sql-reference/string-functions/sql-trim.md +++ b/website/docs/sql-reference/string-functions/sql-trim.md @@ -50,4 +50,4 @@ In this query, you’re adding superfluous asterisks to a string using the [CONC ## TRIM function use cases -If string values in your raw data have extra white spaces or miscellaneous characters, you’ll leverage the TRIM (and subset RTRIM AND LTRIM) functions to help you quickly remove them. You’ll likely do this cleanup in [staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging), where you’re probably standardizing casing and doing other minor formatting changes to string values, so you can use a clean and consistent format across your downstream models. +If string values in your raw data have extra white spaces or miscellaneous characters, you’ll leverage the TRIM (and subset RTRIM AND LTRIM) functions to help you quickly remove them. You’ll likely do this cleanup in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging), where you’re probably standardizing casing and doing other minor formatting changes to string values, so you can use a clean and consistent format across your downstream models. diff --git a/website/docs/sql-reference/string-functions/sql-upper.md b/website/docs/sql-reference/string-functions/sql-upper.md index cf7694f8e46..a505537ac5d 100644 --- a/website/docs/sql-reference/string-functions/sql-upper.md +++ b/website/docs/sql-reference/string-functions/sql-upper.md @@ -46,7 +46,7 @@ After running this query, the `customers` table will look a little something lik Now, all characters in the `first_name` are uppercase (and `last_name` are unchanged). -> Changing string columns to uppercase to create uniformity across data sources typically happens in our [dbt project’s staging models](https://docs.getdbt.com/guides/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lower or upper casing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. +> Changing string columns to uppercase to create uniformity across data sources typically happens in our [dbt project’s staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging). There are a few reasons for that: data cleanup and standardization, such as aliasing, casting, and lower or upper casing, should ideally happen in staging models to create downstream uniformity and improve downstream performance. ## SQL UPPER function syntax in Snowflake, Databricks, BigQuery, and Redshift diff --git a/website/docs/terms/dag.md b/website/docs/terms/dag.md index f4247c785a4..d7ff1c44f49 100644 --- a/website/docs/terms/dag.md +++ b/website/docs/terms/dag.md @@ -65,7 +65,7 @@ See the DAG above? It follows a more traditional approach to data modeling where Instead, there are some key elements that can help you create a more streamlined DAG and [modular data models](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/): -- Leveraging [staging, intermediate, and mart layers](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) to create layers of distinction between sources and transformed data +- Leveraging [staging, intermediate, and mart layers](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) to create layers of distinction between sources and transformed data - Abstracting code that’s used across multiple models to its own model - Joining on surrogate keys versus on multiple values @@ -106,6 +106,6 @@ A Directed acyclic graph (DAG) is a visual representation of your data models an Ready to restructure (or create your first) DAG? Check out some of the resources below to better understand data modularity, data lineage, and how dbt helps bring it all together: - [Data modeling techniques for more modularity](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) -- [How we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) +- [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) - [How to audit your DAG](https://www.youtube.com/watch?v=5W6VrnHVkCA) - [Refactoring legacy SQL to dbt](/guides/migration/tools/refactoring-legacy-sql) diff --git a/website/docs/terms/data-lineage.md b/website/docs/terms/data-lineage.md index a03687eaba3..d0162c35616 100644 --- a/website/docs/terms/data-lineage.md +++ b/website/docs/terms/data-lineage.md @@ -89,7 +89,7 @@ The biggest challenges around data lineage become more apparent as your data, sy As dbt projects scale with data and organization growth, the number of sources, models, macros, seeds, and [exposures](https://docs.getdbt.com/docs/build/exposures) invariably grow. And with an increasing number of nodes in your DAG, it can become harder to audit your DAG for WET code or inefficiencies. -Working with dbt projects with thousands of models and nodes can feel overwhelming, but remember: your DAG and data lineage are meant to help you, not be your enemy. Tackle DAG audits in chunks, document all models, and [leverage strong structure conventions](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview). +Working with dbt projects with thousands of models and nodes can feel overwhelming, but remember: your DAG and data lineage are meant to help you, not be your enemy. Tackle DAG audits in chunks, document all models, and [leverage strong structure conventions](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). :::tip dbt project evaluator @@ -113,4 +113,4 @@ DAGs, data lineage, and root cause analysis…tell me more! Check out some of ou - [Glossary: DRY](https://docs.getdbt.com/terms/dry) - [Data techniques for modularity](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) -- [How we structure our dbt projects](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) +- [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) diff --git a/website/docs/terms/data-wrangling.md b/website/docs/terms/data-wrangling.md index a5b4e99f312..4a26507adfd 100644 --- a/website/docs/terms/data-wrangling.md +++ b/website/docs/terms/data-wrangling.md @@ -12,7 +12,7 @@ hoverSnippet: Data wrangling describes the different processes used to transform Data wrangling describes the different processes used to transform raw data into a consistent and easily usable format. For analytics engineers, you may know this better by the name of data cleaning. In data science or machine learning, "wrangling" often refers to prepping the data for model creation. -The ultimate goal of data wrangling is to work in a way that allows you to dive right into analysis on a dataset or build upon that data in a downstream model without worrying about basic cleaning like renaming, datatype casting, etc. Data wrangling acts as preparation for the development of [intermediate, fct/dim, or mart data models](/guides/best-practices/how-we-structure/1-guide-overview) that form the base layer that other data work can be built off of. Analytics engineers tend to do data wrangling work in the staging layer as a first transformation step after loading the data. This eliminates a foundational step done by an analytics engineer or analyst when building a downstream data model or dashboard. +The ultimate goal of data wrangling is to work in a way that allows you to dive right into analysis on a dataset or build upon that data in a downstream model without worrying about basic cleaning like renaming, datatype casting, etc. Data wrangling acts as preparation for the development of [intermediate, fct/dim, or mart data models](/best-practices/how-we-structure/1-guide-overview) that form the base layer that other data work can be built off of. Analytics engineers tend to do data wrangling work in the staging layer as a first transformation step after loading the data. This eliminates a foundational step done by an analytics engineer or analyst when building a downstream data model or dashboard. ## Data wrangling steps @@ -164,4 +164,4 @@ You could argue that data wrangling is one of the most important parts of an ana - [Our favorite SQL functions](https://www.getdbt.com/sql-foundations/top-sql-functions/) - [Glossary: Data warehouse](/terms/data-warehouse) - [Glossary: Primary key](/terms/primary-key) -- [Glossary: JSON](/terms/json) \ No newline at end of file +- [Glossary: JSON](/terms/json) diff --git a/website/docs/terms/dimensional-modeling.md b/website/docs/terms/dimensional-modeling.md index d0b5e9384a5..de88f7c318d 100644 --- a/website/docs/terms/dimensional-modeling.md +++ b/website/docs/terms/dimensional-modeling.md @@ -28,7 +28,7 @@ If you run a bakery (and we’d be interested in seeing the data person + baker Just as eating raw flour isn’t that appetizing, neither is deriving insights from raw data since it rarely has a nice structure that makes it poised for analytics. There’s some considerable work that’s needed to organize data and make it usable for business users. -This is where dimensional modeling comes into play; it’s a method that can help data folks create meaningful entities (cupcakes and cookies) to live inside their [data mart](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts) (your glass display) and eventually use for business intelligence purposes (eating said cookies). +This is where dimensional modeling comes into play; it’s a method that can help data folks create meaningful entities (cupcakes and cookies) to live inside their [data mart](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) (your glass display) and eventually use for business intelligence purposes (eating said cookies). So I guess we take it back—you’re not just trying to build a bakery, you’re also trying to build a top-notch foundation for meaningful analytics. Dimensional modeling can be a method to get you part of the way there. @@ -135,7 +135,7 @@ If your end data consumers are less comfortable with SQL and your BI tool doesn The benefits and drawbacks of dimensional modeling are pretty straightforward. Generally, the main advantages can be boiled down to: -* **More accessibility**: Since the output of good dimensional modeling is a [data mart](https://docs.getdbt.com/guides/best-practices/how-we-structure/4-marts), the tables created are easier to understand and more accessible to end consumers. +* **More accessibility**: Since the output of good dimensional modeling is a [data mart](https://docs.getdbt.com/best-practices/how-we-structure/4-marts), the tables created are easier to understand and more accessible to end consumers. * **More flexibility**: Easy to slice, dice, filter, and view your data in whatever way suits your purpose. * **Performance**: Fact and dimension models are typically materialized as tables or [incremental models](https://docs.getdbt.com/docs/build/incremental-models). Since these often form the core understanding of a business, they are queried often. Materializing them as tables allows them to be more performant in downstream BI platforms. @@ -156,4 +156,4 @@ Dimensional modeling is a tough, complex, and opinionated topic in the data worl * [Modular data modeling techniques](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) * [Stakeholder-friendly model naming conventions](https://docs.getdbt.com/blog/stakeholder-friendly-model-names/) -* [How we structure our dbt projects guide](https://docs.getdbt.com/guides/best-practices/how-we-structure/1-guide-overview) +* [How we structure our dbt projects guide](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) diff --git a/website/docs/terms/dry.md b/website/docs/terms/dry.md index be3d03ed4f0..b1649278cd2 100644 --- a/website/docs/terms/dry.md +++ b/website/docs/terms/dry.md @@ -89,7 +89,7 @@ DRY code is a principle that you should always be striving for. It saves you tim ## Further reading * [Data modeling technique for more modularity](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) -* [Why we use so many CTEs](https://docs.getdbt.com/docs/guides/best-practices) +* [Why we use so many CTEs](https://docs.getdbt.com/docs/best-practices) * [Glossary: CTE](https://docs.getdbt.com/terms/cte) * [Glossary: Materialization](https://docs.getdbt.com/terms/materialization) * [Glossary: View](https://docs.getdbt.com/terms/view) diff --git a/website/docs/terms/idempotent.md b/website/docs/terms/idempotent.md index 8772ba58b62..ea3ef0a099b 100644 --- a/website/docs/terms/idempotent.md +++ b/website/docs/terms/idempotent.md @@ -20,4 +20,4 @@ A non-idempotent version of the "_Save_" button might do something like "Append If word processors only gave us non-idempotent "Append paragraph" / "Update paragraph" / "Delete paragraph" operations, then saving our document changes would be a lot more difficult! We'd have to keep track of which paragraphs we previously saved, and either make sure to not save them again or have a process in place to regularly clean up duplicate paragraphs. The implementation of the "_Save_" button in word processors takes the collection of low-level non-idempotent filesystem operations (read/append/overwrite/delete), and systematically runs them in a certain order so that the _user_ doesn't have to deal with the non-idempotency. The user can just focus on writing -- choosing words, editing for clarity, ensuring paragraphs aren't too long, etc. -- and the word processor deals with making sure the words get persisted properly to disk. -This word processing analogy is very similar to what dbt does for [data transformation](https://www.getdbt.com/analytics-engineering/transformation/): it takes the collection of low-level non-idempotent database operations (`SELECT`/`INSERT`/`UPDATE`/`DELETE` -- collectively known as DML statements), and systematically runs them in a certain order so that analytics engineers don't have to deal with non-idempotency. We can just focus on the data -- [choosing good model and column names](https://docs.getdbt.com/blog/on-the-importance-of-naming), [documenting them](/community/resources/viewpoint#documentation), [ensuring data consumers can understand them](https://docs.getdbt.com/docs/guides/best-practices#consider-the-information-architecture-of-your-data-warehouse), etc. -- and [`dbt run`](https://docs.getdbt.com/reference/commands/run) will make sure the database ends up in the right state. +This word processing analogy is very similar to what dbt does for [data transformation](https://www.getdbt.com/analytics-engineering/transformation/): it takes the collection of low-level non-idempotent database operations (`SELECT`/`INSERT`/`UPDATE`/`DELETE` -- collectively known as DML statements), and systematically runs them in a certain order so that analytics engineers don't have to deal with non-idempotency. We can just focus on the data -- [choosing good model and column names](https://docs.getdbt.com/blog/on-the-importance-of-naming), [documenting them](/community/resources/viewpoint#documentation), [ensuring data consumers can understand them](https://docs.getdbt.com/docs/best-practices#consider-the-information-architecture-of-your-data-warehouse), etc. -- and [`dbt run`](https://docs.getdbt.com/reference/commands/run) will make sure the database ends up in the right state. diff --git a/website/docs/terms/view.md b/website/docs/terms/view.md index 90cd5d1f36f..53c122ca9e6 100644 --- a/website/docs/terms/view.md +++ b/website/docs/terms/view.md @@ -33,4 +33,4 @@ You shouldn’t expect a view in itself to be your final destination in terms of ## Further reading -- [Best practices guide on choosing table vs view materializations](/guides/best-practices) +- [Best practices guide on choosing table vs view materializations](/best-practices) diff --git a/website/sidebars.js b/website/sidebars.js index 3c9dfbc6536..43a7b56c4d5 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -1027,6 +1027,7 @@ const sidebarSettings = { }, "best-practices/debugging-errors", "best-practices/writing-custom-generic-tests", + "best-practices/best-practice-workflows", ], }, ], diff --git a/website/static/img/guides/best-practices/environment-setup/many-branch-git.png b/website/static/img/best-practices/environment-setup/many-branch-git.png similarity index 100% rename from website/static/img/guides/best-practices/environment-setup/many-branch-git.png rename to website/static/img/best-practices/environment-setup/many-branch-git.png diff --git a/website/static/img/guides/best-practices/environment-setup/many-deployments-table.png b/website/static/img/best-practices/environment-setup/many-deployments-table.png similarity index 100% rename from website/static/img/guides/best-practices/environment-setup/many-deployments-table.png rename to website/static/img/best-practices/environment-setup/many-deployments-table.png diff --git a/website/static/img/guides/best-practices/environment-setup/one-branch-git.png b/website/static/img/best-practices/environment-setup/one-branch-git.png similarity index 100% rename from website/static/img/guides/best-practices/environment-setup/one-branch-git.png rename to website/static/img/best-practices/environment-setup/one-branch-git.png diff --git a/website/static/img/guides/best-practices/environment-setup/one-deployment-table.png b/website/static/img/best-practices/environment-setup/one-deployment-table.png similarity index 100% rename from website/static/img/guides/best-practices/environment-setup/one-deployment-table.png rename to website/static/img/best-practices/environment-setup/one-deployment-table.png diff --git a/website/static/img/guides/best-practices/how-we-structure/narrowing-dag.png b/website/static/img/best-practices/how-we-structure/narrowing-dag.png similarity index 100% rename from website/static/img/guides/best-practices/how-we-structure/narrowing-dag.png rename to website/static/img/best-practices/how-we-structure/narrowing-dag.png diff --git a/website/static/img/guides/best-practices/how-we-structure/widening-dag.png b/website/static/img/best-practices/how-we-structure/widening-dag.png similarity index 100% rename from website/static/img/guides/best-practices/how-we-structure/widening-dag.png rename to website/static/img/best-practices/how-we-structure/widening-dag.png diff --git a/website/static/img/guides/best-practices/materializations/dbt-build-output.png b/website/static/img/best-practices/materializations/dbt-build-output.png similarity index 100% rename from website/static/img/guides/best-practices/materializations/dbt-build-output.png rename to website/static/img/best-practices/materializations/dbt-build-output.png diff --git a/website/static/img/guides/best-practices/materializations/incremental-diagram.png b/website/static/img/best-practices/materializations/incremental-diagram.png similarity index 100% rename from website/static/img/guides/best-practices/materializations/incremental-diagram.png rename to website/static/img/best-practices/materializations/incremental-diagram.png diff --git a/website/static/img/guides/best-practices/materializations/model-timing-diagram.png b/website/static/img/best-practices/materializations/model-timing-diagram.png similarity index 100% rename from website/static/img/guides/best-practices/materializations/model-timing-diagram.png rename to website/static/img/best-practices/materializations/model-timing-diagram.png diff --git a/website/static/img/guides/best-practices/materializations/snowflake-query-timing.png b/website/static/img/best-practices/materializations/snowflake-query-timing.png similarity index 100% rename from website/static/img/guides/best-practices/materializations/snowflake-query-timing.png rename to website/static/img/best-practices/materializations/snowflake-query-timing.png diff --git a/website/static/img/guides/best-practices/materializations/tables-and-views.png b/website/static/img/best-practices/materializations/tables-and-views.png similarity index 100% rename from website/static/img/guides/best-practices/materializations/tables-and-views.png rename to website/static/img/best-practices/materializations/tables-and-views.png diff --git a/website/static/img/guides/best-practices/semantic-layer/orders_erd.png b/website/static/img/best-practices/semantic-layer/orders_erd.png similarity index 100% rename from website/static/img/guides/best-practices/semantic-layer/orders_erd.png rename to website/static/img/best-practices/semantic-layer/orders_erd.png diff --git a/website/vercel.json b/website/vercel.json index 990794d5ee7..c6810ea7788 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -4061,6 +4061,11 @@ "source": "/quickstarts/manual-install", "destination": "/guides/manual-install", "permanent": true + }, + { + "source": "TODO", + "destination": "TODO", + "permanent": true } ] } From 71502f16ee0cbedb51ccba9a48cfd52a14ca7514 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:50:08 -0700 Subject: [PATCH 051/152] moving a few more guides --- .../migrating-from-spark-to-databricks.md | 50 +-- .../migrating-from-stored-procedures.md | 377 ++++++++++++++++++ .../1-migrating-from-stored-procedures.md | 27 -- .../2-mapping-inserts.md | 57 --- .../3-mapping-updates.md | 55 --- .../4-mapping-deletes.md | 45 --- .../5-mapping-merges.md | 184 --------- ...ating-from-stored-procedures-conclusion.md | 6 - .../tools => }/refactoring-legacy-sql.md | 20 +- .../guides/{migration => }/sl-migration.md | 21 +- 10 files changed, 432 insertions(+), 410 deletions(-) rename website/docs/guides/{migration/tools => }/migrating-from-spark-to-databricks.md (88%) create mode 100644 website/docs/guides/migrating-from-stored-procedures.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/3-mapping-updates.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/4-mapping-deletes.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/5-mapping-merges.md delete mode 100644 website/docs/guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion.md rename website/docs/guides/{migration/tools => }/refactoring-legacy-sql.md (95%) rename website/docs/guides/{migration => }/sl-migration.md (93%) diff --git a/website/docs/guides/migration/tools/migrating-from-spark-to-databricks.md b/website/docs/guides/migrating-from-spark-to-databricks.md similarity index 88% rename from website/docs/guides/migration/tools/migrating-from-spark-to-databricks.md rename to website/docs/guides/migrating-from-spark-to-databricks.md index cd0577c2d96..f9255c585db 100644 --- a/website/docs/guides/migration/tools/migrating-from-spark-to-databricks.md +++ b/website/docs/guides/migrating-from-spark-to-databricks.md @@ -1,18 +1,35 @@ --- title: "Migrating from dbt-spark to dbt-databricks" id: "migrating-from-spark-to-databricks" +description: Learn how to migrate from dbt-spark to dbt-databricks. +displayText: Migrating from Spark to Databricks +hoverSnippet: Learn how to migrate from dbt-spark to dbt-databricks. +time_to_complete: '30 minutes' +platform: ['dbt-core','dbt-cloud'] +icon: 'guides' +hide_table_of_contents: true +tags: ['migration', 'dbt Core','dbt Cloud'] +level: 'Intermediate' +recently_updated: true --- +## Introduction + You can [migrate your projects](#migrate-your-dbt-projects) from using the `dbt-spark` adapter to using the [dbt-databricks adapter](https://github.com/databricks/dbt-databricks). In collaboration with dbt Labs, Databricks built this adapter using dbt-spark as the foundation and added some critical improvements. With it, you get an easier set up — requiring only three inputs for authentication — and more features such as support for [Unity Catalog](https://www.databricks.com/product/unity-catalog). -## Simpler authentication +### Prerequisites + +- Your project must be compatible with dbt 1.0 or greater. Refer to [Upgrading to v1.0](/docs/dbt-versions/core-upgrade/upgrading-to-v1.0) for details. For the latest version of dbt, refer to [Upgrading to v1.3](/docs/dbt-versions/core-upgrade/upgrading-to-v1.3). +- For dbt Cloud, you need administrative (admin) privileges to migrate dbt projects. + +### Simpler authentication Previously, you had to provide a `cluster` or `endpoint` ID which was hard to parse from the `http_path` that you were given. Now, it doesn't matter if you're using a cluster or an SQL endpoint because the [dbt-databricks setup](/docs/core/connect-data-platform/databricks-setup) requires the _same_ inputs for both. All you need to provide is: - hostname of the Databricks workspace - HTTP path of the Databricks SQL warehouse or cluster - appropriate credentials -## Better defaults +### Better defaults The `dbt-databricks` adapter provides better defaults than `dbt-spark` does. The defaults help optimize your workflow so you can get the fast performance and cost-effectiveness of Databricks. They are: @@ -24,24 +41,14 @@ With dbt-spark, however, the default for `incremental_strategy` is `append`. If For more information on defaults, see [Caveats](/docs/core/connect-data-platform/databricks-setup#caveats). -## Pure Python +### Pure Python If you use dbt Core, you no longer have to download an independent driver to interact with Databricks. The connection information is all embedded in a pure-Python library called `databricks-sql-connector`. -## Migrate your dbt projects - -In both dbt Core and dbt Cloud, you can migrate your projects to the Databricks-specific adapter from the generic Apache Spark adapter. - -### Prerequisites - -- Your project must be compatible with dbt 1.0 or greater. Refer to [Upgrading to v1.0](/docs/dbt-versions/core-upgrade/upgrading-to-v1.0) for details. For the latest version of dbt, refer to [Upgrading to v1.3](/docs/dbt-versions/core-upgrade/upgrading-to-v1.3). -- For dbt Cloud, you need administrative (admin) privileges to migrate dbt projects. +## Migrate your dbt projects in dbt Cloud - - - - +You can migrate your projects to the Databricks-specific adapter from the generic Apache Spark adapter. If you're using dbt Core, then skip to Step 4. The migration to the `dbt-databricks` adapter from `dbt-spark` shouldn't cause any downtime for production jobs. dbt Labs recommends that you schedule the connection change when usage of the IDE is light to avoid disrupting your team. @@ -60,7 +67,7 @@ To update your Databricks connection in dbt Cloud: Everyone in your organization who uses dbt Cloud must refresh the IDE before starting work again. It should refresh in less than a minute. -#### About your credentials +## Configure your credentials When you update the Databricks connection in dbt Cloud, your team will not lose their credentials. This makes migrating easier since it only requires you to delete the Databricks connection and re-add the cluster or endpoint information. @@ -70,9 +77,7 @@ These credentials will not get lost when there's a successful connection to Data - The personal access tokens your team added in their dbt Cloud profile so they can develop in the IDE for a given project. - The access token you added for each deployment environment so dbt Cloud can connect to Databricks during production jobs. - - - +## Migrate dbt projects in dbt Core To migrate your dbt Core projects to the `dbt-databricks` adapter from `dbt-spark`, you: 1. Install the [dbt-databricks adapter](https://github.com/databricks/dbt-databricks) in your environment @@ -80,13 +85,8 @@ To migrate your dbt Core projects to the `dbt-databricks` adapter from `dbt-spar Anyone who's using your project must also make these changes in their environment. - - - - - -### Examples +## Try these examples You can use the following examples of the `profiles.yml` file to see the authentication setup with `dbt-spark` compared to the simpler setup with `dbt-databricks` when connecting to an SQL endpoint. A cluster example would look similar. diff --git a/website/docs/guides/migrating-from-stored-procedures.md b/website/docs/guides/migrating-from-stored-procedures.md new file mode 100644 index 00000000000..e8abff49aec --- /dev/null +++ b/website/docs/guides/migrating-from-stored-procedures.md @@ -0,0 +1,377 @@ +--- +title: Migrating from DDL, DML, and stored procedures +id: migrating-from-stored-procedures +description: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models, including tips and patterns for the shift from a procedural to a declarative approach in defining datasets. +displayText: Migrating from DDL, DML, and stored procedures +hoverSnippet: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models +time_to_complete: '30 minutes' +platform: 'dbt-core' +icon: 'guides' +hide_table_of_contents: true +tags: ['materializations', 'dbt Core'] +level: 'Beginner' +recently_updated: true +--- + +## Introduction + +One of the more common situations that new dbt adopters encounter is a historical codebase of transformations written as a hodgepodge of DDL and DML statements, or stored procedures. Going from DML statements to dbt models is often a challenging hump for new users to get over, because the process involves a significant paradigm shift between a procedural flow of building a dataset (e.g. a series of DDL and DML statements) to a declarative approach to defining a dataset (e.g. how dbt uses SELECT statements to express data models). This guide aims to provide tips, tricks, and common patterns for converting DML statements to dbt models. + +### Preparing to migrate + +Before getting into the meat of conversion, it’s worth noting that DML statements will not always illustrate a comprehensive set of columns and column types that an original table might contain. Without knowing the DDL to create the table, it’s impossible to know precisely if your conversion effort is apples-to-apples, but you can generally get close. + +If your supports `SHOW CREATE TABLE`, that can be a quick way to get a comprehensive set of columns you’ll want to recreate. If you don’t have the DDL, but are working on a substantial stored procedure, one approach that can work is to pull column lists out of any DML statements that modify the table, and build up a full set of the columns that appear. + +As for ensuring that you have the right column types, since models materialized by dbt generally use `CREATE TABLE AS SELECT` or `CREATE VIEW AS SELECT` as the driver for object creation, tables can end up with unintended column types if the queries aren’t explicit. For example, if you care about `INT` versus `DECIMAL` versus `NUMERIC`, it’s generally going to be best to be explicit. The good news is that this is easy with dbt: you just cast the column to the type you intend. + +We also generally recommend that column renaming and type casting happen as close to the source tables as possible, typically in a layer of staging transformations, which helps ensure that future dbt modelers will know where to look for those transformations! See [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) for more guidance on overall project structure. + +### Operations we need to map + +There are four primary DML statements that you are likely to have to convert to dbt operations while migrating a procedure: + +- `INSERT` +- `UPDATE` +- `DELETE` +- `MERGE` + +Each of these can be addressed using various techniques in dbt. Handling `MERGE`s is a bit more involved than the rest, but can be handled effectively via dbt. The first three, however, are fairly simple to convert. + +## Map INSERTs + +An `INSERT` statement is functionally the same as using dbt to `SELECT` from an existing source or other dbt model. If you are faced with an `INSERT`-`SELECT` statement, the easiest way to convert the statement is to just create a new dbt model, and pull the `SELECT` portion of the `INSERT` statement out of the procedure and into the model. That’s basically it! + +To really break it down, let’s consider a simple example: + +```sql +INSERT INTO returned_orders (order_id, order_date, total_return) + +SELECT order_id, order_date, total FROM orders WHERE type = 'return' +``` + +Converting this with a first pass to a [dbt model](/guides/bigquery?step=8) (in a file called returned_orders.sql) might look something like: + +```sql +SELECT + order_id as order_id, + order_date as order_date, + total as total_return + +FROM {{ ref('orders') }} + +WHERE type = 'return' +``` + +Functionally, this would create a model (which could be materialized as a table or view depending on needs) called `returned_orders` that contains three columns: `order_id`, `order_date`, `total_return`) predicated on the type column. It achieves the same end as the `INSERT`, just in a declarative fashion, using dbt. + +### **A note on `FROM` clauses** + +In dbt, using a hard-coded table or view name in a `FROM` clause is one of the most serious mistakes new users make. dbt uses the ref and source macros to discover the ordering that transformations need to execute in, and if you don’t use them, you’ll be unable to benefit from dbt’s built-in lineage generation and pipeline execution. In the sample code throughout the remainder of this article, we’ll use ref statements in the dbt-converted versions of SQL statements, but it is an exercise for the reader to ensure that those models exist in their dbt projects. + +### **Sequential `INSERT`s to an existing table can be `UNION ALL`’ed together** + +Since dbt models effectively perform a single `CREATE TABLE AS SELECT` (or if you break it down into steps, `CREATE`, then an `INSERT`), you may run into complexities if there are multiple `INSERT` statements in your transformation that all insert data into the same table. Fortunately, this is a simple thing to handle in dbt. Effectively, the logic is performing a `UNION ALL` between the `INSERT` queries. If I have a transformation flow that looks something like (ignore the contrived nature of the scenario): + +```sql +CREATE TABLE all_customers + +INSERT INTO all_customers SELECT * FROM us_customers + +INSERT INTO all_customers SELECT * FROM eu_customers +``` + +The dbt-ified version of this would end up looking something like: + +```sql +SELECT * FROM {{ ref('us_customers') }} + +UNION ALL + +SELECT * FROM {{ ref('eu_customers') }} +``` + +The logic is functionally equivalent. So if there’s another statement that `INSERT`s into a model that I’ve already created, I can just add that logic into a second `SELECT` statement that is just `UNION ALL`'ed with the first. Easy! + +## Map UPDATEs + +`UPDATE`s start to increase the complexity of your transformations, but fortunately, they’re pretty darn simple to migrate, as well. The thought process that you go through when translating an `UPDATE` is quite similar to how an `INSERT` works, but the logic for the `SELECT` list in the dbt model is primarily sourced from the content in the `SET` section of the `UPDATE` statement. Let’s look at a simple example: + +```sql +UPDATE orders + +SET type = 'return' + +WHERE total < 0 +``` + +The way to look at this is similar to an `INSERT`-`SELECT` statement. The table being updated is the model you want to modify, and since this is an `UPDATE`, that model has likely already been created, and you can either: + +- add to it with subsequent transformations +- create an intermediate model that builds off of the original model – perhaps naming it something like `int_[entity]_[verb].sql`. + +The `SELECT` list should contain all of the columns for the table, but for the specific columns being updated by the DML, you’ll use the computation on the right side of the equals sign as the `SELECT`ed value. Then, you can use the target column name on the left of the equals sign as the column alias. + +If I were building an intermediate transformation from the above query would translate to something along the lines of: + +```sql +SELECT + CASE + WHEN total < 0 THEN 'return' + ELSE type + END AS type, + + order_id, + order_date + +FROM {{ ref('stg_orders') }} +``` + +Since the `UPDATE` statement doesn’t modify every value of the type column, we use a `CASE` statement to apply the contents’ `WHERE` clause. We still want to select all of the columns that should end up in the target table. If we left one of the columns out, it wouldn’t be passed through to the target table at all due to dbt’s declarative approach. + +Sometimes, you may not be sure what all the columns are in a table, or in the situation as above, you’re only modifying a small number of columns relative to the total number of columns in the table. It can be cumbersome to list out every column in the table, but fortunately dbt contains some useful utility macros that can help list out the full column list of a table. + +Another way I could have written the model a bit more dynamically might be: + +```sql +SELECT + {{ dbt_utils.star(from=ref('stg_orders'), except=['type']) }}, + CASE + WHEN total < 0 THEN 'return' + ELSE type + END AS type, + +FROM {{ ref('stg_orders') }} +``` + +The `dbt_utils.star()` macro will print out the full list of columns in the table, but skip the ones I’ve listed in the except list, which allows me to perform the same logic while writing fewer lines of code. This is a simple example of using dbt macros to simplify and shorten your code, and dbt can get a lot more sophisticated as you learn more techniques. Read more about the [dbt_utils package](https://hub.getdbt.com/dbt-labs/dbt_utils/latest/) and the [star macro](https://github.com/dbt-labs/dbt-utils/tree/0.8.6/#star-source). + +## Map DELETEs + +One of the biggest differences between a procedural transformation and how dbt models data is that dbt, in general, will never destroy data. While there are ways to execute hard `DELETE`s in dbt that are outside of the scope of this article, the general best practice for handling deleted data is to just use soft deletes, and filter out soft-deleted data in a final transformation. + +Let’s consider a simple example query: + +```sql +DELETE FROM stg_orders WHERE order_status IS NULL +``` + +In a dbt model, you’ll need to first identify the records that should be deleted and then filter them out. There are really two primary ways you might translate this query: + +```sql +SELECT * FROM {{ ref('stg_orders') }} WHERE order_status IS NOT NULL +``` + +This first approach just inverts the logic of the DELETE to describe the set of records that should remain, instead of the set of records that should be removed. This ties back to the way dbt declaratively describes datasets. You reference the data that should be in a dataset, and the table or view gets created with that set of data. + +Another way you could achieve this is by marking the deleted records, and then filtering them out. For example: + +```sql +WITH + +soft_deletes AS ( + + SELECT + *, + CASE + WHEN order_status IS NULL THEN true + ELSE false + END AS to_delete + + FROM {{ ref('stg_orders') }} + +) + +SELECT * FROM soft_deletes WHERE to_delete = false +``` + +This approach flags all of the deleted records, and the final `SELECT` filters out any deleted data, so the resulting table contains only the remaining records. It’s a lot more verbose than just inverting the `DELETE` logic, but for complex `DELETE` logic, this ends up being a very effective way of performing the `DELETE` that retains historical context. + +It’s worth calling out that while this doesn’t enable a hard delete, hard deletes can be executed a number of ways, the most common being to execute a dbt [macros](/docs/build/jinja-macros) via as a [run-operation](https://docs.getdbt.com/reference/commands/run-operation), or by using a [post-hook](https://docs.getdbt.com/reference/resource-configs/pre-hook-post-hook/) to perform a `DELETE` statement after the records to-be-deleted have been marked. These are advanced approaches outside the scope of this guide. + + +## Map MERGEs +dbt has a concept called [materialization](/docs/build/materializations), which determines how a model is physically or logically represented in the warehouse. `INSERT`s, `UPDATE`s, and `DELETE`s will typically be accomplished using table or view materializations. For incremental workloads accomplished via commands like `MERGE` or `UPSERT`, dbt has a particular materialization called [incremental](/docs/build/incremental-models). The incremental materialization is specifically used to handle incremental loads and updates to a table without recreating the entire table from scratch on every run. + +### Step 1: Map the MERGE like an INSERT/UPDATE to start + +Before we get into the exact details of how to implement an incremental materialization, let’s talk about logic conversion. Extracting the logic of the `MERGE` and handling it as you would an `INSERT` or an `UPDATE` is the easiest way to get started migrating a `MERGE` command. . + +To see how the logic conversion works, we’ll start with an example `MERGE`. In this scenario, imagine a ride sharing app where rides are loaded into a details table daily, and tips may be updated at some later date, and need to be kept up-to-date: + +```sql +MERGE INTO ride_details USING ( + SELECT + ride_id, + subtotal, + tip + + FROM rides_to_load AS rtl + + ON ride_details.ride_id = rtl.ride_id + + WHEN MATCHED THEN UPDATE + + SET ride_details.tip = rtl.tip + + WHEN NOT MATCHED THEN INSERT (ride_id, subtotal, tip) + VALUES (rtl.ride_id, rtl.subtotal, NVL(rtl.tip, 0, rtl.tip) +); +``` + +The content of the `USING` clause is a useful piece of code because that can easily be placed in a CTE as a starting point for handling the match statement. I find that the easiest way to break this apart is to treat each match statement as a separate CTE that builds on the previous match statements. + +We can ignore the `ON` clause for now, as that will only come into play once we get to a point where we’re ready to turn this into an incremental. + +As with `UPDATE`s and `INSERT`s, you can use the `SELECT` list and aliases to name columns appropriately for the target table, and `UNION` together `INSERT` statements (taking care to use `UNION`, rather than `UNION ALL` to avoid duplicates). + +The `MERGE` would end up translating to something like this: + +```sql +WITH + +using_clause AS ( + + SELECT + ride_id, + subtotal, + tip + + FROM {{ ref('rides_to_load') }} + +), + +updates AS ( + + SELECT + ride_id, + subtotal, + tip + + FROM using_clause + +), + +inserts AS ( + + SELECT + ride_id, + subtotal, + NVL(tip, 0, tip) + + FROM using_clause + +) + +SELECT * + +FROM updates + +UNION inserts +``` + +To be clear, this transformation isn’t complete. The logic here is similar to the `MERGE`, but will not actually do the same thing, since the updates and inserts CTEs are both selecting from the same source query. We’ll need to ensure we grab the separate sets of data as we transition to the incremental materialization. + +One important caveat is that dbt does not natively support `DELETE` as a `MATCH` action. If you have a line in your `MERGE` statement that uses `WHEN MATCHED THEN DELETE`, you’ll want to treat it like an update and add a soft-delete flag, which is then filtered out in a follow-on transformation. + +### Step 2: Convert to incremental materialization + +As mentioned above, incremental materializations are a little special in that when the target table does not exist, the materialization functions in nearly the same way as a standard table materialization, and executes a `CREATE TABLE AS SELECT` statement. If the target table does exist, however, the materialization instead executes a `MERGE` statement. + +Since a `MERGE` requires a `JOIN` condition between the `USING` clause and the target table, we need a way to specify how dbt determines whether or not a record triggers a match or not. That particular piece of information is specified in the dbt model configuration. + +We can add the following `config()` block to the top of our model to specify how it should build incrementally: + +```sql +{{ + config( + materialized='incremental', + unique_key='ride_id', + incremental_strategy='merge' + ) +}} +``` + +The three configuration fields in this example are the most important ones. + +- Setting `materialized='incremental'` tells dbt to apply UPSERT logic to the target table. +- The `unique_key` should be a primary key of the target table. This is used to match records with the existing table. +- `incremental_strategy` here is set to MERGE any existing rows in the target table with a value for the `unique_key` which matches the incoming batch of data. There are [various incremental strategies](/docs/build/incremental-models#about-incremental_strategy) for different situations and warehouses. + +The bulk of the work in converting a model to an incremental materialization comes in determining how the logic should change for incremental loads versus full backfills or initial loads. dbt offers a special macro, `is_incremental()`, which evaluates false for initial loads or for backfills (called full refreshes in dbt parlance), but true for incremental loads. + +This macro can be used to augment the model code to adjust how data is loaded for subsequent loads. How that logic should be added will depend a little bit on how data is received. Some common ways might be: + +1. The source table is truncated ahead of incremental loads, and only contains the data to be loaded in that increment. +2. The source table contains all historical data, and there is a load timestamp column that identifies new data to be loaded. + +In the first case, the work is essentially done already. Since the source table always contains only the new data to be loaded, the query doesn’t have to change for incremental loads. The second case, however, requires the use of the `is_incremental()` macro to correctly handle the logic. + +Taking the converted `MERGE` statement that we’d put together previously, we’d augment it to add this additional logic: + +```sql +WITH + +using_clause AS ( + + SELECT + ride_id, + subtotal, + tip, + max(load_timestamp) as load_timestamp + + FROM {{ ref('rides_to_load') }} + + + {% if is_incremental() %} + + WHERE load_timestamp > (SELECT max(load_timestamp) FROM {{ this }}) + + {% endif %} + +), + +updates AS ( + + SELECT + ride_id, + subtotal, + tip, + load_timestamp + + FROM using_clause + + {% if is_incremental() %} + + WHERE ride_id IN (SELECT ride_id FROM {{ this }}) + + {% endif %} + +), + +inserts AS ( + + SELECT + ride_id, + subtotal, + NVL(tip, 0, tip), + load_timestamp + + FROM using_clause + + WHERE ride_id NOT IN (SELECT ride_id FROM updates) + +) + +SELECT * FROM updates UNION inserts +``` + +There are a couple important concepts to understand here: + +1. The code in the `is_incremental()` conditional block only executes for incremental executions of this model code. If the target table doesn’t exist, or if the `--full-refresh` option is used, that code will not execute. +2. `{{ this }}` is a special keyword in dbt that when used in a Jinja block, self-refers to the model for which the code is executing. So if you have a model in a file called `my_incremental_model.sql`, `{{ this }}` will refer to `my_incremental_model` (fully qualified with database and schema name if necessary). By using that keyword, we can leverage the current state of the target table to inform the source query. + + +## Migrate Stores procedures + +The techniques shared above are useful ways to get started converting the individual DML statements that are often found in stored procedures. Using these types of patterns, legacy procedural code can be rapidly transitioned to dbt models that are much more readable, maintainable, and benefit from software engineering best practices like DRY principles. Additionally, once transformations are rewritten as dbt models, it becomes much easier to test the transformations to ensure that the data being used downstream is high-quality and trustworthy. diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md deleted file mode 100644 index c16df789939..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Migrating from DDL, DML, and stored procedures -id: 1-migrating-from-stored-procedures ---- - -One of the more common situations that new dbt adopters encounter is a historical codebase of transformations written as a hodgepodge of DDL and DML statements, or stored procedures. Going from DML statements to dbt models is often a challenging hump for new users to get over, because the process involves a significant paradigm shift between a procedural flow of building a dataset (e.g. a series of DDL and DML statements) to a declarative approach to defining a dataset (e.g. how dbt uses SELECT statements to express data models). This guide aims to provide tips, tricks, and common patterns for converting DML statements to dbt models. - -## Preparing to migrate - -Before getting into the meat of conversion, it’s worth noting that DML statements will not always illustrate a comprehensive set of columns and column types that an original table might contain. Without knowing the DDL to create the table, it’s impossible to know precisely if your conversion effort is apples-to-apples, but you can generally get close. - -If your supports `SHOW CREATE TABLE`, that can be a quick way to get a comprehensive set of columns you’ll want to recreate. If you don’t have the DDL, but are working on a substantial stored procedure, one approach that can work is to pull column lists out of any DML statements that modify the table, and build up a full set of the columns that appear. - -As for ensuring that you have the right column types, since models materialized by dbt generally use `CREATE TABLE AS SELECT` or `CREATE VIEW AS SELECT` as the driver for object creation, tables can end up with unintended column types if the queries aren’t explicit. For example, if you care about `INT` versus `DECIMAL` versus `NUMERIC`, it’s generally going to be best to be explicit. The good news is that this is easy with dbt: you just cast the column to the type you intend. - -We also generally recommend that column renaming and type casting happen as close to the source tables as possible, typically in a layer of staging transformations, which helps ensure that future dbt modelers will know where to look for those transformations! See [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) for more guidance on overall project structure. - -### Operations we need to map - -There are four primary DML statements that you are likely to have to convert to dbt operations while migrating a procedure: - -- `INSERT` -- `UPDATE` -- `DELETE` -- `MERGE` - -Each of these can be addressed using various techniques in dbt. Handling `MERGE`s is a bit more involved than the rest, but can be handled effectively via dbt. The first three, however, are fairly simple to convert. diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md deleted file mode 100644 index 44e01784f04..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/2-mapping-inserts.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Inserts -id: 2-inserts ---- - -An `INSERT` statement is functionally the same as using dbt to `SELECT` from an existing source or other dbt model. If you are faced with an `INSERT`-`SELECT` statement, the easiest way to convert the statement is to just create a new dbt model, and pull the `SELECT` portion of the `INSERT` statement out of the procedure and into the model. That’s basically it! - -To really break it down, let’s consider a simple example: - -```sql -INSERT INTO returned_orders (order_id, order_date, total_return) - -SELECT order_id, order_date, total FROM orders WHERE type = 'return' -``` - -Converting this with a first pass to a [dbt model](/guides/bigquery?step=8) (in a file called returned_orders.sql) might look something like: - -```sql -SELECT - order_id as order_id, - order_date as order_date, - total as total_return - -FROM {{ ref('orders') }} - -WHERE type = 'return' -``` - -Functionally, this would create a model (which could be materialized as a table or view depending on needs) called `returned_orders` that contains three columns: `order_id`, `order_date`, `total_return`) predicated on the type column. It achieves the same end as the `INSERT`, just in a declarative fashion, using dbt. - -## **A note on `FROM` clauses** - -In dbt, using a hard-coded table or view name in a `FROM` clause is one of the most serious mistakes new users make. dbt uses the ref and source macros to discover the ordering that transformations need to execute in, and if you don’t use them, you’ll be unable to benefit from dbt’s built-in lineage generation and pipeline execution. In the sample code throughout the remainder of this article, we’ll use ref statements in the dbt-converted versions of SQL statements, but it is an exercise for the reader to ensure that those models exist in their dbt projects. - -## **Sequential `INSERT`s to an existing table can be `UNION ALL`’ed together** - -Since dbt models effectively perform a single `CREATE TABLE AS SELECT` (or if you break it down into steps, `CREATE`, then an `INSERT`), you may run into complexities if there are multiple `INSERT` statements in your transformation that all insert data into the same table. Fortunately, this is a simple thing to handle in dbt. Effectively, the logic is performing a `UNION ALL` between the `INSERT` queries. If I have a transformation flow that looks something like (ignore the contrived nature of the scenario): - -```sql -CREATE TABLE all_customers - -INSERT INTO all_customers SELECT * FROM us_customers - -INSERT INTO all_customers SELECT * FROM eu_customers -``` - -The dbt-ified version of this would end up looking something like: - -```sql -SELECT * FROM {{ ref('us_customers') }} - -UNION ALL - -SELECT * FROM {{ ref('eu_customers') }} -``` - -The logic is functionally equivalent. So if there’s another statement that `INSERT`s into a model that I’ve already created, I can just add that logic into a second `SELECT` statement that is just `UNION ALL`'ed with the first. Easy! diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/3-mapping-updates.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/3-mapping-updates.md deleted file mode 100644 index b6f0874fb6b..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/3-mapping-updates.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Updates -id: 3-updates ---- - -`UPDATE`s start to increase the complexity of your transformations, but fortunately, they’re pretty darn simple to migrate, as well. The thought process that you go through when translating an `UPDATE` is quite similar to how an `INSERT` works, but the logic for the `SELECT` list in the dbt model is primarily sourced from the content in the `SET` section of the `UPDATE` statement. Let’s look at a simple example: - -```sql -UPDATE orders - -SET type = 'return' - -WHERE total < 0 -``` - -The way to look at this is similar to an `INSERT`-`SELECT` statement. The table being updated is the model you want to modify, and since this is an `UPDATE`, that model has likely already been created, and you can either: - -- add to it with subsequent transformations -- create an intermediate model that builds off of the original model – perhaps naming it something like `int_[entity]_[verb].sql`. - -The `SELECT` list should contain all of the columns for the table, but for the specific columns being updated by the DML, you’ll use the computation on the right side of the equals sign as the `SELECT`ed value. Then, you can use the target column name on the left of the equals sign as the column alias. - -If I were building an intermediate transformation from the above query would translate to something along the lines of: - -```sql -SELECT - CASE - WHEN total < 0 THEN 'return' - ELSE type - END AS type, - - order_id, - order_date - -FROM {{ ref('stg_orders') }} -``` - -Since the `UPDATE` statement doesn’t modify every value of the type column, we use a `CASE` statement to apply the contents’ `WHERE` clause. We still want to select all of the columns that should end up in the target table. If we left one of the columns out, it wouldn’t be passed through to the target table at all due to dbt’s declarative approach. - -Sometimes, you may not be sure what all the columns are in a table, or in the situation as above, you’re only modifying a small number of columns relative to the total number of columns in the table. It can be cumbersome to list out every column in the table, but fortunately dbt contains some useful utility macros that can help list out the full column list of a table. - -Another way I could have written the model a bit more dynamically might be: - -```sql -SELECT - {{ dbt_utils.star(from=ref('stg_orders'), except=['type']) }}, - CASE - WHEN total < 0 THEN 'return' - ELSE type - END AS type, - -FROM {{ ref('stg_orders') }} -``` - -The `dbt_utils.star()` macro will print out the full list of columns in the table, but skip the ones I’ve listed in the except list, which allows me to perform the same logic while writing fewer lines of code. This is a simple example of using dbt macros to simplify and shorten your code, and dbt can get a lot more sophisticated as you learn more techniques. Read more about the [dbt_utils package](https://hub.getdbt.com/dbt-labs/dbt_utils/latest/) and the [star macro](https://github.com/dbt-labs/dbt-utils/tree/0.8.6/#star-source). diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/4-mapping-deletes.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/4-mapping-deletes.md deleted file mode 100644 index 1a8c6435d42..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/4-mapping-deletes.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Deletes -id: 4-deletes ---- - -One of the biggest differences between a procedural transformation and how dbt models data is that dbt, in general, will never destroy data. While there are ways to execute hard `DELETE`s in dbt that are outside of the scope of this article, the general best practice for handling deleted data is to just use soft deletes, and filter out soft-deleted data in a final transformation. - -Let’s consider a simple example query: - -```sql -DELETE FROM stg_orders WHERE order_status IS NULL -``` - -In a dbt model, you’ll need to first identify the records that should be deleted and then filter them out. There are really two primary ways you might translate this query: - -```sql -SELECT * FROM {{ ref('stg_orders') }} WHERE order_status IS NOT NULL -``` - -This first approach just inverts the logic of the DELETE to describe the set of records that should remain, instead of the set of records that should be removed. This ties back to the way dbt declaratively describes datasets. You reference the data that should be in a dataset, and the table or view gets created with that set of data. - -Another way you could achieve this is by marking the deleted records, and then filtering them out. For example: - -```sql -WITH - -soft_deletes AS ( - - SELECT - *, - CASE - WHEN order_status IS NULL THEN true - ELSE false - END AS to_delete - - FROM {{ ref('stg_orders') }} - -) - -SELECT * FROM soft_deletes WHERE to_delete = false -``` - -This approach flags all of the deleted records, and the final `SELECT` filters out any deleted data, so the resulting table contains only the remaining records. It’s a lot more verbose than just inverting the `DELETE` logic, but for complex `DELETE` logic, this ends up being a very effective way of performing the `DELETE` that retains historical context. - -It’s worth calling out that while this doesn’t enable a hard delete, hard deletes can be executed a number of ways, the most common being to execute a dbt [macros](/docs/build/jinja-macros) via as a [run-operation](https://docs.getdbt.com/reference/commands/run-operation), or by using a [post-hook](https://docs.getdbt.com/reference/resource-configs/pre-hook-post-hook/) to perform a `DELETE` statement after the records to-be-deleted have been marked. These are advanced approaches outside the scope of this guide. diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/5-mapping-merges.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/5-mapping-merges.md deleted file mode 100644 index d059ab9a258..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/5-mapping-merges.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Merges -id: 5-merges ---- - -dbt has a concept called [materialization](/docs/build/materializations), which determines how a model is physically or logically represented in the warehouse. `INSERT`s, `UPDATE`s, and `DELETE`s will typically be accomplished using table or view materializations. For incremental workloads accomplished via commands like `MERGE` or `UPSERT`, dbt has a particular materialization called [incremental](/docs/build/incremental-models). The incremental materialization is specifically used to handle incremental loads and updates to a table without recreating the entire table from scratch on every run. - -## Step 1: Map the MERGE like an INSERT/UPDATE to start - -Before we get into the exact details of how to implement an incremental materialization, let’s talk about logic conversion. Extracting the logic of the `MERGE` and handling it as you would an `INSERT` or an `UPDATE` is the easiest way to get started migrating a `MERGE` command. . - -To see how the logic conversion works, we’ll start with an example `MERGE`. In this scenario, imagine a ride sharing app where rides are loaded into a details table daily, and tips may be updated at some later date, and need to be kept up-to-date: - -```sql -MERGE INTO ride_details USING ( - SELECT - ride_id, - subtotal, - tip - - FROM rides_to_load AS rtl - - ON ride_details.ride_id = rtl.ride_id - - WHEN MATCHED THEN UPDATE - - SET ride_details.tip = rtl.tip - - WHEN NOT MATCHED THEN INSERT (ride_id, subtotal, tip) - VALUES (rtl.ride_id, rtl.subtotal, NVL(rtl.tip, 0, rtl.tip) -); -``` - -The content of the `USING` clause is a useful piece of code because that can easily be placed in a CTE as a starting point for handling the match statement. I find that the easiest way to break this apart is to treat each match statement as a separate CTE that builds on the previous match statements. - -We can ignore the `ON` clause for now, as that will only come into play once we get to a point where we’re ready to turn this into an incremental. - -As with `UPDATE`s and `INSERT`s, you can use the `SELECT` list and aliases to name columns appropriately for the target table, and `UNION` together `INSERT` statements (taking care to use `UNION`, rather than `UNION ALL` to avoid duplicates). - -The `MERGE` would end up translating to something like this: - -```sql -WITH - -using_clause AS ( - - SELECT - ride_id, - subtotal, - tip - - FROM {{ ref('rides_to_load') }} - -), - -updates AS ( - - SELECT - ride_id, - subtotal, - tip - - FROM using_clause - -), - -inserts AS ( - - SELECT - ride_id, - subtotal, - NVL(tip, 0, tip) - - FROM using_clause - -) - -SELECT * - -FROM updates - -UNION inserts -``` - -To be clear, this transformation isn’t complete. The logic here is similar to the `MERGE`, but will not actually do the same thing, since the updates and inserts CTEs are both selecting from the same source query. We’ll need to ensure we grab the separate sets of data as we transition to the incremental materialization. - -One important caveat is that dbt does not natively support `DELETE` as a `MATCH` action. If you have a line in your `MERGE` statement that uses `WHEN MATCHED THEN DELETE`, you’ll want to treat it like an update and add a soft-delete flag, which is then filtered out in a follow-on transformation. - -### Step 2: Convert to incremental materialization - -As mentioned above, incremental materializations are a little special in that when the target table does not exist, the materialization functions in nearly the same way as a standard table materialization, and executes a `CREATE TABLE AS SELECT` statement. If the target table does exist, however, the materialization instead executes a `MERGE` statement. - -Since a `MERGE` requires a `JOIN` condition between the `USING` clause and the target table, we need a way to specify how dbt determines whether or not a record triggers a match or not. That particular piece of information is specified in the dbt model configuration. - -We can add the following `config()` block to the top of our model to specify how it should build incrementally: - -```sql -{{ - config( - materialized='incremental', - unique_key='ride_id', - incremental_strategy='merge' - ) -}} -``` - -The three configuration fields in this example are the most important ones. - -- Setting `materialized='incremental'` tells dbt to apply UPSERT logic to the target table. -- The `unique_key` should be a primary key of the target table. This is used to match records with the existing table. -- `incremental_strategy` here is set to MERGE any existing rows in the target table with a value for the `unique_key` which matches the incoming batch of data. There are [various incremental strategies](/docs/build/incremental-models#about-incremental_strategy) for different situations and warehouses. - -The bulk of the work in converting a model to an incremental materialization comes in determining how the logic should change for incremental loads versus full backfills or initial loads. dbt offers a special macro, `is_incremental()`, which evaluates false for initial loads or for backfills (called full refreshes in dbt parlance), but true for incremental loads. - -This macro can be used to augment the model code to adjust how data is loaded for subsequent loads. How that logic should be added will depend a little bit on how data is received. Some common ways might be: - -1. The source table is truncated ahead of incremental loads, and only contains the data to be loaded in that increment. -2. The source table contains all historical data, and there is a load timestamp column that identifies new data to be loaded. - -In the first case, the work is essentially done already. Since the source table always contains only the new data to be loaded, the query doesn’t have to change for incremental loads. The second case, however, requires the use of the `is_incremental()` macro to correctly handle the logic. - -Taking the converted `MERGE` statement that we’d put together previously, we’d augment it to add this additional logic: - -```sql -WITH - -using_clause AS ( - - SELECT - ride_id, - subtotal, - tip, - max(load_timestamp) as load_timestamp - - FROM {{ ref('rides_to_load') }} - - - {% if is_incremental() %} - - WHERE load_timestamp > (SELECT max(load_timestamp) FROM {{ this }}) - - {% endif %} - -), - -updates AS ( - - SELECT - ride_id, - subtotal, - tip, - load_timestamp - - FROM using_clause - - {% if is_incremental() %} - - WHERE ride_id IN (SELECT ride_id FROM {{ this }}) - - {% endif %} - -), - -inserts AS ( - - SELECT - ride_id, - subtotal, - NVL(tip, 0, tip), - load_timestamp - - FROM using_clause - - WHERE ride_id NOT IN (SELECT ride_id FROM updates) - -) - -SELECT * FROM updates UNION inserts -``` - -There are a couple important concepts to understand here: - -1. The code in the `is_incremental()` conditional block only executes for incremental executions of this model code. If the target table doesn’t exist, or if the `--full-refresh` option is used, that code will not execute. -2. `{{ this }}` is a special keyword in dbt that when used in a Jinja block, self-refers to the model for which the code is executing. So if you have a model in a file called `my_incremental_model.sql`, `{{ this }}` will refer to `my_incremental_model` (fully qualified with database and schema name if necessary). By using that keyword, we can leverage the current state of the target table to inform the source query. diff --git a/website/docs/guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion.md b/website/docs/guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion.md deleted file mode 100644 index 6fddf15c163..00000000000 --- a/website/docs/guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Putting it all together -id: 6-migrating-from-stored-procedures-conclusion ---- - -The techniques shared above are useful ways to get started converting the individual DML statements that are often found in stored procedures. Using these types of patterns, legacy procedural code can be rapidly transitioned to dbt models that are much more readable, maintainable, and benefit from software engineering best practices like DRY principles. Additionally, once transformations are rewritten as dbt models, it becomes much easier to test the transformations to ensure that the data being used downstream is high-quality and trustworthy. diff --git a/website/docs/guides/migration/tools/refactoring-legacy-sql.md b/website/docs/guides/refactoring-legacy-sql.md similarity index 95% rename from website/docs/guides/migration/tools/refactoring-legacy-sql.md rename to website/docs/guides/refactoring-legacy-sql.md index 0c03942889c..2c1bf0ead03 100644 --- a/website/docs/guides/migration/tools/refactoring-legacy-sql.md +++ b/website/docs/guides/refactoring-legacy-sql.md @@ -2,15 +2,24 @@ title: Refactoring legacy SQL to dbt id: refactoring-legacy-sql description: This guide walks through refactoring a long SQL query (perhaps from a stored procedure) into modular dbt data models. +displayText: Creating new materializations +hoverSnippet: Learn how to refactoring a long SQL query into modular dbt data models. +time_to_complete: '30 minutes' +platform: 'dbt-cloud' +icon: 'guides' +hide_table_of_contents: true +tags: ['SQL', 'legacy'] +level: 'Advanced' +recently_updated: true --- -You may have already learned how to build dbt models from scratch. +## Introduction -But in reality, you probably already have some queries or stored procedures that power analyses and dashboards, and now you’re wondering how to port those into dbt. +You may have already learned how to build dbt models from scratch. But in reality, you probably already have some queries or stored procedures that power analyses and dashboards, and now you’re wondering how to port those into dbt. There are two parts to accomplish this: migration and refactoring. In this guide we’re going to learn a process to help us turn legacy SQL code into modular dbt models. -When migrating and refactoring code, it’s of course important to stay organized. We'll do this by following several steps (jump directly from the right sidebar): +When migrating and refactoring code, it’s of course important to stay organized. We'll do this by following several steps: 1. Migrate your code 1:1 into dbt 2. Implement dbt sources rather than referencing raw database tables @@ -21,9 +30,10 @@ When migrating and refactoring code, it’s of course important to stay organize Let's get into it! -:::info More resources. +:::info More resources This guide is excerpted from the new dbt Learn On-demand Course, "Refactoring SQL for Modularity" - if you're curious, pick up the [free refactoring course here](https://courses.getdbt.com/courses/refactoring-sql-for-modularity), which includes example and practice refactoring projects. Or for a more in-depth look at migrating DDL and DML from stored procedures check out [this guide](/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures). ::: + ## Migrate your existing SQL code @@ -243,7 +253,7 @@ Under the hood, it generates comparison queries between our before and after sta Sure, we could write our own query manually to audit these models, but using the dbt `audit_helper` package gives us a head start and allows us to identify variances more quickly. -## Ready for refactoring practice? +### Ready for refactoring practice? Head to the free on-demand course, [Refactoring from Procedural SQL to dbt](https://courses.getdbt.com/courses/refactoring-sql-for-modularity) for a more in-depth refactoring example + a practice refactoring problem to test your skills. Questions on this guide or the course? Drop a note in #learn-on-demand in [dbt Community Slack](https://getdbt.com/community). diff --git a/website/docs/guides/migration/sl-migration.md b/website/docs/guides/sl-migration.md similarity index 93% rename from website/docs/guides/migration/sl-migration.md rename to website/docs/guides/sl-migration.md index 56cd6dc9d80..865785133e1 100644 --- a/website/docs/guides/migration/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -1,14 +1,23 @@ --- title: "Legacy dbt Semantic Layer migration guide" +id: "sl-migration" sidebar_label: "Legacy dbt Semantic Layer migration" description: "Learn how to migrate from the legacy dbt Semantic Layer to the latest one." +hoverSnippet: Learn how to migrate from the legacy dbt Semantic Layer to the latest one. +time_to_complete: '30 minutes' +icon: 'guides' +hide_table_of_contents: true tags: [Semantic Layer] +level: 'Intermediate' +recently_updated: true --- +## introduction + The legacy Semantic Layer will be deprecated in H2 2023. Additionally, the `dbt_metrics` package will not be supported in dbt v1.6 and later. If you are using `dbt_metrics`, you'll need to upgrade your configurations before upgrading to v1.6. This guide is for people who have the legacy dbt Semantic Layer setup and would like to migrate to the new dbt Semantic Layer. The estimated migration time is two weeks. -## Step 1: Migrate metric configs to the new spec +## Migrate metric configs to the new spec The metrics specification in dbt Core is changed in v1.6 to support the integration of MetricFlow. It's strongly recommended that you refer to [Build your metrics](/docs/build/build-metrics-intro) and before getting started so you understand the core concepts of the Semantic Layer. @@ -35,7 +44,7 @@ dbt Labs recommends completing these steps in a local dev environment (such as t **To make this process easier, dbt Labs provides a [custom migration tool](https://github.com/dbt-labs/dbt-converter) that automates these steps for you. You can find installation instructions in the [README](https://github.com/dbt-labs/dbt-converter/blob/master/README.md). Derived metrics aren’t supported in the migration tool, and will have to be migrated manually.* -## Step 2: Audit metric values after the migration +## Audit metric values after the migration You might need to audit metric values during the migration to ensure that the historical values of key business metrics are the same. @@ -58,7 +67,7 @@ You might need to audit metric values during the migration to ensure that the hi 1. Run the [dbt-audit](https://github.com/dbt-labs/dbt-audit-helper) helper on both models to compare the metric values. -## Step 3: Setup the Semantic Layer in a new environment +## Setup the Semantic Layer in a new environment This step is only relevant to users who want the legacy and new semantic layer to run in parallel for a short time. This will let you recreate content in downstream tools like Hex and Mode with minimal downtime. If you do not need to recreate assets in these tools skip to step 5. @@ -79,7 +88,7 @@ This step is only relevant to users who want the legacy and new semantic layer t At this point, both the new semantic layer and the old semantic layer will be running. The new semantic layer will be pointing at your migration branch with the updated metrics definitions. -## Step 4: Update connection in downstream integrations +## Update connection in downstream integrations Now that your Semantic Layer is set up, you will need to update any downstream integrations that used the legacy Semantic Layer. @@ -105,7 +114,7 @@ To learn more about integrating with Hex, check out their [documentation](https: 3. For specific SQL syntax details, refer to [Querying the API for metric metadata](/docs/dbt-cloud-apis/sl-jdbc#querying-the-api-for-metric-metadata) to query metrics using the API. -## Step 5: Merge your metrics migration branch to main, and upgrade your production environment to 1.6. +## Merge your metrics migration branch to main, and upgrade your production environment to 1.6. 1. Upgrade your production environment to 1.6 or higher. * **Note** — The old metrics definitions are no longer valid so your dbt jobs will not pass. @@ -118,7 +127,7 @@ If you created a new environment in [Step 3](#step-3-setup-the-semantic-layer-in 4. Delete your migration environment. Be sure to update your connection details in any downstream tools to account for the environment change. -## Related docs +### Related docs - [MetricFlow quickstart guide](/docs/build/sl-getting-started) - [Example dbt project](https://github.com/dbt-labs/jaffle-sl-template) From 1b664b19bd0fc9a937e2350d498d85224f57fe12 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:09:15 -0700 Subject: [PATCH 052/152] fixing links --- website/docs/docs/build/metrics.md | 4 ++-- website/docs/docs/build/sl-getting-started.md | 2 +- .../docs/dbt-versions/core-upgrade/01-upgrading-to-v1.6.md | 2 +- .../docs/docs/dbt-versions/release-notes/03-Oct-2023/sl-ga.md | 2 +- .../dbt-versions/release-notes/05-Aug-2023/sl-revamp-beta.md | 4 ++-- website/docs/docs/use-dbt-semantic-layer/dbt-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/setup-sl.md | 4 ++-- website/docs/docs/use-dbt-semantic-layer/sl-architecture.md | 2 +- website/docs/terms/cte.md | 4 ++-- website/docs/terms/dag.md | 2 +- website/snippets/_legacy-sl-callout.md | 2 +- website/snippets/_new-sl-setup.md | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/website/docs/docs/build/metrics.md b/website/docs/docs/build/metrics.md index 7a505fdad14..b75c4bfb502 100644 --- a/website/docs/docs/build/metrics.md +++ b/website/docs/docs/build/metrics.md @@ -11,7 +11,7 @@ tags: [Metrics] The dbt_metrics package has been deprecated and replaced with [MetricFlow](/docs/build/about-metricflow?version=1.6). If you're using the dbt_metrics package or the legacy Semantic Layer (available on v1.5 or lower), we **highly** recommend [upgrading your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to access MetricFlow and the new [dbt Semantic Layer](/docs/use-dbt-semantic-layer/dbt-sl?version=1.6). -To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/sl-migration) for more info. ::: @@ -26,7 +26,7 @@ The dbt_metrics package has been [deprecated](https://docs.getdbt.com/blog/depre Anyone who uses the dbt_metrics package or is integrated with the legacy Semantic Layer. The new Semantic Layer is available to [Team or Enterprise](https://www.getdbt.com/pricing/) multi-tenant dbt Cloud plans [hosted in North America](/docs/cloud/about-cloud/regions-ip-addresses). You must be on dbt v1.6 or higher to access it. All users can define metrics using MetricFlow. Users on dbt Cloud Developer plans or dbt Core can only use it to define and test metrics locally, but can't dynamically query them with integrated tools. **What should you do?**

-If you've defined metrics using dbt_metrics or integrated with the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use MetricFlow or the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +If you've defined metrics using dbt_metrics or integrated with the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use MetricFlow or the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/sl-migration) for more info. diff --git a/website/docs/docs/build/sl-getting-started.md b/website/docs/docs/build/sl-getting-started.md index 64cec11c302..1df12e86522 100644 --- a/website/docs/docs/build/sl-getting-started.md +++ b/website/docs/docs/build/sl-getting-started.md @@ -77,7 +77,7 @@ If you're encountering some issues when defining your metrics or setting up the
How do I migrate from the legacy Semantic Layer to the new one?
-
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
+
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
diff --git a/website/docs/docs/dbt-versions/core-upgrade/01-upgrading-to-v1.6.md b/website/docs/docs/dbt-versions/core-upgrade/01-upgrading-to-v1.6.md index db54073fd9f..4b2ff58d319 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/01-upgrading-to-v1.6.md +++ b/website/docs/docs/dbt-versions/core-upgrade/01-upgrading-to-v1.6.md @@ -36,7 +36,7 @@ The [spec for metrics](https://github.com/dbt-labs/dbt-core/discussions/7456) ha If your dbt project defines metrics, you must migrate to dbt v1.6 because the YAML spec has moved from dbt_metrics to MetricFlow. Any tests you have won't compile on v1.5 or older. - dbt Core v1.6 does not support Python 3.7, which reached End Of Life on June 23. Support Python versions are 3.8, 3.9, 3.10, and 3.11. -- As part of the [dbt Semantic layer](/docs/use-dbt-semantic-layer/dbt-sl) re-launch (in beta), the spec for `metrics` has changed significantly. Refer to the [migration guide](/guides/migration/sl-migration) for more info on how to migrate to the re-launched dbt Semantic Layer. +- As part of the [dbt Semantic layer](/docs/use-dbt-semantic-layer/dbt-sl) re-launch (in beta), the spec for `metrics` has changed significantly. Refer to the [migration guide](/guides/sl-migration) for more info on how to migrate to the re-launched dbt Semantic Layer. - The manifest schema version is now v10. - dbt Labs is ending support for Homebrew installation of dbt-core and adapters. See [the discussion](https://github.com/dbt-labs/dbt-core/discussions/8277) for more details. diff --git a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/sl-ga.md b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/sl-ga.md index 5e53363f62a..e427a85a346 100644 --- a/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/sl-ga.md +++ b/website/docs/docs/dbt-versions/release-notes/03-Oct-2023/sl-ga.md @@ -8,7 +8,7 @@ tags: [Oct-2023] --- :::important -If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher and [migrate](/guides/migration/sl-migration) to the latest Semantic Layer. +If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher and [migrate](/guides/sl-migration) to the latest Semantic Layer. ::: dbt Labs is thrilled to announce that the [dbt Semantic Layer](/docs/use-dbt-semantic-layer/dbt-sl) is now generally available. It offers consistent data organization, improved governance, reduced costs, enhanced efficiency, and accessible data for better decision-making and collaboration across organizations. diff --git a/website/docs/docs/dbt-versions/release-notes/05-Aug-2023/sl-revamp-beta.md b/website/docs/docs/dbt-versions/release-notes/05-Aug-2023/sl-revamp-beta.md index 921ed6dcd79..f44fd57aa4a 100644 --- a/website/docs/docs/dbt-versions/release-notes/05-Aug-2023/sl-revamp-beta.md +++ b/website/docs/docs/dbt-versions/release-notes/05-Aug-2023/sl-revamp-beta.md @@ -8,14 +8,14 @@ sidebar_position: 7 --- :::important -If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/sl-migration) for more info. ::: dbt Labs are thrilled to announce the re-release of the [dbt Semantic Layer](/docs/use-dbt-semantic-layer/dbt-sl), now available in [public beta](#public-beta). It aims to bring the best of modeling and semantics to downstream applications by introducing: - [MetricFlow](/docs/build/about-metricflow) is a framework for constructing performant and legible SQL from an all new set of semantic constructs which include semantic models, entities, and metrics. - New Semantic Layer infrastructure that enables support for more data platforms (Snowflake, Databricks, BigQuery, Redshift, and soon more), along with improved performance. -- New and improved [developer workflows](/guides/migration/sl-migration), governance, and collaboration features. +- New and improved [developer workflows](/guides/sl-migration), governance, and collaboration features. - New [Semantic Layer API](/docs/dbt-cloud-apis/sl-api-overview) using JDBC to query metrics and build integrations. With semantics at its core, the dbt Semantic Layer marks a crucial milestone towards a new era of centralized logic and data applications. diff --git a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md index b9728fc108e..4e3caa3eb21 100644 --- a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md @@ -130,7 +130,7 @@ You can design and define your metrics in `.yml` files nested under a metrics ke
How do I migrate from the legacy Semantic Layer to the new one?
-
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
+
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
diff --git a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md index cf9217146b5..b521eaf713a 100644 --- a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md @@ -91,7 +91,7 @@ If you're encountering some issues when defining your metrics or setting up the
How do I migrate from the legacy Semantic Layer to the new one?
-
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
+
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
diff --git a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md index 4c88ee50b25..5f793142bdc 100644 --- a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md @@ -53,7 +53,7 @@ With the dbt Semantic Layer, you can define business metrics, reduce code duplic ## Set up dbt Semantic Layer :::tip -If you're using the legacy Semantic Layer, dbt Labs strongly recommends that you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the latest dbt Semantic Layer. Refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +If you're using the legacy Semantic Layer, dbt Labs strongly recommends that you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the latest dbt Semantic Layer. Refer to the dedicated [migration guide](/guides/sl-migration) for more info. ::: @@ -95,5 +95,5 @@ It is _not_ recommended that you use your dbt Cloud credentials due to elevated - [Build your metrics](/docs/build/build-metrics-intro) - [Available integrations](/docs/use-dbt-semantic-layer/avail-sl-integrations) - [Semantic Layer APIs](/docs/dbt-cloud-apis/sl-api-overview) -- [Migrate your legacy Semantic Layer](/guides/migration/sl-migration) +- [Migrate your legacy Semantic Layer](/guides/sl-migration) - [Get started with the dbt Semantic Layer](/docs/use-dbt-semantic-layer/quickstart-sl) diff --git a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md index 152821b7e59..60fb460e53b 100644 --- a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md +++ b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md @@ -32,7 +32,7 @@ The dbt Semantic Layer includes the following components:
How do I migrate from the legacy Semantic Layer to the new one?
-
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
+
If you're using the legacy Semantic Layer, we highly recommend you upgrade your dbt version to dbt v1.6 or higher to use the new dbt Semantic Layer. Refer to the dedicated migration guide for more info.
diff --git a/website/docs/terms/cte.md b/website/docs/terms/cte.md index d4a4bb15915..f67480325b4 100644 --- a/website/docs/terms/cte.md +++ b/website/docs/terms/cte.md @@ -66,7 +66,7 @@ When people talk about how CTEs can simplify your queries, they specifically mea #### Establish Structure -In leveraging CTEs, you can break complex code into smaller segments, ultimately helping provide structure to your code. At dbt Labs, we often like to use the [import, logical, and final structure](/guides/migration/tools/refactoring-legacy-sql#implement-cte-groupings) for CTEs which creates a predictable and organized structure to your dbt models. +In leveraging CTEs, you can break complex code into smaller segments, ultimately helping provide structure to your code. At dbt Labs, we often like to use the [import, logical, and final structure](/guides/refactoring-legacy-sql?step=5#implement-cte-groupings) for CTEs which creates a predictable and organized structure to your dbt models. #### Easily identify dependencies @@ -181,7 +181,7 @@ CTEs are essentially temporary views that can be used throughout a query. They a If you’re interested in reading more about CTE best practices, check out some of our favorite content around model refactoring and style: -- [Refactoring Legacy SQL to dbt](/guides/migration/tools/refactoring-legacy-sql#implement-cte-groupings) +- [Refactoring Legacy SQL to dbt](/guides/refactoring-legacy-sql?step=5#implement-cte-groupings) - [dbt Labs Style Guide](https://github.com/dbt-labs/corp/blob/main/dbt_style_guide.md#ctes) - [Modular Data Modeling Technique](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) diff --git a/website/docs/terms/dag.md b/website/docs/terms/dag.md index d7ff1c44f49..c6b91300bfc 100644 --- a/website/docs/terms/dag.md +++ b/website/docs/terms/dag.md @@ -108,4 +108,4 @@ Ready to restructure (or create your first) DAG? Check out some of the resources - [Data modeling techniques for more modularity](https://www.getdbt.com/analytics-engineering/modular-data-modeling-technique/) - [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) - [How to audit your DAG](https://www.youtube.com/watch?v=5W6VrnHVkCA) -- [Refactoring legacy SQL to dbt](/guides/migration/tools/refactoring-legacy-sql) +- [Refactoring legacy SQL to dbt](/guides/refactoring-legacy-sql) diff --git a/website/snippets/_legacy-sl-callout.md b/website/snippets/_legacy-sl-callout.md index f45c6b68af3..97c95512332 100644 --- a/website/snippets/_legacy-sl-callout.md +++ b/website/snippets/_legacy-sl-callout.md @@ -6,6 +6,6 @@ The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.c **What’s changed?** The dbt_metrics package has been [deprecated](https://docs.getdbt.com/blog/deprecating-dbt-metrics) and replaced with [MetricFlow](/docs/build/about-metricflow?version=1.6), a new framework for defining metrics in dbt. This means dbt_metrics is no longer supported after dbt v1.5 and won't receive any code fixes. -**What should you do?** If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +**What should you do?** If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/sl-migration) for more info. ::: diff --git a/website/snippets/_new-sl-setup.md b/website/snippets/_new-sl-setup.md index ad248bc3ca9..3cb6e09eb4c 100644 --- a/website/snippets/_new-sl-setup.md +++ b/website/snippets/_new-sl-setup.md @@ -7,7 +7,7 @@ You can set up the dbt Semantic Layer in dbt Cloud at the environment and projec - You must have a successful run in your new environment. :::tip -If you're using the legacy Semantic Layer, dbt Labs strongly recommends that you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt version 1.6 or newer to use the latest dbt Semantic Layer. Refer to the dedicated [migration guide](/guides/migration/sl-migration) for details. +If you're using the legacy Semantic Layer, dbt Labs strongly recommends that you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt version 1.6 or newer to use the latest dbt Semantic Layer. Refer to the dedicated [migration guide](/guides/sl-migration) for details. ::: 1. In dbt Cloud, create a new [deployment environment](/docs/deploy/deploy-environments#create-a-deployment-environment) or use an existing environment on dbt 1.6 or higher. From c8e1cb10310ea47c00e85732331d7dff6ec01058 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:42:33 -0700 Subject: [PATCH 053/152] moving more guides --- .../dbt-unity-catalog-best-practices.md | 0 website/docs/guides/airflow-and-dbt-cloud.md | 3 +- website/docs/guides/building-packages.md | 3 +- .../guides/creating-new-materializations.md | 7 - ...b-on-merge.md => custom-cicd-pipelines.md} | 228 +++++++++-- ...abricks.md => dbt models on Databricks.md} | 21 +- website/docs/guides/debugging-schema-names.md | 2 +- ...w-to-set-up-your-databricks-dbt-project.md | 25 +- ...abricks-workflows-to-run-dbt-cloud-jobs.md | 17 +- .../migrating-from-spark-to-databricks.md | 2 +- .../migrating-from-stored-procedures.md | 2 +- .../1-cicd-background.md | 43 --- .../4-dbt-cloud-job-on-pr.md | 131 ------- .../5-something-to-consider.md | 8 - .../orchestration/set-up-ci/1-introduction.md | 10 - .../orchestration/set-up-ci/2-quick-setup.md | 50 --- .../set-up-ci/3-run-dbt-project-evaluator.md | 46 --- .../orchestration/set-up-ci/4-lint-on-push.md | 190 ---------- .../set-up-ci/5-multiple-checks.md | 62 --- ...uctionizing-your-dbt-databricks-project.md | 23 +- website/docs/guides/set-up-ci.md | 355 ++++++++++++++++++ website/docs/guides/sl-migration.md | 2 +- 22 files changed, 628 insertions(+), 602 deletions(-) rename website/docs/{guides/dbt-ecosystem/databricks-guides => best-practices}/dbt-unity-catalog-best-practices.md (100%) rename website/docs/guides/{orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge.md => custom-cicd-pipelines.md} (58%) rename website/docs/guides/{dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md => dbt models on Databricks.md} (97%) rename website/docs/guides/{dbt-ecosystem/databricks-guides => }/how-to-set-up-your-databricks-dbt-project.md (90%) rename website/docs/guides/{orchestration => }/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md (96%) delete mode 100644 website/docs/guides/orchestration/custom-cicd-pipelines/1-cicd-background.md delete mode 100644 website/docs/guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr.md delete mode 100644 website/docs/guides/orchestration/custom-cicd-pipelines/5-something-to-consider.md delete mode 100644 website/docs/guides/orchestration/set-up-ci/1-introduction.md delete mode 100644 website/docs/guides/orchestration/set-up-ci/2-quick-setup.md delete mode 100644 website/docs/guides/orchestration/set-up-ci/3-run-dbt-project-evaluator.md delete mode 100644 website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md delete mode 100644 website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md rename website/docs/guides/{dbt-ecosystem/databricks-guides => }/productionizing-your-dbt-databricks-project.md (97%) create mode 100644 website/docs/guides/set-up-ci.md diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md b/website/docs/best-practices/dbt-unity-catalog-best-practices.md similarity index 100% rename from website/docs/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices.md rename to website/docs/best-practices/dbt-unity-catalog-best-practices.md diff --git a/website/docs/guides/airflow-and-dbt-cloud.md b/website/docs/guides/airflow-and-dbt-cloud.md index 329fe729038..98eb9f82d6c 100644 --- a/website/docs/guides/airflow-and-dbt-cloud.md +++ b/website/docs/guides/airflow-and-dbt-cloud.md @@ -2,10 +2,9 @@ title: Airflow and dbt Cloud id: airflow-and-dbt-cloud time_to_complete: '60 minutes' -platform: 'dbt-cloud' icon: 'guides' hide_table_of_contents: true -tags: ['airflow', 'dbt Cloud', 'orchestration'] +tags: ['dbt Cloud', 'Orchestration'] level: 'Intermediate' recently_updated: true --- diff --git a/website/docs/guides/building-packages.md b/website/docs/guides/building-packages.md index 167eed47137..9d26d7c85e4 100644 --- a/website/docs/guides/building-packages.md +++ b/website/docs/guides/building-packages.md @@ -5,10 +5,9 @@ description: When you have dbt code that might help others, you can create a pac displayText: Building dbt packages hoverSnippet: Learn how to create packages for dbt. time_to_complete: '60 minutes' -platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['packages', 'dbt Core', 'legacy'] +tags: ['dbt Core', 'legacy'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/creating-new-materializations.md b/website/docs/guides/creating-new-materializations.md index 963141dc335..12bdd0685b6 100644 --- a/website/docs/guides/creating-new-materializations.md +++ b/website/docs/guides/creating-new-materializations.md @@ -172,13 +172,6 @@ For more information on the `config` dbt Jinja function, see the [config](/refer ## Materialization precedence - -:::info New in 0.15.1 - -The materialization resolution order was poorly defined in versions of dbt prior to 0.15.1. Please use this guide for versions of dbt greater than or equal to 0.15.1. - -::: - dbt will pick the materialization macro in the following order (lower takes priority): 1. global project - default diff --git a/website/docs/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge.md b/website/docs/guides/custom-cicd-pipelines.md similarity index 58% rename from website/docs/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge.md rename to website/docs/guides/custom-cicd-pipelines.md index d22d1d14284..094a30eed31 100644 --- a/website/docs/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge.md +++ b/website/docs/guides/custom-cicd-pipelines.md @@ -1,13 +1,64 @@ --- -title: Run a dbt Cloud job on merge -id: 3-dbt-cloud-job-on-merge +title: Customizing CI/CD with Custom Pipelines +id: custom-cicd-pipelines +description: "Learn the benefits of version-controlled analytics code and custom pipelines in dbt for enhanced code testing and workflow automation during the development process." +displayText: Learn version-controlled code, custom pipelines, and enhanced code testing. +hoverSnippet: Learn version-controlled code, custom pipelines, and enhanced code testing. +time_to_complete: '60 minutes' +icon: 'guides' +hide_table_of_contents: true +tags: ['dbt Cloud', 'Orchestration'] +level: 'Intermediate' +recently_updated: true --- +## Introduction + +One of the core tenets of dbt is that analytic code should be version controlled. This provides a ton of benefit to your organization in terms of collaboration, code consistency, stability, and the ability to roll back to a prior version. There’s an additional benefit that is provided with your code hosting platform that is often overlooked or underutilized. Some of you may have experience using dbt Cloud’s [webhook functionality](https://docs.getdbt.com/docs/dbt-cloud/using-dbt-cloud/cloud-enabling-continuous-integration) to run a job when a PR is created. This is a fantastic capability, and meets most use cases for testing your code before merging to production. However, there are circumstances when an organization needs additional functionality, like running workflows on every commit (linting), or running workflows after a merge is complete. In this article, we will show you how to setup custom pipelines to lint your project and trigger a dbt Cloud job via the API. + +A note on parlance in this article since each code hosting platform uses different terms for similar concepts. The terms `pull request` (PR) and `merge request` (MR) are used interchangeably to mean the process of merging one branch into another branch. + + +### What are pipelines? + +Pipelines (which are known by many names, such as workflows, actions, or build steps) are a series of pre-defined jobs that are triggered by specific events in your repository (PR created, commit pushed, branch merged, etc). Those jobs can do pretty much anything your heart desires assuming you have the proper security access and coding chops. + +Jobs are executed on [runners](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#runners), which are virtual servers. The runners come pre-configured with Ubuntu Linux, macOS, or Windows. That means the commands you execute are determined by the operating system of your runner. You’ll see how this comes into play later in the setup, but for now just remember that your code is executed on virtual servers that are, typically, hosted by the code hosting platform. + +![Diagram of how pipelines work](/img/guides/orchestration/custom-cicd-pipelines/pipeline-diagram.png) + +Please note, runners hosted by your code hosting platform provide a certain amount of free time. After that, billing charges may apply depending on how your account is setup. You also have the ability to host your own runners. That is beyond the scope of this article, but checkout the links below for more information if you’re interested in setting that up: + +- Repo-hosted runner billing information: + - [GitHub](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions) + - [GitLab](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html) + - [Bitbucket](https://bitbucket.org/product/features/pipelines#) +- Self-hosted runner information: + - [GitHub](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) + - [GitLab](https://docs.gitlab.com/runner/) + - [Bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/runners/) + +Additionally, if you’re using the free tier of GitLab you can still follow this guide, but it may ask you to provide a credit card to verify your account. You’ll see something like this the first time you try to run a pipeline: + +![Warning from GitLab showing payment information is required](/img/guides/orchestration/custom-cicd-pipelines/gitlab-cicd-payment-warning.png) + + +### How to setup pipelines + +This guide provides details for multiple code hosting platforms. Where steps are unique, they are presented without a selection option. If code is specific to a platform (i.e. GitHub, GitLab, Bitbucket) you will see a selection option for each. + +Pipelines can be triggered by various events. The [dbt Cloud webhook](https://docs.getdbt.com/docs/dbt-cloud/using-dbt-cloud/cloud-enabling-continuous-integration) process already triggers a run if you want to run your jobs on a merge request, so this guide focuses on running pipelines for every push and when PRs are merged. Since pushes happen frequently in a project, we’ll keep this job super simple and fast by linting with SQLFluff. The pipeline that runs on merge requests will run less frequently, and can be used to call the dbt Cloud API to trigger a specific job. This can be helpful if you have specific requirements that need to happen when code is updated in production, like running a `--full-refresh` on all impacted incremental models. + +Here’s a quick look at what this pipeline will accomplish: + +![Diagram showing the pipelines to be created and the programs involved](/img/guides/orchestration/custom-cicd-pipelines/pipeline-programs-diagram.png) + +## Run a dbt Cloud job on merge + This job will take a bit more to setup, but is a good example of how to call the dbt Cloud API from a CI/CD pipeline. The concepts presented here can be generalized and used in whatever way best suits your use case. The setup below shows how to call the dbt Cloud API to run a job every time there's a push to your main branch (The branch where pull requests are typically merged. Commonly referred to as the main, primary, or master branch, but can be named differently). - ### 1. Get your dbt Cloud API key When running a CI/CD pipeline you’ll want to use a service token instead of any individual’s API key. There are [detailed docs](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens) available on this, but below is a quick rundown (this must be performed by an Account Admin): @@ -28,7 +79,7 @@ Here’s a video showing the steps as well: ### 2. Put your dbt Cloud API key into your repo -This next part will happen in you code hosting platform. We need to save your API key from above into a repository secret so the job we create can access it. It is **not** recommended to ever save passwords or API keys in your code, so this step ensures that your key stays secure, but is still usable for your pipelines. +This next part will happen in you code hosting platform. We need to save your API key from above into a repository secret so the job we create can access it. It is **not** recommended to ever save passwords or API keys in your code, so this step ensures that your key stays secure, but is still usable for your pipelines. *CI/CD* - Under the *Variables* section, click *Expand,* then click *Add variable* - It will ask you for a name, so let’s call ours `DBT_API_KEY` - - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** + - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** - In the *Value* section, paste in the key you copied from dbt Cloud - Make sure the check box next to *Protect variable* is unchecked, and the box next to *Mask variable* is selected (see below) - - “Protected” means that the variable is only available in pipelines that run on protected branches or protected tags - that won’t work for us because we want to run this pipeline on multiple branches. “Masked” means that it will be available to your pipeline runner, but will be masked in the logs. - + - “Protected” means that the variable is only available in pipelines that run on protected branches or protected tags - that won’t work for us because we want to run this pipeline on multiple branches. “Masked” means that it will be available to your pipeline runner, but will be masked in the logs. + ![View of the GitLab window for entering DBT_API_KEY](/img/guides/orchestration/custom-cicd-pipelines/dbt-api-key-gitlab.png) - + Here’s a video showing these steps: - + - + @@ -91,7 +142,7 @@ In Azure: - Select *Starter pipeline* (this will be updated later in Step 4) - Click on *Variables* and then *New variable* - In the *Name* field, enter the `DBT_API_KEY` - - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** + - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** - In the *Value* section, paste in the key you copied from dbt Cloud - Make sure the check box next to *Keep this value secret* is checked. This will mask the value in logs, and you won't be able to see the value for the variable in the UI. - Click *OK* and then *Save* to save the variable @@ -99,7 +150,7 @@ In Azure: - + In Bitbucket: @@ -108,16 +159,16 @@ In Bitbucket: - In the left menu, click *Repository Settings* - Scroll to the bottom of the left menu, and select *Repository variables* - In the *Name* field, input `DBT_API_KEY` - - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** + - **It’s very important that you copy/paste this name exactly because it’s used in the scripts below.** - In the *Value* section, paste in the key you copied from dbt Cloud - Make sure the check box next to *Secured* is checked. This will mask the value in logs, and you won't be able to see the value for the variable in the UI. - Click *Add* to save the variable - + ![View of the Bitbucket window for entering DBT_API_KEY](/img/guides/orchestration/custom-cicd-pipelines/dbt-api-key-bitbucket.png) - + Here’s a video showing these steps: - + @@ -304,13 +355,12 @@ run-dbt-cloud-job: - For this new job, open the existing Azure pipeline you created above and select the *Edit* button. We'll want to edit the corresponding Azure pipeline YAML file with the appropriate configuration, instead of the starter code, along with including a `variables` section to pass in the required variables. -Copy the below YAML file into your Azure pipeline and update the variables below to match your setup based on the comments in the file. It's worth noting that we changed the `trigger` section so that it will run **only** when there are pushes to a branch named `main` (like a PR merged to your main branch). +Copy the below YAML file into your Azure pipeline and update the variables below to match your setup based on the comments in the file. It's worth noting that we changed the `trigger` section so that it will run **only** when there are pushes to a branch named `main` (like a PR merged to your main branch). Read through [Azure's docs](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops) on these filters for additional use cases. @@ -406,13 +456,12 @@ pipelines: - ### 5. Test your new action -Now that you have a shiny new action, it’s time to test it out! Since this change is setup to only run on merges to your default branch, you’ll need to create and merge this change into your main branch. Once you do that, you’ll see a new pipeline job has been triggered to run the dbt Cloud job you assigned in the variables section. +Now that you have a shiny new action, it’s time to test it out! Since this change is setup to only run on merges to your default branch, you’ll need to create and merge this change into your main branch. Once you do that, you’ll see a new pipeline job has been triggered to run the dbt Cloud job you assigned in the variables section. Additionally, you’ll see the job in the run history of dbt Cloud. It should be fairly easy to spot because it will say it was triggered by the API, and the *INFO* section will have the branch you used for this guide. @@ -454,3 +503,140 @@ Additionally, you’ll see the job in the run history of dbt Cloud. It should be + +## Run a dbt Cloud job on pull request + +If your git provider is not one with a native integration with dbt Cloud, but you still want to take advantage of CI builds, you've come to the right spot! With just a bit of work it's possible to setup a job that will run a dbt Cloud job when a pull request (PR) is created. + +:::info Run on PR + +If your git provider has a native integration with dbt Cloud, you can take advantage of the setup instructions [here](/docs/deploy/ci-jobs). +This section is only for those projects that connect to their git repository using an SSH key. + +::: + +The setup for this pipeline will use the same steps as the prior page. Before moving on, **follow steps 1-5 from the [prior page](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge)** + +### 1. Create a pipeline job that runs when PRs are created + + + + +For this job, we'll set it up using the `bitbucket-pipelines.yml` file as in the prior step. The YAML file will look pretty similar to our earlier job, but we’ll pass in the required variables to the Python script using `export` statements. Update this section to match your setup based on the comments in the file. + +**What is this pipeline going to do?** +The setup below will trigger a dbt Cloud job to run every time a PR is opened in this repository. It will also run a fresh version of the pipeline for every commit that is made on the PR until it is merged. +For example: If you open a PR, it will run the pipeline. If you then decide additional changes are needed, and commit/push to the PR branch, a new pipeline will run with the updated code. + +The following varibles control this job: + +- `DBT_JOB_BRANCH`: Tells the dbt Cloud job to run the code in the branch that created this PR +- `DBT_JOB_SCHEMA_OVERRIDE`: Tells the dbt Cloud job to run this into a custom target schema + - The format of this will look like: `DBT_CLOUD_PR_{REPO_KEY}_{PR_NUMBER}` + +```yaml +image: python:3.11.1 + + +pipelines: + # This job will run when pull requests are created in the repository + pull-requests: + '**': + - step: + name: 'Run dbt Cloud PR Job' + script: + # Check to only build if PR destination is master (or other branch). + # Comment or remove line below if you want to run on all PR's regardless of destination branch. + - if [ "${BITBUCKET_PR_DESTINATION_BRANCH}" != "main" ]; then printf 'PR Destination is not master, exiting.'; exit; fi + - export DBT_URL="https://cloud.getdbt.com" + - export DBT_JOB_CAUSE="Bitbucket Pipeline CI Job" + - export DBT_JOB_BRANCH=$BITBUCKET_BRANCH + - export DBT_JOB_SCHEMA_OVERRIDE="DBT_CLOUD_PR_"$BITBUCKET_PROJECT_KEY"_"$BITBUCKET_PR_ID + - export DBT_ACCOUNT_ID=00000 # enter your account id here + - export DBT_PROJECT_ID=00000 # enter your project id here + - export DBT_PR_JOB_ID=00000 # enter your job id here + - python python/run_and_monitor_dbt_job.py +``` + + + + +### 2. Confirm the pipeline runs + +Now that you have a new pipeline, it's time to run it and make sure it works. Since this only triggers when a PR is created, you'll need to create a new PR on a branch that contains the code above. Once you do that, you should see a pipeline that looks like this: + + + + +Bitbucket pipeline: +![dbt run on PR job in Bitbucket](/img/guides/orchestration/custom-cicd-pipelines/bitbucket-run-on-pr.png) + +dbt Cloud job: +![dbt Cloud job showing it was triggered by Bitbucket](/img/guides/orchestration/custom-cicd-pipelines/bitbucket-dbt-cloud-pr.png) + + + + +### 3. Handle those extra schemas in your database + +As noted above, when the PR job runs it will create a new schema based on the PR. To avoid having your database overwhelmed with PR schemas, consider adding a "cleanup" job to your dbt Cloud account. This job can run on a scheduled basis to cleanup any PR schemas that haven't been updated/used recently. + +Add this as a macro to your project. It takes 2 arguments that lets you control which schema get dropped: + +- `age_in_days`: The number of days since the schema was last altered before it should be dropped (default 10 days) +- `database_to_clean`: The name of the database to remove schemas from + +```sql +{# + This macro finds PR schemas older than a set date and drops them + The macro defaults to 10 days old, but can be configured with the input argument age_in_days + Sample usage with different date: + dbt run-operation pr_schema_cleanup --args "{'database_to_clean': 'analytics','age_in_days':'15'}" +#} +{% macro pr_schema_cleanup(database_to_clean, age_in_days=10) %} + + {% set find_old_schemas %} + select + 'drop schema {{ database_to_clean }}.'||schema_name||';' + from {{ database_to_clean }}.information_schema.schemata + where + catalog_name = '{{ database_to_clean | upper }}' + and schema_name ilike 'DBT_CLOUD_PR%' + and last_altered <= (current_date() - interval '{{ age_in_days }} days') + {% endset %} + + {% if execute %} + + {{ log('Schema drop statements:' ,True) }} + + {% set schema_drop_list = run_query(find_old_schemas).columns[0].values() %} + + {% for schema_to_drop in schema_drop_list %} + {% do run_query(schema_to_drop) %} + {{ log(schema_to_drop ,True) }} + {% endfor %} + + {% endif %} + +{% endmacro %} +``` + +This macro goes into a dbt Cloud job that is run on a schedule. The command will look like this (text below for copy/paste): +![dbt Cloud job showing the run operation command for the cleanup macro](/img/guides/orchestration/custom-cicd-pipelines/dbt-macro-cleanup-pr.png) +`dbt run-operation pr_schema_cleanup --args "{ 'database_to_clean': 'development','age_in_days':15}"` + +## Consider risk of conflicts when using multiple orchestration tools + +Running dbt Cloud jobs through a CI/CD pipeline is a form of job orchestration. If you also run jobs using dbt Cloud’s built in scheduler, you now have 2 orchestration tools running jobs. The risk with this is that you could run into conflicts - you can imagine a case where you are triggering a pipeline on certain actions and running scheduled jobs in dbt Cloud, you would probably run into job clashes. The more tools you have, the more you have to make sure everything talks to each other. + +That being said, if **the only reason you want to use pipelines is for adding a lint check or run on merge**, you might decide the pros outweigh the cons, and as such you want to go with a hybrid approach. Just keep in mind that if two processes try and run the same job at the same time, dbt Cloud will queue the jobs and run one after the other. It’s a balancing act but can be accomplished with diligence to ensure you’re orchestrating jobs in a manner that does not conflict. diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md b/website/docs/guides/dbt models on Databricks.md similarity index 97% rename from website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md rename to website/docs/guides/dbt models on Databricks.md index cb7390fa799..5bb6de61e77 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks.md +++ b/website/docs/guides/dbt models on Databricks.md @@ -1,9 +1,18 @@ --- -title: How to optimize and troubleshoot dbt models on Databricks -sidebar_label: "How to optimize and troubleshoot dbt models on Databricks" +title: Optimize and troubleshoot dbt models on Databricks +sidebar_label: "Optimize and troubleshoot dbt models on Databricks" description: "Learn more about optimizing and troubleshooting your dbt models on Databricks" +displayText: Optimizing and troubleshooting your dbt models on Databricks +hoverSnippet: Learn how to optimize and troubleshoot your dbt models on Databricks. +time_to_complete: '30 minutes' +icon: 'databricks' +hide_table_of_contents: true +tags: ['Databricks', 'dbt Core','dbt Cloud'] +level: 'Intermediate' +recently_updated: true --- +## Introduction Continuing our Databricks and dbt guide series from the last [guide](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project), it’s time to talk about performance optimization. In this follow-up post,  we outline simple strategies to optimize for cost, performance, and simplicity when architecting your data pipelines. We’ve encapsulated these strategies in this acronym-framework: @@ -11,7 +20,7 @@ Continuing our Databricks and dbt guide series from the last [guide](/guides/dbt - Patterns & Best Practices - Performance Troubleshooting -## 1. Platform Components +## Platform Components As you start to develop your dbt projects, one of the first decisions you will make is what kind of backend infrastructure to run your models against. Databricks offers SQL warehouses, All-Purpose Compute, and Jobs Compute, each optimized to workloads they are catered to. Our recommendation is to use Databricks SQL warehouses for all your SQL workloads. SQL warehouses are optimized for SQL workloads when compared to other compute options, additionally, they can scale both vertically to support larger workloads and horizontally to support concurrency. Also, SQL warehouses are easier to manage and provide out-of-the-box features such as query history to help audit and optimize your SQL workloads. Between Serverless, Pro, and Classic SQL Warehouse types that Databricks offers, our standard recommendation for you is to leverage Databricks serverless warehouses. You can explore features of these warehouse types in the [Compare features section](https://www.databricks.com/product/pricing/databricks-sql?_gl=1*2rsmlo*_ga*ZmExYzgzZDAtMWU0Ny00N2YyLWFhYzEtM2RhZTQzNTAyZjZi*_ga_PQSEQ3RZQC*MTY3OTYwMDg0Ni4zNTAuMS4xNjc5NjAyMDMzLjUzLjAuMA..&_ga=2.104593536.1471430337.1679342371-fa1c83d0-1e47-47f2-aac1-3dae43502f6b) on the Databricks pricing page. @@ -31,7 +40,7 @@ Another technique worth implementing is to provision separate SQL warehouses for Because of the ability of serverless warehouses to spin up in a matter of seconds, setting your auto-stop configuration to a lower threshold will not impact SLAs and end-user experience. From the SQL Workspace UI, the default value is 10 minutes and  you can set it to 5 minutes for a lower threshold with the UI. If you would like more custom settings, you can set the threshold to as low as 1 minute with the [API](https://docs.databricks.com/sql/api/sql-endpoints.html#). -## 2. Patterns & Best Practices +## Patterns & Best Practices Now that we have a solid sense of the infrastructure components, we can shift our focus to best practices and design patterns on pipeline development.  We recommend the staging/intermediate/mart approach which is analogous to the medallion architecture bronze/silver/gold approach that’s recommended by Databricks. Let’s dissect each stage further. @@ -121,7 +130,7 @@ incremental_predicates = [ }} ``` -## 3. Performance Troubleshooting +## Performance Troubleshooting Performance troubleshooting refers to the process of identifying and resolving issues that impact the performance of your dbt models and overall data pipelines. By improving the speed and performance of your Lakehouse platform, you will be able to process data faster, process large and complex queries more effectively, and provide faster time to market.  Let’s go into detail the three effective strategies that you can implement. @@ -166,7 +175,7 @@ Now you might be wondering, how do you identify opportunities for performance im With the [dbt Cloud Admin API](/docs/dbt-cloud-apis/admin-cloud-api), you can  pull the dbt artifacts from your dbt Cloud run,  put the generated `manifest.json` into an S3 bucket, stage it, and model the data using the [dbt artifacts package](https://hub.getdbt.com/brooklyn-data/dbt_artifacts/latest/). That package can help you identify inefficiencies in your dbt models and pinpoint where opportunities for improvement are. -## Conclusion +### Conclusion This concludes the second guide in our series on “Working with Databricks and dbt”, following [How to set up your Databricks and dbt Project](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project). diff --git a/website/docs/guides/debugging-schema-names.md b/website/docs/guides/debugging-schema-names.md index 1778d25d0b4..fbd1ce0ae69 100644 --- a/website/docs/guides/debugging-schema-names.md +++ b/website/docs/guides/debugging-schema-names.md @@ -8,7 +8,7 @@ time_to_complete: '45 minutes' platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['schema names', 'dbt Core', 'legacy'] +tags: ['dbt Core', 'legacy'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md similarity index 90% rename from website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md rename to website/docs/guides/how-to-set-up-your-databricks-dbt-project.md index ad12bdca725..1ca1940cc50 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md @@ -1,5 +1,16 @@ -# How to set up your Databricks and dbt project - +--- +title: How to set up your Databricks and dbt project +sidebar_label: "How to set up your Databricks and dbt project" +description: "Learn more about setting up your dbt project with Databricks" +displayText: Setting up your dbt project with Databricks +hoverSnippet: Learn how to set up your dbt project with Databricks. +time_to_complete: '30 minutes' +icon: 'databricks' +hide_table_of_contents: true +tags: ['Databricks', 'dbt Core','dbt Cloud'] +level: 'Intermediate' +recently_updated: true +--- Databricks and dbt Labs are partnering to help data teams think like software engineering teams and ship trusted data, faster. The dbt-databricks adapter enables dbt users to leverage the latest Databricks features in their dbt project. Hundreds of customers are now using dbt and Databricks to build expressive and reliable data pipelines on the Lakehouse, generating data assets that enable analytics, ML, and AI use cases throughout the business. @@ -80,9 +91,9 @@ For your development credentials/profiles.yml: During your first invocation of `dbt run`, dbt will create the developer schema if it doesn't already exist in the dev catalog. -### Defining your dbt deployment environment +## Defining your dbt deployment environment -Last, we need to give dbt a way to deploy code outside of development environments. To do so, we’ll use dbt [environments](https://docs.getdbt.com/docs/collaborate/environments) to define the production targets that end users will interact with. +We need to give dbt a way to deploy code outside of development environments. To do so, we’ll use dbt [environments](https://docs.getdbt.com/docs/collaborate/environments) to define the production targets that end users will interact with. Core projects can use [targets in profiles](https://docs.getdbt.com/docs/core/connection-profiles#understanding-targets-in-profiles) to separate environments. [dbt Cloud environments](https://docs.getdbt.com/docs/cloud/develop-in-the-cloud#set-up-and-access-the-cloud-ide) allow you to define environments via the UI and [schedule jobs](/guides/databricks#create-and-run-a-job) for specific environments. @@ -94,10 +105,10 @@ Let’s set up our deployment environment: 4. Set the schema to the default for your prod environment. This can be overridden by [custom schemas](https://docs.getdbt.com/docs/build/custom-schemas#what-is-a-custom-schema) if you need to use more than one. 5. Provide your Service Principal token. -### Connect dbt to your git repository +## Connect dbt to your git repository Next, you’ll need somewhere to store and version control your code that allows you to collaborate with teammates. Connect your dbt project to a git repository with [dbt Cloud](/guides/databricks#set-up-a-dbt-cloud-managed-repository). [Core](/guides/manual-install#create-a-repository) projects will use the git CLI. -## Next steps +### Next steps -Now that your project is configured, you can start transforming your Databricks data with dbt. To help you scale efficiently, we recommend you follow our best practices, starting with the ["Unity Catalog best practices" guide](dbt-unity-catalog-best-practices). +Now that your project is configured, you can start transforming your Databricks data with dbt. To help you scale efficiently, we recommend you follow our best practices, starting with the [Unity Catalog best practices](/best-practices/dbt-unity-catalog-best-practices), then you can [Optimize dbt models on Databricks](/guides/how_to_optimize_dbt_models_on_databricks) . diff --git a/website/docs/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md similarity index 96% rename from website/docs/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md rename to website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md index bb1045b3d2f..f80dc081c8b 100644 --- a/website/docs/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md +++ b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md @@ -4,7 +4,14 @@ id: how-to-use-databricks-workflows-to-run-dbt-cloud-jobs description: Learn how to use Databricks workflows to run dbt Cloud jobs displayText: "Use Databricks workflows to run dbt Cloud jobs" hoverSnippet: Learn how to use Databricks workflows to run dbt Cloud jobs +time_to_complete: '60 minutes' +icon: 'databricks' +hide_table_of_contents: true +tags: ['Databricks', 'dbt Core','dbt Cloud','Orchestration'] +level: 'Intermediate' +recently_updated: true --- +## Introduction Using Databricks workflows to call the dbt Cloud job API can be useful for several reasons: @@ -13,7 +20,7 @@ Using Databricks workflows to call the dbt Cloud job API can be useful for sever 3. [**Separation of concerns —**](https://en.wikipedia.org/wiki/Separation_of_concerns) Detailed logs for dbt jobs in the dbt Cloud environment can lead to more modularity and efficient debugging. By doing so, it becomes easier to isolate bugs quickly while still being able to see the overall status in Databricks. 4. **Custom job triggering —** Use a Databricks workflow to trigger dbt Cloud jobs based on custom conditions or logic that aren't natively supported by dbt Cloud's scheduling feature. This can give you more flexibility in terms of when and how your dbt Cloud jobs run. -## Prerequisites +### Prerequisites - Active [Teams or Enterprise dbt Cloud account](https://www.getdbt.com/pricing/) - You must have a configured and existing [dbt Cloud deploy job](/docs/deploy/deploy-jobs) @@ -29,7 +36,7 @@ To use Databricks workflows for running dbt Cloud jobs, you need to perform the - [Create a Databricks Python notebook](#create-a-databricks-python-notebook) - [Configure the workflows to run the dbt Cloud jobs](#configure-the-workflows-to-run-the-dbt-cloud-jobs) -### Set up a Databricks secret scope +## Set up a Databricks secret scope 1. Retrieve **[User API Token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens#user-api-tokens) **or **[Service Account Token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens#generating-service-account-tokens) **from dbt Cloud 2. Set up a **Databricks secret scope**, which is used to securely store your dbt Cloud API key. @@ -47,7 +54,7 @@ databricks secrets put --scope --key --s 5. Replace **``** with the actual API key value that you copied from dbt Cloud in step 1. -### Create a Databricks Python notebook +## Create a Databricks Python notebook 1. [Create a **Databricks Python notebook**](https://docs.databricks.com/notebooks/notebooks-manage.html), which executes a Python script that calls the dbt Cloud job API. @@ -165,7 +172,7 @@ DbtJobRunStatus.SUCCESS You can cancel the job from dbt Cloud if necessary. ::: -### Configure the workflows to run the dbt Cloud jobs +## Configure the workflows to run the dbt Cloud jobs You can set up workflows directly from the notebook OR by adding this notebook to one of your existing workflows: @@ -206,6 +213,4 @@ You can set up workflows directly from the notebook OR by adding this notebook t Multiple Workflow tasks can be set up using the same notebook by configuring the `job_id` parameter to point to different dbt Cloud jobs. -## Closing - Using Databricks workflows to access the dbt Cloud job API can improve integration of your data pipeline processes and enable scheduling of more complex workflows. diff --git a/website/docs/guides/migrating-from-spark-to-databricks.md b/website/docs/guides/migrating-from-spark-to-databricks.md index f9255c585db..dd128a330e3 100644 --- a/website/docs/guides/migrating-from-spark-to-databricks.md +++ b/website/docs/guides/migrating-from-spark-to-databricks.md @@ -8,7 +8,7 @@ time_to_complete: '30 minutes' platform: ['dbt-core','dbt-cloud'] icon: 'guides' hide_table_of_contents: true -tags: ['migration', 'dbt Core','dbt Cloud'] +tags: ['Migration', 'dbt Core','dbt Cloud'] level: 'Intermediate' recently_updated: true --- diff --git a/website/docs/guides/migrating-from-stored-procedures.md b/website/docs/guides/migrating-from-stored-procedures.md index e8abff49aec..72f0dee7810 100644 --- a/website/docs/guides/migrating-from-stored-procedures.md +++ b/website/docs/guides/migrating-from-stored-procedures.md @@ -8,7 +8,7 @@ time_to_complete: '30 minutes' platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['materializations', 'dbt Core'] +tags: ['Migration', 'dbt Core'] level: 'Beginner' recently_updated: true --- diff --git a/website/docs/guides/orchestration/custom-cicd-pipelines/1-cicd-background.md b/website/docs/guides/orchestration/custom-cicd-pipelines/1-cicd-background.md deleted file mode 100644 index a66259c6c49..00000000000 --- a/website/docs/guides/orchestration/custom-cicd-pipelines/1-cicd-background.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Customizing CI/CD with Custom Pipelines -id: 1-cicd-background ---- - -One of the core tenets of dbt is that analytic code should be version controlled. This provides a ton of benefit to your organization in terms of collaboration, code consistency, stability, and the ability to roll back to a prior version. There’s an additional benefit that is provided with your code hosting platform that is often overlooked or underutilized. Some of you may have experience using dbt Cloud’s [webhook functionality](https://docs.getdbt.com/docs/dbt-cloud/using-dbt-cloud/cloud-enabling-continuous-integration) to run a job when a PR is created. This is a fantastic capability, and meets most use cases for testing your code before merging to production. However, there are circumstances when an organization needs additional functionality, like running workflows on every commit (linting), or running workflows after a merge is complete. In this article, we will show you how to setup custom pipelines to lint your project and trigger a dbt Cloud job via the API. - -A note on parlance in this article since each code hosting platform uses different terms for similar concepts. The terms `pull request` (PR) and `merge request` (MR) are used interchangeably to mean the process of merging one branch into another branch. - - -## What are pipelines? - -Pipelines (which are known by many names, such as workflows, actions, or build steps) are a series of pre-defined jobs that are triggered by specific events in your repository (PR created, commit pushed, branch merged, etc). Those jobs can do pretty much anything your heart desires assuming you have the proper security access and coding chops. - -Jobs are executed on [runners](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#runners), which are virtual servers. The runners come pre-configured with Ubuntu Linux, macOS, or Windows. That means the commands you execute are determined by the operating system of your runner. You’ll see how this comes into play later in the setup, but for now just remember that your code is executed on virtual servers that are, typically, hosted by the code hosting platform. - -![Diagram of how pipelines work](/img/guides/orchestration/custom-cicd-pipelines/pipeline-diagram.png) - -Please note, runners hosted by your code hosting platform provide a certain amount of free time. After that, billing charges may apply depending on how your account is setup. You also have the ability to host your own runners. That is beyond the scope of this article, but checkout the links below for more information if you’re interested in setting that up: - -- Repo-hosted runner billing information: - - [GitHub](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions) - - [GitLab](https://docs.gitlab.com/ee/ci/pipelines/cicd_minutes.html) - - [Bitbucket](https://bitbucket.org/product/features/pipelines#) -- Self-hosted runner information: - - [GitHub](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) - - [GitLab](https://docs.gitlab.com/runner/) - - [Bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/runners/) - -Additionally, if you’re using the free tier of GitLab you can still follow this guide, but it may ask you to provide a credit card to verify your account. You’ll see something like this the first time you try to run a pipeline: - -![Warning from GitLab showing payment information is required](/img/guides/orchestration/custom-cicd-pipelines/gitlab-cicd-payment-warning.png) - - -## How to setup pipelines - -This guide provides details for multiple code hosting platforms. Where steps are unique, they are presented without a selection option. If code is specific to a platform (i.e. GitHub, GitLab, Bitbucket) you will see a selection option for each. - -Pipelines can be triggered by various events. The [dbt Cloud webhook](https://docs.getdbt.com/docs/dbt-cloud/using-dbt-cloud/cloud-enabling-continuous-integration) process already triggers a run if you want to run your jobs on a merge request, so this guide focuses on running pipelines for every push and when PRs are merged. Since pushes happen frequently in a project, we’ll keep this job super simple and fast by linting with SQLFluff. The pipeline that runs on merge requests will run less frequently, and can be used to call the dbt Cloud API to trigger a specific job. This can be helpful if you have specific requirements that need to happen when code is updated in production, like running a `--full-refresh` on all impacted incremental models. - -Here’s a quick look at what this pipeline will accomplish: - -![Diagram showing the pipelines to be created and the programs involved](/img/guides/orchestration/custom-cicd-pipelines/pipeline-programs-diagram.png) diff --git a/website/docs/guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr.md b/website/docs/guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr.md deleted file mode 100644 index 1a75fdc17ac..00000000000 --- a/website/docs/guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: Run a dbt Cloud job on pull request -id: 4-dbt-cloud-job-on-pr ---- - -:::info Run on PR - -If your git provider has a native integration with dbt Cloud, you can take advantage of the setup instructions [here](/docs/deploy/ci-jobs). -This section is only for those projects that connect to their git repository using an SSH key. - -::: - -If your git provider is not one with a native integration with dbt Cloud, but you still want to take advantage of CI builds, you've come to the right spot! With just a bit of work it's possible to setup a job that will run a dbt Cloud job when a pull request (PR) is created. - -The setup for this pipeline will use the same steps as the prior page. Before moving on, **follow steps 1-5 from the [prior page](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge)** - -### 6. Create a pipeline job that runs when PRs are created - - - -For this job, we'll set it up using the `bitbucket-pipelines.yml` file as in the prior step. The YAML file will look pretty similar to our earlier job, but we’ll pass in the required variables to the Python script using `export` statements. Update this section to match your setup based on the comments in the file. - -**What is this pipeline going to do?** -The setup below will trigger a dbt Cloud job to run every time a PR is opened in this repository. It will also run a fresh version of the pipeline for every commit that is made on the PR until it is merged. -For example: If you open a PR, it will run the pipeline. If you then decide additional changes are needed, and commit/push to the PR branch, a new pipeline will run with the updated code. - -The following varibles control this job: - - `DBT_JOB_BRANCH`: Tells the dbt Cloud job to run the code in the branch that created this PR - - `DBT_JOB_SCHEMA_OVERRIDE`: Tells the dbt Cloud job to run this into a custom target schema - - The format of this will look like: `DBT_CLOUD_PR_{REPO_KEY}_{PR_NUMBER}` - - -```yaml -image: python:3.11.1 - - -pipelines: - # This job will run when pull requests are created in the repository - pull-requests: - '**': - - step: - name: 'Run dbt Cloud PR Job' - script: - # Check to only build if PR destination is master (or other branch). - # Comment or remove line below if you want to run on all PR's regardless of destination branch. - - if [ "${BITBUCKET_PR_DESTINATION_BRANCH}" != "main" ]; then printf 'PR Destination is not master, exiting.'; exit; fi - - export DBT_URL="https://cloud.getdbt.com" - - export DBT_JOB_CAUSE="Bitbucket Pipeline CI Job" - - export DBT_JOB_BRANCH=$BITBUCKET_BRANCH - - export DBT_JOB_SCHEMA_OVERRIDE="DBT_CLOUD_PR_"$BITBUCKET_PROJECT_KEY"_"$BITBUCKET_PR_ID - - export DBT_ACCOUNT_ID=00000 # enter your account id here - - export DBT_PROJECT_ID=00000 # enter your project id here - - export DBT_PR_JOB_ID=00000 # enter your job id here - - python python/run_and_monitor_dbt_job.py -``` - - - - -### 7. Confirm the pipeline runs - -Now that you have a new pipeline, it's time to run it and make sure it works. Since this only triggers when a PR is created, you'll need to create a new PR on a branch that contains the code above. Once you do that, you should see a pipeline that looks like this: - - - - -Bitbucket pipeline: -![dbt run on PR job in Bitbucket](/img/guides/orchestration/custom-cicd-pipelines/bitbucket-run-on-pr.png) - -dbt Cloud job: -![dbt Cloud job showing it was triggered by Bitbucket](/img/guides/orchestration/custom-cicd-pipelines/bitbucket-dbt-cloud-pr.png) - - - - -### 8. Handle those extra schemas in your database - -As noted above, when the PR job runs it will create a new schema based on the PR. To avoid having your database overwhelmed with PR schemas, consider adding a "cleanup" job to your dbt Cloud account. This job can run on a scheduled basis to cleanup any PR schemas that haven't been updated/used recently. - -Add this as a macro to your project. It takes 2 arguments that lets you control which schema get dropped: - - `age_in_days`: The number of days since the schema was last altered before it should be dropped (default 10 days) - - `database_to_clean`: The name of the database to remove schemas from - -```sql -{# - This macro finds PR schemas older than a set date and drops them - The macro defaults to 10 days old, but can be configured with the input argument age_in_days - Sample usage with different date: - dbt run-operation pr_schema_cleanup --args "{'database_to_clean': 'analytics','age_in_days':'15'}" -#} -{% macro pr_schema_cleanup(database_to_clean, age_in_days=10) %} - - {% set find_old_schemas %} - select - 'drop schema {{ database_to_clean }}.'||schema_name||';' - from {{ database_to_clean }}.information_schema.schemata - where - catalog_name = '{{ database_to_clean | upper }}' - and schema_name ilike 'DBT_CLOUD_PR%' - and last_altered <= (current_date() - interval '{{ age_in_days }} days') - {% endset %} - - {% if execute %} - - {{ log('Schema drop statements:' ,True) }} - - {% set schema_drop_list = run_query(find_old_schemas).columns[0].values() %} - - {% for schema_to_drop in schema_drop_list %} - {% do run_query(schema_to_drop) %} - {{ log(schema_to_drop ,True) }} - {% endfor %} - - {% endif %} - -{% endmacro %} -``` - -This macro goes into a dbt Cloud job that is run on a schedule. The command will look like this (text below for copy/paste): -![dbt Cloud job showing the run operation command for the cleanup macro](/img/guides/orchestration/custom-cicd-pipelines/dbt-macro-cleanup-pr.png) -`dbt run-operation pr_schema_cleanup --args "{ 'database_to_clean': 'development','age_in_days':15}"` diff --git a/website/docs/guides/orchestration/custom-cicd-pipelines/5-something-to-consider.md b/website/docs/guides/orchestration/custom-cicd-pipelines/5-something-to-consider.md deleted file mode 100644 index 6b39c5ce405..00000000000 --- a/website/docs/guides/orchestration/custom-cicd-pipelines/5-something-to-consider.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Something to Consider -id: 5-something-to-consider ---- - -Running dbt Cloud jobs through a CI/CD pipeline is a form of job orchestration. If you also run jobs using dbt Cloud’s built in scheduler, you now have 2 orchestration tools running jobs. The risk with this is that you could run into conflicts - you can imagine a case where you are triggering a pipeline on certain actions and running scheduled jobs in dbt Cloud, you would probably run into job clashes. The more tools you have, the more you have to make sure everything talks to each other. - -That being said, if **the only reason you want to use pipelines is for adding a lint check or run on merge**, you might decide the pros outweigh the cons, and as such you want to go with a hybrid approach. Just keep in mind that if two processes try and run the same job at the same time, dbt Cloud will queue the jobs and run one after the other. It’s a balancing act but can be accomplished with diligence to ensure you’re orchestrating jobs in a manner that does not conflict. \ No newline at end of file diff --git a/website/docs/guides/orchestration/set-up-ci/1-introduction.md b/website/docs/guides/orchestration/set-up-ci/1-introduction.md deleted file mode 100644 index 97df16b4ce1..00000000000 --- a/website/docs/guides/orchestration/set-up-ci/1-introduction.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "Get started with Continuous Integration tests" -slug: overview ---- - -By validating your code _before_ it goes into production, you don't need to spend your afternoon fielding messages from people whose reports are suddenly broken. - -A solid CI setup is critical to preventing avoidable downtime and broken trust. dbt Cloud uses **sensible defaults** to get you up and running in a performant and cost-effective way in minimal time. - -After that, there's time to get fancy, but let's walk before we run. diff --git a/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md b/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md deleted file mode 100644 index 0dcfba7eb06..00000000000 --- a/website/docs/guides/orchestration/set-up-ci/2-quick-setup.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Baseline: Enable CI in 15 minutes" -slug: in-15-minutes -description: Find issues before they are deployed to production with dbt Cloud's Slim CI. ---- - -In this guide, we're going to add a **CI environment**, where proposed changes can be validated in the context of the entire project without impacting production systems. We will use a single set of deployment credentials (like the Prod environment), but models are built in a separate location to avoid impacting others (like the Dev environment). - -Your git flow will look like this: - - -## Prerequisites - -As part of your initial dbt Cloud setup, you should already have Development and Production environments configured. Let's recap what each does: - -- Your **Development environment** powers the IDE. Each user has individual credentials, and builds into an individual dev schema. Nothing you do here impacts any of your colleagues. -- Your **Production environment** brings the canonical version of your project to life for downstream consumers. There is a single set of deployment credentials, and everything is built into your production schema(s). - -## Step 1: Create a new CI environment - -See [Create a new environment](/docs/dbt-cloud-environments#create-a-deployment-environment). The environment should be called **CI**. Just like your existing Production environment, it will be a Deployment-type environment. - -When setting a Schema in the **Deployment Credentials** area, remember that dbt Cloud will automatically generate a custom schema name for each PR to ensure that they don't interfere with your deployed models. This means you can safely set the same Schema name as your Production job. - -## Step 2: Double-check your Production environment is identified - -Go into your existing Production environment, and ensure that the **Set as Production environment** checkbox is set. It'll make things easier later. - -## Step 3: Create a new job in the CI environment - -Use the **Continuous Integration Job** template, and call the job **CI Check**. - -In the Execution Settings, your command will be preset to `dbt build --select state:modified+`. Let's break this down: - -- [`dbt build`](/reference/commands/build) runs all nodes (seeds, models, snapshots, tests) at once in DAG order. If something fails, nodes that depend on it will be skipped. -- The [`state:modified+` selector](/reference/node-selection/methods#the-state-method) means that only modified nodes and their children will be run ("Slim CI"). In addition to [not wasting time](https://discourse.getdbt.com/t/how-we-sped-up-our-ci-runs-by-10x-using-slim-ci/2603) building and testing nodes that weren't changed in the first place, this significantly reduces compute costs. - -To be able to find modified nodes, dbt needs to have something to compare against. dbt Cloud uses the last successful run of any job in your Production environment as its [comparison state](/reference/node-selection/syntax#about-node-selection). As long as you identified your Production environment in Step 2, you won't need to touch this. If you didn't, pick the right environment from the dropdown. - -## Step 4: Test your process - -That's it! There are other steps you can take to be even more confident in your work, such as [validating your structure follows best practices](/guides/orchestration/set-up-ci/run-dbt-project-evaluator) and [linting your code](/guides/orchestration/set-up-ci/lint-on-push), but this covers the most critical checks. - -To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft). Within a few seconds, you’ll see a new check appear in your git provider. - -## Things to keep in mind - -- If you make a new commit while a CI run based on older code is in progress, it will be automatically canceled and replaced with the fresh code. -- An unlimited number of CI jobs can run at once. If 10 developers all commit code to different PRs at the same time, each person will get their own schema containing their changes. Once each PR is merged, dbt Cloud will drop that schema. -- CI jobs will never block a production run. diff --git a/website/docs/guides/orchestration/set-up-ci/3-run-dbt-project-evaluator.md b/website/docs/guides/orchestration/set-up-ci/3-run-dbt-project-evaluator.md deleted file mode 100644 index 646a9cb42b7..00000000000 --- a/website/docs/guides/orchestration/set-up-ci/3-run-dbt-project-evaluator.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "Enforce best practices with dbt project evaluator" -slug: run-dbt-project-evaluator -description: dbt Project Evaluator can be run from inside of your existing dbt Cloud CI job to identify common flaws in projects. ---- - -dbt Project Evaluator is a package designed to identify deviations from best practices common to many dbt projects, including modeling, testing, documentation, structure and performance problems. For an introduction to the package, read its [launch blog post](/blog/align-with-dbt-project-evaluator). - -## Step 1: Install the package - -As with all packages, add a reference to `dbt-labs/dbt_project_evaluator` to your `packages.yml` file. See the [dbt Package Hub](https://hub.getdbt.com/dbt-labs/dbt_project_evaluator/latest/) for full installation instructions. - -## Step 2: Define test severity with an environment variable - -As noted in the [documentation](https://dbt-labs.github.io/dbt-project-evaluator/latest/ci-check/), tests in the package are set to `warn` severity by default. - -To have these tests fail in CI, create a new environment called `DBT_PROJECT_EVALUATOR_SEVERITY`. Set the project-wide default to `warn`, and set it to `error` in the CI environment. - -In your `dbt_project.yml` file, override the severity configuration: - -```yaml -tests: -dbt_project_evaluator: - +severity: "{{ env_var('DBT_PROJECT_EVALUATOR_SEVERITY', 'warn') }}" -``` - -## Step 3: Update your CI commands - -Because these tests should only run after the rest of your project has been built, your existing CI command will need to be updated to exclude the dbt_project_evaluator package. You will then add a second step which builds _only_ the package's models and tests. - -Update your steps to: - -```bash -dbt build --select state:modified+ --exclude package:dbt_project_evaluator -dbt build --select package:dbt_project_evaluator -``` - -## Step 4: Apply any customizations - -Depending on the state of your project when you roll out the evaluator, you may need to skip some tests or allow exceptions for some areas. To do this, refer to the documentation on: - -- [disabling tests](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/customization/) -- [excluding groups of models from a specific test](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/exceptions/) -- [excluding packages or sources/models based on path](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/excluding-packages-and-paths/) - -If you create a seed to exclude groups of models from a specific test, remember to disable the default seed and include `dbt_project_evaluator_exceptions` in your second `dbt build` command above. diff --git a/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md b/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md deleted file mode 100644 index bc080922ab5..00000000000 --- a/website/docs/guides/orchestration/set-up-ci/4-lint-on-push.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: "Run linting checks with SQLFluff" -slug: lint-on-push -description: Enforce your organization's SQL style guide with by running SQLFluff in your git workflow whenever new code is pushed. ---- - -By [linting](/docs/cloud/dbt-cloud-ide/lint-format#lint) your project during CI, you can ensure that code styling standards are consistently enforced, without spending human time nitpicking comma placement. - -The steps below create an action/pipeline which uses [SQLFluff](https://docs.sqlfluff.com/en/stable/) to scan your code and look for linting errors. If you don't already have SQLFluff rules defined, check out [our recommended config file](/best-practices/how-we-style/2-how-we-style-our-sql). - -### 1. Create a YAML file to define your pipeline - -The YAML files defined below are what tell your code hosting platform the steps to run. In this setup, you’re telling the platform to run a SQLFluff lint job every time a commit is pushed. - - - - -GitHub Actions are defined in the `.github/workflows` directory. To define the job for your action, add a new file named `lint_on_push.yml` under the `workflows` folder. Your final folder structure will look like this: - -```sql -my_awesome_project -├── .github -│ ├── workflows -│ │ └── lint_on_push.yml -``` - -**Key pieces:** - -- `on:` defines when the pipeline is run. This workflow will run whenever code is pushed to any branch except `main`. For other trigger options, check out [GitHub’s docs](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). -- `runs-on: ubuntu-latest` - this defines the operating system we’re using to run the job -- `uses:` - When the Ubuntu server is created, it is completely empty. [`checkout`](https://github.com/actions/checkout#checkout-v3) and [`setup-python`](https://github.com/actions/setup-python#setup-python-v3) are public GitHub Actions which enable the server to access the code in your repo, and set up Python correctly. -- `run:` - these steps are run at the command line, as though you typed them at a prompt yourself. This will install sqlfluff and lint the project. Be sure to set the correct `--dialect` for your project. - -For a full breakdown of the properties in a workflow file, see [Understanding the workflow file](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#understanding-the-workflow-file) on GitHub's website. - -```yaml -name: lint dbt project on push - -on: - push: - branches-ignore: - - 'main' - -jobs: - # this job runs SQLFluff with a specific set of rules - # note the dialect is set to Snowflake, so make that specific to your setup - # details on linter rules: https://docs.sqlfluff.com/en/stable/rules.html - lint_project: - name: Run SQLFluff linter - runs-on: ubuntu-latest - - steps: - - uses: "actions/checkout@v3" - - uses: "actions/setup-python@v4" - with: - python-version: "3.9" - - name: Install SQLFluff - run: "pip install sqlfluff" - - name: Lint project - run: "sqlfluff lint models --dialect snowflake" - -``` - - - - -Create a `.gitlab-ci.yml` file in your **root directory** to define the triggers for when to execute the script below. You’ll put the code below into this file. - -```sql -my_awesome_project -├── dbt_project.yml -├── .gitlab-ci.yml -``` - -**Key pieces:** - -- `image: python:3.9` - this defines the virtual image we’re using to run the job -- `rules:` - defines when the pipeline is run. This workflow will run whenever code is pushed to any branch except `main`. For other rules, refer to [GitLab’s documentation](https://docs.gitlab.com/ee/ci/yaml/#rules). -- `script:` - this is how we’re telling the GitLab runner to execute the Python script we defined above. - -```yaml -image: python:3.9 - -stages: - - pre-build - -# this job runs SQLFluff with a specific set of rules -# note the dialect is set to Snowflake, so make that specific to your setup -# details on linter rules: https://docs.sqlfluff.com/en/stable/rules.html -lint-project: - stage: pre-build - rules: - - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH != 'main' - script: - - pip install sqlfluff - - sqlfluff lint models --dialect snowflake -``` - - - - -Create a `bitbucket-pipelines.yml` file in your **root directory** to define the triggers for when to execute the script below. You’ll put the code below into this file. - -```sql -my_awesome_project -├── bitbucket-pipelines.yml -├── dbt_project.yml -``` - -**Key pieces:** - -- `image: python:3.11.1` - this defines the virtual image we’re using to run the job -- `'**':` - this is used to filter when the pipeline runs. In this case we’re telling it to run on every push event, and you can see at line 12 we're creating a dummy pipeline for `main`. More information on filtering when a pipeline is run can be found in [Bitbucket's documentation](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-triggers/) -- `script:` - this is how we’re telling the Bitbucket runner to execute the Python script we defined above. - -```yaml -image: python:3.11.1 - - -pipelines: - branches: - '**': # this sets a wildcard to run on every branch - - step: - name: Lint dbt project - script: - - pip install sqlfluff==0.13.1 - - sqlfluff lint models --dialect snowflake --rules L019,L020,L021,L022 - - 'main': # override if your default branch doesn't run on a branch named "main" - - step: - script: - - python --version -``` - - - - -### 2. Commit and push your changes to make sure everything works - -After you finish creating the YAML files, commit and push your code to trigger your pipeline for the first time. If everything goes well, you should see the pipeline in your code platform. When you click into the job you’ll get a log showing that SQLFluff was run. If your code failed linting you’ll get an error in the job with a description of what needs to be fixed. If everything passed the lint check, you’ll see a successful job run. - - - - -In your repository, click the *Actions* tab - -![Image showing the GitHub action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-github.png) - -Sample output from SQLFluff in the `Run SQLFluff linter` job: - -![Image showing the logs in GitHub for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-github.png) - - - - -In the menu option go to *CI/CD > Pipelines* - -![Image showing the GitLab action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-gitlab.png) - -Sample output from SQLFluff in the `Run SQLFluff linter` job: - -![Image showing the logs in GitLab for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-gitlab.png) - - - - -In the left menu pane, click on *Pipelines* - -![Image showing the Bitbucket action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-bitbucket.png) - -Sample output from SQLFluff in the `Run SQLFluff linter` job: - -![Image showing the logs in Bitbucket for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-bitbucket.png) - - - diff --git a/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md b/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md deleted file mode 100644 index 2c48e453c2c..00000000000 --- a/website/docs/guides/orchestration/set-up-ci/5-multiple-checks.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: "Advanced: Create a release train with additional environments" -slug: multiple-environments -description: Large and complex enterprises sometimes require additional layers of validation before deployment. Learn how to add these checks with dbt Cloud. ---- - -:::caution Are you sure you need this? -This approach can increase release safety, but creates additional manual steps in the deployment process as well as a greater maintenance burden. - -As such, it may slow down the time it takes to get new features into production. - -The team at Sunrun maintained a SOX-compliant deployment in dbt while reducing the number of environments. Check out [their Coalesce presentation](https://www.youtube.com/watch?v=vmBAO2XN-fM) to learn more. -::: - -In this section, we will add a new **QA** environment. New features will branch off from and be merged back into the associated `qa` branch, and a member of your team (the "Release Manager") will create a PR against `main` to be validated in the CI environment before going live. - -The git flow will look like this: - - -## Prerequisites - -- You have the **Development**, **CI**, and **Production** environments, as described in [the Baseline setup](/guides/orchestration/set-up-ci/in-15-minutes). - - -## Step 1: Create a `release` branch in your git repo - -As noted above, this branch will outlive any individual feature, and will be the base of all feature development for a period of time. Your team might choose to create a new branch for each sprint (`qa/sprint-01`, `qa/sprint-02`, etc), tie it to a version of your data product (`qa/1.0`, `qa/1.1`), or just have a single `qa` branch which remains active indefinitely. - -## Step 2: Update your Development environment to use the `qa` branch - -See [Custom branch behavior](/docs/dbt-cloud-environments#custom-branch-behavior). Setting `qa` as your custom branch ensures that the IDE creates new branches and PRs with the correct target, instead of using `main`. - - - -## Step 3: Create a new QA environment - -See [Create a new environment](/docs/dbt-cloud-environments#create-a-deployment-environment). The environment should be called **QA**. Just like your existing Production and CI environments, it will be a Deployment-type environment. - -Set its branch to `qa` as well. - -## Step 4: Create a new job - -Use the **Continuous Integration Job** template, and call the job **QA Check**. - -In the Execution Settings, your command will be preset to `dbt build --select state:modified+`. Let's break this down: - -- [`dbt build`](/reference/commands/build) runs all nodes (seeds, models, snapshots, tests) at once in DAG order. If something fails, nodes that depend on it will be skipped. -- The [`state:modified+` selector](/reference/node-selection/methods#the-state-method) means that only modified nodes and their children will be run ("Slim CI"). In addition to [not wasting time](https://discourse.getdbt.com/t/how-we-sped-up-our-ci-runs-by-10x-using-slim-ci/2603) building and testing nodes that weren't changed in the first place, this significantly reduces compute costs. - -To be able to find modified nodes, dbt needs to have something to compare against. Normally, we use the Production environment as the source of truth, but in this case there will be new code merged into `qa` long before it hits the `main` branch and Production environment. Because of this, we'll want to defer the Release environment to itself. - -### Optional: also add a compile-only job - -dbt Cloud uses the last successful run of any job in that environment as its [comparison state](/reference/node-selection/syntax#about-node-selection). If you have a lot of PRs in flight, the comparison state could switch around regularly. - -Adding a regularly-scheduled job inside of the QA environment whose only command is `dbt compile` can regenerate a more stable manifest for comparison purposes. - -## Step 5: Test your process - -When the Release Manager is ready to cut a new release, they will manually open a PR from `qa` into `main` from their git provider (e.g. GitHub, GitLab, Azure DevOps). dbt Cloud will detect the new PR, at which point the existing check in the CI environment will trigger and run. When using the [baseline configuration](/guides/orchestration/set-up-ci/in-15-minutes), it's possible to kick off the PR creation from inside of the dbt Cloud IDE. Under this paradigm, that button will create PRs targeting your QA branch instead. - -To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft) against your `qa` branch. You'll see the integration tests begin to run. Once they complete, manually create a PR against `main`, and within a few seconds you’ll see the tests run again but this time incorporating all changes from all code that hasn't been merged to main yet. diff --git a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md b/website/docs/guides/productionizing-your-dbt-databricks-project.md similarity index 97% rename from website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md rename to website/docs/guides/productionizing-your-dbt-databricks-project.md index 9100ca8c3ce..06245846198 100644 --- a/website/docs/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project.md +++ b/website/docs/guides/productionizing-your-dbt-databricks-project.md @@ -3,12 +3,21 @@ title: Productionizing your dbt Databricks project id: "productionizing-your-dbt-databricks-project" sidebar_label: "Productionizing your dbt Databricks project" description: "Learn how to deliver models to end users and use best practices to maintain production data" +displayText: Productionizing your dbt Databricks project +hoverSnippet: Learn how to Productionizing your dbt Databricks project. +time_to_complete: '30 minutes' +icon: 'databricks' +hide_table_of_contents: true +tags: ['Databricks', 'dbt Core','dbt Cloud'] +level: 'Intermediate' +recently_updated: true --- +## Introduction Welcome to the third installment of our comprehensive series on optimizing and deploying your data pipelines using Databricks and dbt Cloud. In this guide, we'll dive into delivering these models to end users while incorporating best practices to ensure that your production data remains reliable and timely. -## Prerequisites +### Prerequisites If you don't have any of the following requirements, refer to the instructions in the [setup guide](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project) to catch up: @@ -67,7 +76,7 @@ After your job is set up and runs successfully, configure your **[project artifa This will be our main production job to refresh data that will be used by end users. Another job everyone should include in their dbt project is a continuous integration job. -### Add a CI job +## Add a CI job CI/CD, or Continuous Integration and Continuous Deployment/Delivery, has become a standard practice in software development for rapidly delivering new features and bug fixes while maintaining high quality and stability. dbt Cloud enables you to apply these practices to your data transformations. @@ -89,7 +98,7 @@ We recommend setting up a dbt Cloud CI job. This will decrease the job’s runti With dbt tests and SlimCI, you can feel confident that your production data will be timely and accurate even while delivering at high velocity. -### Monitor your jobs +## Monitor your jobs Keeping a close eye on your dbt Cloud jobs is crucial for maintaining a robust and efficient data pipeline. By monitoring job performance and quickly identifying potential issues, you can ensure that your data transformations run smoothly. dbt Cloud provides three entry points to monitor the health of your project: run history, deployment monitor, and status tiles. @@ -101,7 +110,7 @@ The deployment monitor in dbt Cloud offers a higher-level view of your run histo By adding [status tiles](/docs/deploy/dashboard-status-tiles) to your BI dashboards, you can give stakeholders visibility into the health of your data pipeline without leaving their preferred interface. Status tiles instill confidence in your data and help prevent unnecessary inquiries or context switching. To implement dashboard status tiles, you'll need to have dbt docs with [exposures](/docs/build/exposures) defined. -### Notifications +## Set up notifications Setting up [notifications](/docs/deploy/job-notifications) in dbt Cloud allows you to receive alerts via email or a Slack channel whenever a run ends. This ensures that the appropriate teams are notified and can take action promptly when jobs fail or are canceled. To set up notifications: @@ -111,7 +120,7 @@ Setting up [notifications](/docs/deploy/job-notifications) in dbt Cloud allows y If you require notifications through other means than email or Slack, you can use dbt Cloud's outbound [webhooks](/docs/deploy/webhooks) feature to relay job events to other tools. Webhooks enable you to [integrate dbt Cloud with a wide range of SaaS applications](/guides/orchestration/webhooks), extending your pipeline’s automation into other systems. -### Troubleshooting +## Troubleshooting When a disruption occurs in your production pipeline, it's essential to know how to troubleshoot issues effectively to minimize downtime and maintain a high degree of trust with your stakeholders. @@ -150,7 +159,7 @@ Inserting dbt Cloud jobs into a Databricks Workflows allows you to chain togethe To trigger your dbt Cloud job from Databricks, follow the instructions in our [Databricks Workflows to run dbt Cloud jobs guide](/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs). -### Data masking +## Data masking Our [Best Practices for dbt and Unity Catalog](/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices) guide recommends using separate catalogs *dev* and *prod* for development and deployment environments, with Unity Catalog and dbt Cloud handling configurations and permissions for environment isolation. Ensuring security while maintaining efficiency in your development and deployment environments is crucial. Additional security measures may be necessary to protect sensitive data, such as personally identifiable information (PII). @@ -179,7 +188,7 @@ Unity Catalog is a unified governance solution for your lakehouse. It provides a To get the most out of both tools, you can use the [persist docs config](/reference/resource-configs/persist_docs) to push table and column descriptions written in dbt into Unity Catalog, making the information easily accessible to both tools' users. Keeping the descriptions in dbt ensures they are version controlled and can be reproduced after a table is dropped. -## Additional resources +### Related docs - [Advanced deployments course](https://courses.getdbt.com/courses/advanced-deployment) if you want a deeper dive into these topics - [Autoscaling CI: The intelligent Slim CI](https://docs.getdbt.com/blog/intelligent-slim-ci) diff --git a/website/docs/guides/set-up-ci.md b/website/docs/guides/set-up-ci.md new file mode 100644 index 00000000000..cbff83d6b1d --- /dev/null +++ b/website/docs/guides/set-up-ci.md @@ -0,0 +1,355 @@ +--- +title: "Get started with Continuous Integration tests" +description: How to implement a CI environment for safe project validation. +displayText: +hoverSnippet: Learn how to +id: set-up-ci +time_to_complete: '60 minutes' +icon: 'guides' +hide_table_of_contents: true +tags: ['dbt Cloud', 'Orchestration'] +level: 'Intermediate' +recently_updated: true +--- +## Introduction + +By validating your code _before_ it goes into production, you don't need to spend your afternoon fielding messages from people whose reports are suddenly broken. + +A solid CI setup is critical to preventing avoidable downtime and broken trust. dbt Cloud uses **sensible defaults** to get you up and running in a performant and cost-effective way in minimal time. + +After that, there's time to get fancy, but let's walk before we run. + +In this guide, we're going to add a **CI environment**, where proposed changes can be validated in the context of the entire project without impacting production systems. We will use a single set of deployment credentials (like the Prod environment), but models are built in a separate location to avoid impacting others (like the Dev environment). + +Your git flow will look like this: + + +### Prerequisites + +As part of your initial dbt Cloud setup, you should already have Development and Production environments configured. Let's recap what each does: + +- Your **Development environment** powers the IDE. Each user has individual credentials, and builds into an individual dev schema. Nothing you do here impacts any of your colleagues. +- Your **Production environment** brings the canonical version of your project to life for downstream consumers. There is a single set of deployment credentials, and everything is built into your production schema(s). + +## Create a new CI environment + +See [Create a new environment](/docs/dbt-cloud-environments#create-a-deployment-environment). The environment should be called **CI**. Just like your existing Production environment, it will be a Deployment-type environment. + +When setting a Schema in the **Deployment Credentials** area, remember that dbt Cloud will automatically generate a custom schema name for each PR to ensure that they don't interfere with your deployed models. This means you can safely set the same Schema name as your Production job. + +### 1. Double-check your Production environment is identified + +Go into your existing Production environment, and ensure that the **Set as Production environment** checkbox is set. It'll make things easier later. + +### 2. Create a new job in the CI environment + +Use the **Continuous Integration Job** template, and call the job **CI Check**. + +In the Execution Settings, your command will be preset to `dbt build --select state:modified+`. Let's break this down: + +- [`dbt build`](/reference/commands/build) runs all nodes (seeds, models, snapshots, tests) at once in DAG order. If something fails, nodes that depend on it will be skipped. +- The [`state:modified+` selector](/reference/node-selection/methods#the-state-method) means that only modified nodes and their children will be run ("Slim CI"). In addition to [not wasting time](https://discourse.getdbt.com/t/how-we-sped-up-our-ci-runs-by-10x-using-slim-ci/2603) building and testing nodes that weren't changed in the first place, this significantly reduces compute costs. + +To be able to find modified nodes, dbt needs to have something to compare against. dbt Cloud uses the last successful run of any job in your Production environment as its [comparison state](/reference/node-selection/syntax#about-node-selection). As long as you identified your Production environment in Step 2, you won't need to touch this. If you didn't, pick the right environment from the dropdown. + +### 3. Test your process + +That's it! There are other steps you can take to be even more confident in your work, such as [validating your structure follows best practices](/guides/orchestration/set-up-ci/run-dbt-project-evaluator) and [linting your code](/guides/orchestration/set-up-ci/lint-on-push), but this covers the most critical checks. + +To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft). Within a few seconds, you’ll see a new check appear in your git provider. + +### Things to keep in mind + +- If you make a new commit while a CI run based on older code is in progress, it will be automatically canceled and replaced with the fresh code. +- An unlimited number of CI jobs can run at once. If 10 developers all commit code to different PRs at the same time, each person will get their own schema containing their changes. Once each PR is merged, dbt Cloud will drop that schema. +- CI jobs will never block a production run. + +## Enforce best practices with dbt project evaluator + +dbt Project Evaluator is a package designed to identify deviations from best practices common to many dbt projects, including modeling, testing, documentation, structure and performance problems. For an introduction to the package, read its [launch blog post](/blog/align-with-dbt-project-evaluator). + +### 1. Install the package + +As with all packages, add a reference to `dbt-labs/dbt_project_evaluator` to your `packages.yml` file. See the [dbt Package Hub](https://hub.getdbt.com/dbt-labs/dbt_project_evaluator/latest/) for full installation instructions. + +### 2. Define test severity with an environment variable + +As noted in the [documentation](https://dbt-labs.github.io/dbt-project-evaluator/latest/ci-check/), tests in the package are set to `warn` severity by default. + +To have these tests fail in CI, create a new environment called `DBT_PROJECT_EVALUATOR_SEVERITY`. Set the project-wide default to `warn`, and set it to `error` in the CI environment. + +In your `dbt_project.yml` file, override the severity configuration: + +```yaml +tests: +dbt_project_evaluator: + +severity: "{{ env_var('DBT_PROJECT_EVALUATOR_SEVERITY', 'warn') }}" +``` + +### 3. Update your CI commands + +Because these tests should only run after the rest of your project has been built, your existing CI command will need to be updated to exclude the dbt_project_evaluator package. You will then add a second step which builds _only_ the package's models and tests. + +Update your steps to: + +```bash +dbt build --select state:modified+ --exclude package:dbt_project_evaluator +dbt build --select package:dbt_project_evaluator +``` + +### 4. Apply any customizations + +Depending on the state of your project when you roll out the evaluator, you may need to skip some tests or allow exceptions for some areas. To do this, refer to the documentation on: + +- [disabling tests](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/customization/) +- [excluding groups of models from a specific test](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/exceptions/) +- [excluding packages or sources/models based on path](https://dbt-labs.github.io/dbt-project-evaluator/latest/customization/excluding-packages-and-paths/) + +If you create a seed to exclude groups of models from a specific test, remember to disable the default seed and include `dbt_project_evaluator_exceptions` in your second `dbt build` command above. + +## Run linting checks with SQLFluff + +By [linting](/docs/cloud/dbt-cloud-ide/lint-format#lint) your project during CI, you can ensure that code styling standards are consistently enforced, without spending human time nitpicking comma placement. + +The steps below create an action/pipeline which uses [SQLFluff](https://docs.sqlfluff.com/en/stable/) to scan your code and look for linting errors. If you don't already have SQLFluff rules defined, check out [our recommended config file](/best-practices/how-we-style/2-how-we-style-our-sql). + +### 1. Create a YAML file to define your pipeline + +The YAML files defined below are what tell your code hosting platform the steps to run. In this setup, you’re telling the platform to run a SQLFluff lint job every time a commit is pushed. + + + + +GitHub Actions are defined in the `.github/workflows` directory. To define the job for your action, add a new file named `lint_on_push.yml` under the `workflows` folder. Your final folder structure will look like this: + +```sql +my_awesome_project +├── .github +│ ├── workflows +│ │ └── lint_on_push.yml +``` + +**Key pieces:** + +- `on:` defines when the pipeline is run. This workflow will run whenever code is pushed to any branch except `main`. For other trigger options, check out [GitHub’s docs](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). +- `runs-on: ubuntu-latest` - this defines the operating system we’re using to run the job +- `uses:` - When the Ubuntu server is created, it is completely empty. [`checkout`](https://github.com/actions/checkout#checkout-v3) and [`setup-python`](https://github.com/actions/setup-python#setup-python-v3) are public GitHub Actions which enable the server to access the code in your repo, and set up Python correctly. +- `run:` - these steps are run at the command line, as though you typed them at a prompt yourself. This will install sqlfluff and lint the project. Be sure to set the correct `--dialect` for your project. + +For a full breakdown of the properties in a workflow file, see [Understanding the workflow file](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#understanding-the-workflow-file) on GitHub's website. + +```yaml +name: lint dbt project on push + +on: + push: + branches-ignore: + - 'main' + +jobs: + # this job runs SQLFluff with a specific set of rules + # note the dialect is set to Snowflake, so make that specific to your setup + # details on linter rules: https://docs.sqlfluff.com/en/stable/rules.html + lint_project: + name: Run SQLFluff linter + runs-on: ubuntu-latest + + steps: + - uses: "actions/checkout@v3" + - uses: "actions/setup-python@v4" + with: + python-version: "3.9" + - name: Install SQLFluff + run: "pip install sqlfluff" + - name: Lint project + run: "sqlfluff lint models --dialect snowflake" + +``` + + + + +Create a `.gitlab-ci.yml` file in your **root directory** to define the triggers for when to execute the script below. You’ll put the code below into this file. + +```sql +my_awesome_project +├── dbt_project.yml +├── .gitlab-ci.yml +``` + +**Key pieces:** + +- `image: python:3.9` - this defines the virtual image we’re using to run the job +- `rules:` - defines when the pipeline is run. This workflow will run whenever code is pushed to any branch except `main`. For other rules, refer to [GitLab’s documentation](https://docs.gitlab.com/ee/ci/yaml/#rules). +- `script:` - this is how we’re telling the GitLab runner to execute the Python script we defined above. + +```yaml +image: python:3.9 + +stages: + - pre-build + +# this job runs SQLFluff with a specific set of rules +# note the dialect is set to Snowflake, so make that specific to your setup +# details on linter rules: https://docs.sqlfluff.com/en/stable/rules.html +lint-project: + stage: pre-build + rules: + - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH != 'main' + script: + - pip install sqlfluff + - sqlfluff lint models --dialect snowflake +``` + + + + +Create a `bitbucket-pipelines.yml` file in your **root directory** to define the triggers for when to execute the script below. You’ll put the code below into this file. + +```sql +my_awesome_project +├── bitbucket-pipelines.yml +├── dbt_project.yml +``` + +**Key pieces:** + +- `image: python:3.11.1` - this defines the virtual image we’re using to run the job +- `'**':` - this is used to filter when the pipeline runs. In this case we’re telling it to run on every push event, and you can see at line 12 we're creating a dummy pipeline for `main`. More information on filtering when a pipeline is run can be found in [Bitbucket's documentation](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-triggers/) +- `script:` - this is how we’re telling the Bitbucket runner to execute the Python script we defined above. + +```yaml +image: python:3.11.1 + + +pipelines: + branches: + '**': # this sets a wildcard to run on every branch + - step: + name: Lint dbt project + script: + - pip install sqlfluff==0.13.1 + - sqlfluff lint models --dialect snowflake --rules L019,L020,L021,L022 + + 'main': # override if your default branch doesn't run on a branch named "main" + - step: + script: + - python --version +``` + + + + +### 2. Commit and push your changes to make sure everything works + +After you finish creating the YAML files, commit and push your code to trigger your pipeline for the first time. If everything goes well, you should see the pipeline in your code platform. When you click into the job you’ll get a log showing that SQLFluff was run. If your code failed linting you’ll get an error in the job with a description of what needs to be fixed. If everything passed the lint check, you’ll see a successful job run. + + + + +In your repository, click the _Actions_ tab + +![Image showing the GitHub action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-github.png) + +Sample output from SQLFluff in the `Run SQLFluff linter` job: + +![Image showing the logs in GitHub for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-github.png) + + + + +In the menu option go to *CI/CD > Pipelines* + +![Image showing the GitLab action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-gitlab.png) + +Sample output from SQLFluff in the `Run SQLFluff linter` job: + +![Image showing the logs in GitLab for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-gitlab.png) + + + + +In the left menu pane, click on *Pipelines* + +![Image showing the Bitbucket action for lint on push](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-bitbucket.png) + +Sample output from SQLFluff in the `Run SQLFluff linter` job: + +![Image showing the logs in Bitbucket for the SQLFluff run](/img/guides/orchestration/custom-cicd-pipelines/lint-on-push-logs-bitbucket.png) + + + + +## Advanced: Create a release train with additional environments + +Large and complex enterprises sometimes require additional layers of validation before deployment. Learn how to add these checks with dbt Cloud. + +:::caution Are you sure you need this? +This approach can increase release safety, but creates additional manual steps in the deployment process as well as a greater maintenance burden. + +As such, it may slow down the time it takes to get new features into production. + +The team at Sunrun maintained a SOX-compliant deployment in dbt while reducing the number of environments. Check out [their Coalesce presentation](https://www.youtube.com/watch?v=vmBAO2XN-fM) to learn more. +::: + +In this section, we will add a new **QA** environment. New features will branch off from and be merged back into the associated `qa` branch, and a member of your team (the "Release Manager") will create a PR against `main` to be validated in the CI environment before going live. + +The git flow will look like this: + + +### Advanced prerequisites + +- You have the **Development**, **CI**, and **Production** environments, as described in [the Baseline setup](/guides/orchestration/set-up-ci/in-15-minutes). + +### 1. Create a `release` branch in your git repo + +As noted above, this branch will outlive any individual feature, and will be the base of all feature development for a period of time. Your team might choose to create a new branch for each sprint (`qa/sprint-01`, `qa/sprint-02`, etc), tie it to a version of your data product (`qa/1.0`, `qa/1.1`), or just have a single `qa` branch which remains active indefinitely. + +### 2. Update your Development environment to use the `qa` branch + +See [Custom branch behavior](/docs/dbt-cloud-environments#custom-branch-behavior). Setting `qa` as your custom branch ensures that the IDE creates new branches and PRs with the correct target, instead of using `main`. + + + +### 3. Create a new QA environment + +See [Create a new environment](/docs/dbt-cloud-environments#create-a-deployment-environment). The environment should be called **QA**. Just like your existing Production and CI environments, it will be a Deployment-type environment. + +Set its branch to `qa` as well. + +### 4. Create a new job + +Use the **Continuous Integration Job** template, and call the job **QA Check**. + +In the Execution Settings, your command will be preset to `dbt build --select state:modified+`. Let's break this down: + +- [`dbt build`](/reference/commands/build) runs all nodes (seeds, models, snapshots, tests) at once in DAG order. If something fails, nodes that depend on it will be skipped. +- The [`state:modified+` selector](/reference/node-selection/methods#the-state-method) means that only modified nodes and their children will be run ("Slim CI"). In addition to [not wasting time](https://discourse.getdbt.com/t/how-we-sped-up-our-ci-runs-by-10x-using-slim-ci/2603) building and testing nodes that weren't changed in the first place, this significantly reduces compute costs. + +To be able to find modified nodes, dbt needs to have something to compare against. Normally, we use the Production environment as the source of truth, but in this case there will be new code merged into `qa` long before it hits the `main` branch and Production environment. Because of this, we'll want to defer the Release environment to itself. + +### Optional: also add a compile-only job + +dbt Cloud uses the last successful run of any job in that environment as its [comparison state](/reference/node-selection/syntax#about-node-selection). If you have a lot of PRs in flight, the comparison state could switch around regularly. + +Adding a regularly-scheduled job inside of the QA environment whose only command is `dbt compile` can regenerate a more stable manifest for comparison purposes. + +### 5. Test your process + +When the Release Manager is ready to cut a new release, they will manually open a PR from `qa` into `main` from their git provider (e.g. GitHub, GitLab, Azure DevOps). dbt Cloud will detect the new PR, at which point the existing check in the CI environment will trigger and run. When using the [baseline configuration](/guides/orchestration/set-up-ci/in-15-minutes), it's possible to kick off the PR creation from inside of the dbt Cloud IDE. Under this paradigm, that button will create PRs targeting your QA branch instead. + +To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft) against your `qa` branch. You'll see the integration tests begin to run. Once they complete, manually create a PR against `main`, and within a few seconds you’ll see the tests run again but this time incorporating all changes from all code that hasn't been merged to main yet. diff --git a/website/docs/guides/sl-migration.md b/website/docs/guides/sl-migration.md index 865785133e1..fe4755c6225 100644 --- a/website/docs/guides/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -7,7 +7,7 @@ hoverSnippet: Learn how to migrate from the legacy dbt Semantic Layer to the lat time_to_complete: '30 minutes' icon: 'guides' hide_table_of_contents: true -tags: [Semantic Layer] +tags: ['Semantic Layer'] level: 'Intermediate' recently_updated: true --- From 1b148d59af5b08f98e8f5e020fdc6fca29ea9d7b Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:54:43 -0800 Subject: [PATCH 054/152] adding feedback from @nghi-ly --- website/docs/guides/airflow-and-dbt-cloud.md | 4 +++- website/docs/guides/bigquery-qs.md | 3 +-- website/docs/guides/building-packages.md | 4 ++-- website/docs/guides/codespace-qs.md | 3 ++- website/docs/guides/creating-new-materializations.md | 5 ++--- website/docs/guides/custom-cicd-pipelines.md | 4 ---- website/docs/guides/databricks-qs.md | 3 ++- ...t models on Databricks.md => dbt-models-on-databricks.md} | 1 + website/docs/guides/debugging-schema-names.md | 2 +- .../docs/guides/how-to-set-up-your-databricks-dbt-project.md | 3 +++ .../how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md | 1 + website/docs/guides/manual-install-qs.md | 2 ++ website/docs/guides/migrating-from-spark-to-databricks.md | 1 - website/docs/guides/redshift-qs.md | 3 +-- website/docs/guides/refactoring-legacy-sql.md | 2 +- website/docs/guides/sl-migration.md | 2 +- website/docs/guides/snowflake-qs.md | 3 ++- website/docs/guides/starburst-galaxy-qs.md | 3 ++- website/docs/guides/using-jinja.md | 5 +++-- 19 files changed, 30 insertions(+), 24 deletions(-) rename website/docs/guides/{dbt models on Databricks.md => dbt-models-on-databricks.md} (99%) diff --git a/website/docs/guides/airflow-and-dbt-cloud.md b/website/docs/guides/airflow-and-dbt-cloud.md index 98eb9f82d6c..6963a9b9baa 100644 --- a/website/docs/guides/airflow-and-dbt-cloud.md +++ b/website/docs/guides/airflow-and-dbt-cloud.md @@ -164,7 +164,9 @@ Now you have all the working pieces to get up and running with Airflow + dbt Clo ![https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2](https://lh3.googleusercontent.com/sRxe5xbv_LYhIKblc7eiY7AmByr1OibOac2_fIe54rpU3TBGwjMpdi_j0EPEFzM1_gNQXry7Jsm8aVw9wQBSNs1I6Cyzpvijaj0VGwSnmVf3OEV8Hv5EPOQHrwQgK2RhNBdyBxN2) -## Add your `job_id` and `account_id` config details to the python file: [dbt_cloud_provider_eltml.py](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/dags/dbt_cloud_provider_eltml.py) +## Add your `job_id` and `account_id` config details to the python file + + Add your `job_id` and `account_id` config details to the python file: [dbt_cloud_provider_eltml.py](https://github.com/sungchun12/airflow-dbt-cloud/blob/main/dags/dbt_cloud_provider_eltml.py). 1. You’ll find these details within the dbt Cloud job URL, see the comments in the code snippet below for an example. diff --git a/website/docs/guides/bigquery-qs.md b/website/docs/guides/bigquery-qs.md index 5f0b641875f..d84e9a7528f 100644 --- a/website/docs/guides/bigquery-qs.md +++ b/website/docs/guides/bigquery-qs.md @@ -2,11 +2,10 @@ title: "Quickstart for dbt Cloud and BigQuery" id: "bigquery" time_to_complete: '30 minutes' -platform: 'dbt-cloud' +level: 'Beginner' icon: 'bigquery' hide_table_of_contents: true tags: ['BigQuery', 'dbt Cloud'] -level: 'Beginner' recently_updated: true --- diff --git a/website/docs/guides/building-packages.md b/website/docs/guides/building-packages.md index 9d26d7c85e4..32f863271f1 100644 --- a/website/docs/guides/building-packages.md +++ b/website/docs/guides/building-packages.md @@ -1,5 +1,5 @@ --- -title: "Building dbt packages" # to do: update this to creating +title: "Building dbt packages" id: "building-packages" description: When you have dbt code that might help others, you can create a package for dbt using a GitHub repository. displayText: Building dbt packages @@ -7,7 +7,7 @@ hoverSnippet: Learn how to create packages for dbt. time_to_complete: '60 minutes' icon: 'guides' hide_table_of_contents: true -tags: ['dbt Core', 'legacy'] +tags: ['dbt Core'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/codespace-qs.md b/website/docs/guides/codespace-qs.md index f30d82457a8..2f5f57677af 100644 --- a/website/docs/guides/codespace-qs.md +++ b/website/docs/guides/codespace-qs.md @@ -3,8 +3,9 @@ title: "Quickstart for dbt Core using GitHub Codespaces" id: codespace platform: 'dbt-core' icon: 'fa-github' +level: 'Beginner' hide_table_of_contents: true -tags: ['dbt Core'] +tags: ['dbt Core','Quickstart'] --- ## Introduction diff --git a/website/docs/guides/creating-new-materializations.md b/website/docs/guides/creating-new-materializations.md index 12bdd0685b6..f2d1e9c40a2 100644 --- a/website/docs/guides/creating-new-materializations.md +++ b/website/docs/guides/creating-new-materializations.md @@ -1,14 +1,13 @@ --- title: "Creating new materializations" -id: "creating-new-materializations" +id: creating-new-materializations description: Learn how to create your own materializations. displayText: Creating new materializations hoverSnippet: Learn how to create your own materializations. time_to_complete: '30 minutes' -platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['materializations', 'dbt Core'] +tags: ['dbt Core'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/custom-cicd-pipelines.md b/website/docs/guides/custom-cicd-pipelines.md index 094a30eed31..5b4456c161c 100644 --- a/website/docs/guides/custom-cicd-pipelines.md +++ b/website/docs/guides/custom-cicd-pipelines.md @@ -92,8 +92,6 @@ This next part will happen in you code hosting platform. We need to save your AP }> -In GitHub: - - Open up your repository where you want to run the pipeline (the same one that houses your dbt project) - Click *Settings* to open up the repository options - On the left click the *Security* dropdown @@ -113,8 +111,6 @@ Here’s a video showing these steps: -In GitLab: - - Open up your repository where you want to run the pipeline (the same one that houses your dbt project) - Click *Settings* > *CI/CD* - Under the *Variables* section, click *Expand,* then click *Add variable* diff --git a/website/docs/guides/databricks-qs.md b/website/docs/guides/databricks-qs.md index cbd1c36f9a1..0aadf79c18e 100644 --- a/website/docs/guides/databricks-qs.md +++ b/website/docs/guides/databricks-qs.md @@ -1,10 +1,11 @@ --- title: "Quickstart for dbt Cloud and Databricks" id: "databricks" -platform: 'dbt-cloud' +level: 'Beginner' icon: 'databricks' hide_table_of_contents: true recently_updated: true +tags: ['dbt Cloud', 'Quickstart'] --- ## Introduction diff --git a/website/docs/guides/dbt models on Databricks.md b/website/docs/guides/dbt-models-on-databricks.md similarity index 99% rename from website/docs/guides/dbt models on Databricks.md rename to website/docs/guides/dbt-models-on-databricks.md index 5bb6de61e77..a205dd4fe6f 100644 --- a/website/docs/guides/dbt models on Databricks.md +++ b/website/docs/guides/dbt-models-on-databricks.md @@ -1,5 +1,6 @@ --- title: Optimize and troubleshoot dbt models on Databricks +id: optimize-dbt-models-on-databricks sidebar_label: "Optimize and troubleshoot dbt models on Databricks" description: "Learn more about optimizing and troubleshooting your dbt models on Databricks" displayText: Optimizing and troubleshooting your dbt models on Databricks diff --git a/website/docs/guides/debugging-schema-names.md b/website/docs/guides/debugging-schema-names.md index fbd1ce0ae69..73b67941ee4 100644 --- a/website/docs/guides/debugging-schema-names.md +++ b/website/docs/guides/debugging-schema-names.md @@ -8,7 +8,7 @@ time_to_complete: '45 minutes' platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['dbt Core', 'legacy'] +tags: ['dbt Core'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md index 1ca1940cc50..87f313a2542 100644 --- a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md @@ -1,5 +1,6 @@ --- title: How to set up your Databricks and dbt project +id: how-to-set-up-your-databricks-dbt-project sidebar_label: "How to set up your Databricks and dbt project" description: "Learn more about setting up your dbt project with Databricks" displayText: Setting up your dbt project with Databricks @@ -12,6 +13,8 @@ level: 'Intermediate' recently_updated: true --- +## Introduction + Databricks and dbt Labs are partnering to help data teams think like software engineering teams and ship trusted data, faster. The dbt-databricks adapter enables dbt users to leverage the latest Databricks features in their dbt project. Hundreds of customers are now using dbt and Databricks to build expressive and reliable data pipelines on the Lakehouse, generating data assets that enable analytics, ML, and AI use cases throughout the business. In this guide, we discuss how to set up your dbt project on the Databricks Lakehouse Platform so that it scales from a small team all the way up to a large organization. diff --git a/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md index f80dc081c8b..2bff4401acd 100644 --- a/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md +++ b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md @@ -11,6 +11,7 @@ tags: ['Databricks', 'dbt Core','dbt Cloud','Orchestration'] level: 'Intermediate' recently_updated: true --- + ## Introduction Using Databricks workflows to call the dbt Cloud job API can be useful for several reasons: diff --git a/website/docs/guides/manual-install-qs.md b/website/docs/guides/manual-install-qs.md index 5c56f07dc5f..e49ef5bfdf7 100644 --- a/website/docs/guides/manual-install-qs.md +++ b/website/docs/guides/manual-install-qs.md @@ -3,8 +3,10 @@ title: "Quickstart for dbt Core from a manual install" id: manual-install description: "Connecting your warehouse to dbt Core using the CLI." sidebar_label: "Manual install quickstart" +level: 'Beginner' platform: 'dbt-core' icon: 'fa-light fa-square-terminal' +tags: ['dbt Core','Quickstart'] hide_table_of_contents: true --- ## Introduction diff --git a/website/docs/guides/migrating-from-spark-to-databricks.md b/website/docs/guides/migrating-from-spark-to-databricks.md index dd128a330e3..332ba5f0573 100644 --- a/website/docs/guides/migrating-from-spark-to-databricks.md +++ b/website/docs/guides/migrating-from-spark-to-databricks.md @@ -5,7 +5,6 @@ description: Learn how to migrate from dbt-spark to dbt-databricks. displayText: Migrating from Spark to Databricks hoverSnippet: Learn how to migrate from dbt-spark to dbt-databricks. time_to_complete: '30 minutes' -platform: ['dbt-core','dbt-cloud'] icon: 'guides' hide_table_of_contents: true tags: ['Migration', 'dbt Core','dbt Cloud'] diff --git a/website/docs/guides/redshift-qs.md b/website/docs/guides/redshift-qs.md index b635b86a693..d9f41be939c 100644 --- a/website/docs/guides/redshift-qs.md +++ b/website/docs/guides/redshift-qs.md @@ -1,11 +1,10 @@ --- title: "Quickstart for dbt Cloud and Redshift" id: "redshift" -platform: 'dbt-cloud' +level: 'Beginner' icon: 'redshift' hide_table_of_contents: true tags: ['Redshift', 'dbt Cloud'] -level: 'Advanced' --- ## Introduction diff --git a/website/docs/guides/refactoring-legacy-sql.md b/website/docs/guides/refactoring-legacy-sql.md index 2c1bf0ead03..c64157cc24b 100644 --- a/website/docs/guides/refactoring-legacy-sql.md +++ b/website/docs/guides/refactoring-legacy-sql.md @@ -8,7 +8,7 @@ time_to_complete: '30 minutes' platform: 'dbt-cloud' icon: 'guides' hide_table_of_contents: true -tags: ['SQL', 'legacy'] +tags: ['SQL'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/sl-migration.md b/website/docs/guides/sl-migration.md index fe4755c6225..031997b5e4d 100644 --- a/website/docs/guides/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -12,7 +12,7 @@ level: 'Intermediate' recently_updated: true --- -## introduction +## Introduction The legacy Semantic Layer will be deprecated in H2 2023. Additionally, the `dbt_metrics` package will not be supported in dbt v1.6 and later. If you are using `dbt_metrics`, you'll need to upgrade your configurations before upgrading to v1.6. This guide is for people who have the legacy dbt Semantic Layer setup and would like to migrate to the new dbt Semantic Layer. The estimated migration time is two weeks. diff --git a/website/docs/guides/snowflake-qs.md b/website/docs/guides/snowflake-qs.md index 33e253e8c15..4488d6b3097 100644 --- a/website/docs/guides/snowflake-qs.md +++ b/website/docs/guides/snowflake-qs.md @@ -1,8 +1,9 @@ --- title: "Quickstart for dbt Cloud and Snowflake" id: "snowflake" -platform: 'dbt-cloud' +level: 'Beginner' icon: 'snowflake' +tags: ['dbt Cloud','Quickstart'] hide_table_of_contents: true --- ## Introduction diff --git a/website/docs/guides/starburst-galaxy-qs.md b/website/docs/guides/starburst-galaxy-qs.md index 33228710509..1822c83fa90 100644 --- a/website/docs/guides/starburst-galaxy-qs.md +++ b/website/docs/guides/starburst-galaxy-qs.md @@ -1,9 +1,10 @@ --- title: "Quickstart for dbt Cloud and Starburst Galaxy" id: "starburst-galaxy" -platform: 'dbt-cloud' +level: 'Beginner' icon: 'starburst' hide_table_of_contents: true +tags: ['dbt Cloud','Quickstart'] --- ## Introduction diff --git a/website/docs/guides/using-jinja.md b/website/docs/guides/using-jinja.md index 1b2b7d7fd59..9078cb1aedd 100644 --- a/website/docs/guides/using-jinja.md +++ b/website/docs/guides/using-jinja.md @@ -2,14 +2,15 @@ title: "Using Jinja" id: "using-jinja" time_to_complete: '30 minutes' -platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['jinja', 'dbt Core'] +tags: ['Jinja', 'dbt Core'] level: 'Advanced' recently_updated: true --- +## Introduction + In this guide, we're going to take a common pattern used in SQL, and then use Jinja to improve our code. If you'd like to work through this query, add [this CSV](https://github.com/dbt-labs/jaffle_shop/blob/core-v1.0.0/seeds/raw_payments.csv) to the `seeds/` folder of your dbt project, and then execute `dbt seed`. From 054792264d0486e7484cdc6925cc2676cde96a9c Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:14:43 -0800 Subject: [PATCH 055/152] commenting out time_to_complete --- website/docs/guides/airflow-and-dbt-cloud.md | 2 +- website/docs/guides/bigquery-qs.md | 2 +- website/docs/guides/building-packages.md | 2 +- website/docs/guides/creating-new-materializations.md | 2 +- website/docs/guides/custom-cicd-pipelines.md | 2 +- website/docs/guides/dbt-models-on-databricks.md | 2 +- website/docs/guides/debugging-schema-names.md | 2 +- .../docs/guides/how-to-set-up-your-databricks-dbt-project.md | 2 +- .../how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md | 2 +- website/docs/guides/migrating-from-spark-to-databricks.md | 2 +- website/docs/guides/migrating-from-stored-procedures.md | 2 +- .../docs/guides/productionizing-your-dbt-databricks-project.md | 2 +- website/docs/guides/refactoring-legacy-sql.md | 2 +- website/docs/guides/set-up-ci.md | 2 +- website/docs/guides/sl-migration.md | 2 +- website/docs/guides/using-jinja.md | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/website/docs/guides/airflow-and-dbt-cloud.md b/website/docs/guides/airflow-and-dbt-cloud.md index 6963a9b9baa..a3ff59af14e 100644 --- a/website/docs/guides/airflow-and-dbt-cloud.md +++ b/website/docs/guides/airflow-and-dbt-cloud.md @@ -1,7 +1,7 @@ --- title: Airflow and dbt Cloud id: airflow-and-dbt-cloud -time_to_complete: '60 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['dbt Cloud', 'Orchestration'] diff --git a/website/docs/guides/bigquery-qs.md b/website/docs/guides/bigquery-qs.md index d84e9a7528f..f13749333f5 100644 --- a/website/docs/guides/bigquery-qs.md +++ b/website/docs/guides/bigquery-qs.md @@ -1,7 +1,7 @@ --- title: "Quickstart for dbt Cloud and BigQuery" id: "bigquery" -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test level: 'Beginner' icon: 'bigquery' hide_table_of_contents: true diff --git a/website/docs/guides/building-packages.md b/website/docs/guides/building-packages.md index 32f863271f1..ed22ae2fa12 100644 --- a/website/docs/guides/building-packages.md +++ b/website/docs/guides/building-packages.md @@ -4,7 +4,7 @@ id: "building-packages" description: When you have dbt code that might help others, you can create a package for dbt using a GitHub repository. displayText: Building dbt packages hoverSnippet: Learn how to create packages for dbt. -time_to_complete: '60 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['dbt Core'] diff --git a/website/docs/guides/creating-new-materializations.md b/website/docs/guides/creating-new-materializations.md index f2d1e9c40a2..fe1f674e74f 100644 --- a/website/docs/guides/creating-new-materializations.md +++ b/website/docs/guides/creating-new-materializations.md @@ -4,7 +4,7 @@ id: creating-new-materializations description: Learn how to create your own materializations. displayText: Creating new materializations hoverSnippet: Learn how to create your own materializations. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['dbt Core'] diff --git a/website/docs/guides/custom-cicd-pipelines.md b/website/docs/guides/custom-cicd-pipelines.md index 5b4456c161c..df3d30e7c27 100644 --- a/website/docs/guides/custom-cicd-pipelines.md +++ b/website/docs/guides/custom-cicd-pipelines.md @@ -4,7 +4,7 @@ id: custom-cicd-pipelines description: "Learn the benefits of version-controlled analytics code and custom pipelines in dbt for enhanced code testing and workflow automation during the development process." displayText: Learn version-controlled code, custom pipelines, and enhanced code testing. hoverSnippet: Learn version-controlled code, custom pipelines, and enhanced code testing. -time_to_complete: '60 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['dbt Cloud', 'Orchestration'] diff --git a/website/docs/guides/dbt-models-on-databricks.md b/website/docs/guides/dbt-models-on-databricks.md index a205dd4fe6f..21b848b6945 100644 --- a/website/docs/guides/dbt-models-on-databricks.md +++ b/website/docs/guides/dbt-models-on-databricks.md @@ -5,7 +5,7 @@ sidebar_label: "Optimize and troubleshoot dbt models on Databricks" description: "Learn more about optimizing and troubleshooting your dbt models on Databricks" displayText: Optimizing and troubleshooting your dbt models on Databricks hoverSnippet: Learn how to optimize and troubleshoot your dbt models on Databricks. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'databricks' hide_table_of_contents: true tags: ['Databricks', 'dbt Core','dbt Cloud'] diff --git a/website/docs/guides/debugging-schema-names.md b/website/docs/guides/debugging-schema-names.md index 73b67941ee4..a5d8c9be5db 100644 --- a/website/docs/guides/debugging-schema-names.md +++ b/website/docs/guides/debugging-schema-names.md @@ -4,7 +4,7 @@ id: debugging-schema-names description: Learn how to debug schema names when models build under unexpected schemas displayText: Debugging schema names hoverSnippet: Learn how to debug schema names dbt. -time_to_complete: '45 minutes' +# time_to_complete: '30 minutes' commenting out until we test platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md index 87f313a2542..fc03c542861 100644 --- a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md @@ -5,7 +5,7 @@ sidebar_label: "How to set up your Databricks and dbt project" description: "Learn more about setting up your dbt project with Databricks" displayText: Setting up your dbt project with Databricks hoverSnippet: Learn how to set up your dbt project with Databricks. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'databricks' hide_table_of_contents: true tags: ['Databricks', 'dbt Core','dbt Cloud'] diff --git a/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md index 2bff4401acd..30221332355 100644 --- a/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md +++ b/website/docs/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs.md @@ -4,7 +4,7 @@ id: how-to-use-databricks-workflows-to-run-dbt-cloud-jobs description: Learn how to use Databricks workflows to run dbt Cloud jobs displayText: "Use Databricks workflows to run dbt Cloud jobs" hoverSnippet: Learn how to use Databricks workflows to run dbt Cloud jobs -time_to_complete: '60 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'databricks' hide_table_of_contents: true tags: ['Databricks', 'dbt Core','dbt Cloud','Orchestration'] diff --git a/website/docs/guides/migrating-from-spark-to-databricks.md b/website/docs/guides/migrating-from-spark-to-databricks.md index 332ba5f0573..68c867e762e 100644 --- a/website/docs/guides/migrating-from-spark-to-databricks.md +++ b/website/docs/guides/migrating-from-spark-to-databricks.md @@ -4,7 +4,7 @@ id: "migrating-from-spark-to-databricks" description: Learn how to migrate from dbt-spark to dbt-databricks. displayText: Migrating from Spark to Databricks hoverSnippet: Learn how to migrate from dbt-spark to dbt-databricks. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['Migration', 'dbt Core','dbt Cloud'] diff --git a/website/docs/guides/migrating-from-stored-procedures.md b/website/docs/guides/migrating-from-stored-procedures.md index 72f0dee7810..ef86534c4af 100644 --- a/website/docs/guides/migrating-from-stored-procedures.md +++ b/website/docs/guides/migrating-from-stored-procedures.md @@ -4,7 +4,7 @@ id: migrating-from-stored-procedures description: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models, including tips and patterns for the shift from a procedural to a declarative approach in defining datasets. displayText: Migrating from DDL, DML, and stored procedures hoverSnippet: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/productionizing-your-dbt-databricks-project.md b/website/docs/guides/productionizing-your-dbt-databricks-project.md index 06245846198..c7a53ebc105 100644 --- a/website/docs/guides/productionizing-your-dbt-databricks-project.md +++ b/website/docs/guides/productionizing-your-dbt-databricks-project.md @@ -5,7 +5,7 @@ sidebar_label: "Productionizing your dbt Databricks project" description: "Learn how to deliver models to end users and use best practices to maintain production data" displayText: Productionizing your dbt Databricks project hoverSnippet: Learn how to Productionizing your dbt Databricks project. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'databricks' hide_table_of_contents: true tags: ['Databricks', 'dbt Core','dbt Cloud'] diff --git a/website/docs/guides/refactoring-legacy-sql.md b/website/docs/guides/refactoring-legacy-sql.md index c64157cc24b..09fcb9aaf82 100644 --- a/website/docs/guides/refactoring-legacy-sql.md +++ b/website/docs/guides/refactoring-legacy-sql.md @@ -4,7 +4,7 @@ id: refactoring-legacy-sql description: This guide walks through refactoring a long SQL query (perhaps from a stored procedure) into modular dbt data models. displayText: Creating new materializations hoverSnippet: Learn how to refactoring a long SQL query into modular dbt data models. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test platform: 'dbt-cloud' icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/set-up-ci.md b/website/docs/guides/set-up-ci.md index cbff83d6b1d..1dfe7270708 100644 --- a/website/docs/guides/set-up-ci.md +++ b/website/docs/guides/set-up-ci.md @@ -4,7 +4,7 @@ description: How to implement a CI environment for safe project validation. displayText: hoverSnippet: Learn how to id: set-up-ci -time_to_complete: '60 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['dbt Cloud', 'Orchestration'] diff --git a/website/docs/guides/sl-migration.md b/website/docs/guides/sl-migration.md index 031997b5e4d..5f5705314e9 100644 --- a/website/docs/guides/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -4,7 +4,7 @@ id: "sl-migration" sidebar_label: "Legacy dbt Semantic Layer migration" description: "Learn how to migrate from the legacy dbt Semantic Layer to the latest one." hoverSnippet: Learn how to migrate from the legacy dbt Semantic Layer to the latest one. -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['Semantic Layer'] diff --git a/website/docs/guides/using-jinja.md b/website/docs/guides/using-jinja.md index 9078cb1aedd..9c604b86453 100644 --- a/website/docs/guides/using-jinja.md +++ b/website/docs/guides/using-jinja.md @@ -1,7 +1,7 @@ --- title: "Using Jinja" id: "using-jinja" -time_to_complete: '30 minutes' +# time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['Jinja', 'dbt Core'] From 5578d03f131ea02740ebd8082d80fb975c9ab6cc Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:54:42 -0800 Subject: [PATCH 056/152] adding some consistency --- ...rializations.md => create-new-materializations.md} | 4 ++-- website/docs/guides/dbt-models-on-databricks.md | 1 - ...ebugging-schema-names.md => debug-schema-names.md} | 8 ++++---- website/docs/guides/manual-install-qs.md | 1 - ...abricks.md => migrate-from-spark-to-databricks.md} | 6 +++--- ...rocedures.md => migrate-from-stored-procedures.md} | 6 +++--- .../orchestration/webhooks/serverless-datadog.md | 10 ++++++++-- ...d => productionize-your-dbt-databricks-project.md} | 11 +++++------ ...oject.md => set-up-your-databricks-dbt-project.md} | 5 ++--- website/docs/guides/sl-migration.md | 3 +-- website/docs/guides/using-jinja.md | 4 +++- 11 files changed, 31 insertions(+), 28 deletions(-) rename website/docs/guides/{creating-new-materializations.md => create-new-materializations.md} (99%) rename website/docs/guides/{debugging-schema-names.md => debug-schema-names.md} (97%) rename website/docs/guides/{migrating-from-spark-to-databricks.md => migrate-from-spark-to-databricks.md} (97%) rename website/docs/guides/{migrating-from-stored-procedures.md => migrate-from-stored-procedures.md} (99%) rename website/docs/guides/{productionizing-your-dbt-databricks-project.md => productionize-your-dbt-databricks-project.md} (98%) rename website/docs/guides/{how-to-set-up-your-databricks-dbt-project.md => set-up-your-databricks-dbt-project.md} (98%) diff --git a/website/docs/guides/creating-new-materializations.md b/website/docs/guides/create-new-materializations.md similarity index 99% rename from website/docs/guides/creating-new-materializations.md rename to website/docs/guides/create-new-materializations.md index fe1f674e74f..1ad7d202de6 100644 --- a/website/docs/guides/creating-new-materializations.md +++ b/website/docs/guides/create-new-materializations.md @@ -1,6 +1,6 @@ --- -title: "Creating new materializations" -id: creating-new-materializations +title: "Create new materializations" +id: create-new-materializations description: Learn how to create your own materializations. displayText: Creating new materializations hoverSnippet: Learn how to create your own materializations. diff --git a/website/docs/guides/dbt-models-on-databricks.md b/website/docs/guides/dbt-models-on-databricks.md index 21b848b6945..d1a55915777 100644 --- a/website/docs/guides/dbt-models-on-databricks.md +++ b/website/docs/guides/dbt-models-on-databricks.md @@ -1,7 +1,6 @@ --- title: Optimize and troubleshoot dbt models on Databricks id: optimize-dbt-models-on-databricks -sidebar_label: "Optimize and troubleshoot dbt models on Databricks" description: "Learn more about optimizing and troubleshooting your dbt models on Databricks" displayText: Optimizing and troubleshooting your dbt models on Databricks hoverSnippet: Learn how to optimize and troubleshoot your dbt models on Databricks. diff --git a/website/docs/guides/debugging-schema-names.md b/website/docs/guides/debug-schema-names.md similarity index 97% rename from website/docs/guides/debugging-schema-names.md rename to website/docs/guides/debug-schema-names.md index a5d8c9be5db..7f40576d7a9 100644 --- a/website/docs/guides/debugging-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -1,9 +1,9 @@ --- -title: Debugging schema names -id: debugging-schema-names +title: Debug schema names +id: debug-schema-names description: Learn how to debug schema names when models build under unexpected schemas -displayText: Debugging schema names -hoverSnippet: Learn how to debug schema names dbt. +displayText: Debug schema names +hoverSnippet: Learn how to debug schema names in dbt. # time_to_complete: '30 minutes' commenting out until we test platform: 'dbt-core' icon: 'guides' diff --git a/website/docs/guides/manual-install-qs.md b/website/docs/guides/manual-install-qs.md index e49ef5bfdf7..61796fe008a 100644 --- a/website/docs/guides/manual-install-qs.md +++ b/website/docs/guides/manual-install-qs.md @@ -2,7 +2,6 @@ title: "Quickstart for dbt Core from a manual install" id: manual-install description: "Connecting your warehouse to dbt Core using the CLI." -sidebar_label: "Manual install quickstart" level: 'Beginner' platform: 'dbt-core' icon: 'fa-light fa-square-terminal' diff --git a/website/docs/guides/migrating-from-spark-to-databricks.md b/website/docs/guides/migrate-from-spark-to-databricks.md similarity index 97% rename from website/docs/guides/migrating-from-spark-to-databricks.md rename to website/docs/guides/migrate-from-spark-to-databricks.md index 68c867e762e..5be1c08d787 100644 --- a/website/docs/guides/migrating-from-spark-to-databricks.md +++ b/website/docs/guides/migrate-from-spark-to-databricks.md @@ -1,8 +1,8 @@ --- -title: "Migrating from dbt-spark to dbt-databricks" -id: "migrating-from-spark-to-databricks" +title: "Migrate from dbt-spark to dbt-databricks" +id: "migrate-from-spark-to-databricks" description: Learn how to migrate from dbt-spark to dbt-databricks. -displayText: Migrating from Spark to Databricks +displayText: Migrate from Spark to Databricks hoverSnippet: Learn how to migrate from dbt-spark to dbt-databricks. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' diff --git a/website/docs/guides/migrating-from-stored-procedures.md b/website/docs/guides/migrate-from-stored-procedures.md similarity index 99% rename from website/docs/guides/migrating-from-stored-procedures.md rename to website/docs/guides/migrate-from-stored-procedures.md index ef86534c4af..c894bce9873 100644 --- a/website/docs/guides/migrating-from-stored-procedures.md +++ b/website/docs/guides/migrate-from-stored-procedures.md @@ -1,8 +1,8 @@ --- -title: Migrating from DDL, DML, and stored procedures -id: migrating-from-stored-procedures +title: Migrate from DDL, DML, and stored procedures +id: migrate-from-stored-procedures description: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models, including tips and patterns for the shift from a procedural to a declarative approach in defining datasets. -displayText: Migrating from DDL, DML, and stored procedures +displayText: Migrate from DDL, DML, and stored procedures hoverSnippet: Learn how to transform from a historical codebase of mixed DDL and DML statements to dbt models # time_to_complete: '30 minutes' commenting out until we test platform: 'dbt-core' diff --git a/website/docs/guides/orchestration/webhooks/serverless-datadog.md b/website/docs/guides/orchestration/webhooks/serverless-datadog.md index 6bd38869259..ebdc7419727 100644 --- a/website/docs/guides/orchestration/webhooks/serverless-datadog.md +++ b/website/docs/guides/orchestration/webhooks/serverless-datadog.md @@ -1,8 +1,14 @@ --- title: "Create Datadog events from dbt Cloud results" -id: webhooks-guide-serverless-datadog -slug: serverless-datadog +id: serverless-datadog description: Configure a serverless app to add Datadog logs +hoverSnippet: Learn how to configure a serverless app to add Datadog logs. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Intermediate' +recently_updated: true --- This guide will teach you how to build and host a basic Python app which will add dbt Cloud job events to Datadog. To do this, when a dbt Cloud job completes it will create a log entry for each node that was run, containing all information about the node provided by the [Discovery API](/docs/dbt-cloud-apis/discovery-schema-job-models). diff --git a/website/docs/guides/productionizing-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md similarity index 98% rename from website/docs/guides/productionizing-your-dbt-databricks-project.md rename to website/docs/guides/productionize-your-dbt-databricks-project.md index c7a53ebc105..ce4e73b567b 100644 --- a/website/docs/guides/productionizing-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -1,14 +1,13 @@ --- -title: Productionizing your dbt Databricks project -id: "productionizing-your-dbt-databricks-project" -sidebar_label: "Productionizing your dbt Databricks project" +title: Productionize your dbt Databricks project +id: "productionize-your-dbt-databricks-project" description: "Learn how to deliver models to end users and use best practices to maintain production data" -displayText: Productionizing your dbt Databricks project -hoverSnippet: Learn how to Productionizing your dbt Databricks project. +displayText: Productionize your dbt Databricks project +hoverSnippet: Learn how to Productionize your dbt Databricks project. # time_to_complete: '30 minutes' commenting out until we test icon: 'databricks' hide_table_of_contents: true -tags: ['Databricks', 'dbt Core','dbt Cloud'] +tags: ['Databricks','dbt Core','dbt Cloud'] level: 'Intermediate' recently_updated: true --- diff --git a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md b/website/docs/guides/set-up-your-databricks-dbt-project.md similarity index 98% rename from website/docs/guides/how-to-set-up-your-databricks-dbt-project.md rename to website/docs/guides/set-up-your-databricks-dbt-project.md index fc03c542861..e40a4182423 100644 --- a/website/docs/guides/how-to-set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/set-up-your-databricks-dbt-project.md @@ -1,7 +1,6 @@ --- -title: How to set up your Databricks and dbt project -id: how-to-set-up-your-databricks-dbt-project -sidebar_label: "How to set up your Databricks and dbt project" +title: Set up your dbt project with Databricks +id: set-up-your-databricks-dbt-project description: "Learn more about setting up your dbt project with Databricks" displayText: Setting up your dbt project with Databricks hoverSnippet: Learn how to set up your dbt project with Databricks. diff --git a/website/docs/guides/sl-migration.md b/website/docs/guides/sl-migration.md index 5f5705314e9..b0605ece333 100644 --- a/website/docs/guides/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -1,9 +1,8 @@ --- title: "Legacy dbt Semantic Layer migration guide" id: "sl-migration" -sidebar_label: "Legacy dbt Semantic Layer migration" description: "Learn how to migrate from the legacy dbt Semantic Layer to the latest one." -hoverSnippet: Learn how to migrate from the legacy dbt Semantic Layer to the latest one. +hoverSnippet: Migrate from the legacy dbt Semantic Layer to the latest one. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/using-jinja.md b/website/docs/guides/using-jinja.md index 9c604b86453..9f098bb637f 100644 --- a/website/docs/guides/using-jinja.md +++ b/website/docs/guides/using-jinja.md @@ -1,6 +1,8 @@ --- -title: "Using Jinja" +title: "Use Jinja to improve your SQL code" id: "using-jinja" +description: "Learn how to improve your SQL code using Jinja." +hoverSnippet: "Improve your SQL code with Jinja" # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true From 1fa986ce9f617f8ea8931c8c8bdc3945fc65f8a3 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:01:24 -0800 Subject: [PATCH 057/152] moving webhooks guide content --- .../orchestration/webhooks/zapier-slack.md | 10 ++- .../webhooks => }/serverless-datadog.md | 78 ++++++++++--------- .../webhooks => }/serverless-pagerduty.md | 37 +++++---- .../webhooks => }/zapier-ms-teams.md | 33 +++++--- .../webhooks => }/zapier-new-cloud-job.md | 29 ++++--- .../zapier-refresh-mode-report.md | 35 +++++---- .../zapier-refresh-tableau-workbook.md | 33 ++++---- 7 files changed, 153 insertions(+), 102 deletions(-) rename website/docs/guides/{orchestration/webhooks => }/serverless-datadog.md (70%) rename website/docs/guides/{orchestration/webhooks => }/serverless-pagerduty.md (87%) rename website/docs/guides/{orchestration/webhooks => }/zapier-ms-teams.md (92%) rename website/docs/guides/{orchestration/webhooks => }/zapier-new-cloud-job.md (89%) rename website/docs/guides/{orchestration/webhooks => }/zapier-refresh-mode-report.md (90%) rename website/docs/guides/{orchestration/webhooks => }/zapier-refresh-tableau-workbook.md (92%) diff --git a/website/docs/guides/orchestration/webhooks/zapier-slack.md b/website/docs/guides/orchestration/webhooks/zapier-slack.md index 6ce89eadd12..f4eaff89728 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-slack.md +++ b/website/docs/guides/orchestration/webhooks/zapier-slack.md @@ -1,8 +1,14 @@ --- title: "Post to Slack with error context when a job fails" -id: webhooks-guide-zapier-slack -slug: zapier-slack +id: zapier-slack description: Use Zapier and the dbt Cloud API to post error context to Slack +hoverSnippet: Learn how to use Zapier to trigger a dbt Cloud job once a run completes. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- This guide will show you how to set up an integration between dbt Cloud jobs and Slack using [dbt Cloud webhooks](/docs/deploy/webhooks) and Zapier. It builds on the native [native Slack integration](/docs/deploy/job-notifications#slack-notifications) by attaching error message details of models and tests in a thread. diff --git a/website/docs/guides/orchestration/webhooks/serverless-datadog.md b/website/docs/guides/serverless-datadog.md similarity index 70% rename from website/docs/guides/orchestration/webhooks/serverless-datadog.md rename to website/docs/guides/serverless-datadog.md index ebdc7419727..3b1a3bd6db4 100644 --- a/website/docs/guides/orchestration/webhooks/serverless-datadog.md +++ b/website/docs/guides/serverless-datadog.md @@ -1,68 +1,71 @@ --- title: "Create Datadog events from dbt Cloud results" id: serverless-datadog -description: Configure a serverless app to add Datadog logs -hoverSnippet: Learn how to configure a serverless app to add Datadog logs. +description: Configure a serverless app to add dbt Cloud events to Datadog logs +hoverSnippet: Learn how to configure a serverless app to add dbt Cloud events to Datadog logs. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true tags: ['Webhooks'] -level: 'Intermediate' +level: 'Advanced' recently_updated: true --- +## Introduction + This guide will teach you how to build and host a basic Python app which will add dbt Cloud job events to Datadog. To do this, when a dbt Cloud job completes it will create a log entry for each node that was run, containing all information about the node provided by the [Discovery API](/docs/dbt-cloud-apis/discovery-schema-job-models). In this example, we will use [fly.io](https://fly.io) for hosting/running the service. fly.io is a platform for running full stack apps without provisioning servers etc. This level of usage should comfortably fit inside of the Free tier. You can also use an alternative tool such as [AWS Lambda](https://adem.sh/blog/tutorial-fastapi-aws-lambda-serverless) or [Google Cloud Run](https://github.com/sekR4/FastAPI-on-Google-Cloud-Run). -## Prerequisites +### Prerequisites + This guide assumes some familiarity with: - [dbt Cloud Webhooks](/docs/deploy/webhooks) - CLI apps - Deploying code to a serverless code runner like fly.io or AWS Lambda -## Integration steps - -### 1. Clone the `dbt-cloud-webhooks-datadog` repo +## Clone the `dbt-cloud-webhooks-datadog` repo [This repository](https://github.com/dpguthrie/dbt-cloud-webhooks-datadog) contains the sample code for validating a webhook and creating logs in Datadog. -### 2. Install `flyctl` and sign up for fly.io +## Install `flyctl` and sign up for fly.io Follow the directions for your OS in the [fly.io docs](https://fly.io/docs/hands-on/install-flyctl/), then from your command line, run the following commands: Switch to the directory containing the repo you cloned in step 1: -```shell -#example: replace with your actual path -cd ~/Documents/GitHub/dbt-cloud-webhooks-datadog -``` + + ```shell + #example: replace with your actual path + cd ~/Documents/GitHub/dbt-cloud-webhooks-datadog + ``` Sign up for fly.io: -```shell -flyctl auth signup -``` + ```shell + flyctl auth signup + ``` Your console should show `successfully logged in as YOUR_EMAIL` when you're done, but if it doesn't then sign in to fly.io from your command line: -```shell -flyctl auth login -``` + ```shell + flyctl auth login + ``` + +## Launch your fly.io app -### 3. Launch your fly.io app Launching your app publishes it to the web and makes it ready to catch webhook events: -```shell -flyctl launch -``` + ```shell + flyctl launch + ``` -You will see a message saying that an existing `fly.toml` file was found. Type `y` to copy its configuration to your new app. +1. You will see a message saying that an existing `fly.toml` file was found. Type `y` to copy its configuration to your new app. -Choose an app name of your choosing, such as `YOUR_COMPANY-dbt-cloud-webhook-datadog`, or leave blank and one will be generated for you. Note that your name can only contain numbers, lowercase letters and dashes. +2. Choose an app name of your choosing, such as `YOUR_COMPANY-dbt-cloud-webhook-datadog`, or leave blank and one will be generated for you. Note that your name can only contain numbers, lowercase letters and dashes. -Choose a deployment region, and take note of the hostname that is generated (normally `APP_NAME.fly.dev`). +3. Choose a deployment region, and take note of the hostname that is generated (normally `APP_NAME.fly.dev`). -When asked if you would like to set up Postgresql or Redis databases, type `n` for each. +4. When asked if you would like to set up Postgresql or Redis databases, type `n` for each. -Type `y` when asked if you would like to deploy now. +5. Type `y` when asked if you would like to deploy now.
Sample output from the setup wizard: @@ -92,16 +95,16 @@ Wrote config file fly.toml
### 4. Create a Datadog API Key [Create an API Key for your Datadog account](https://docs.datadoghq.com/account_management/api-app-keys/) and make note of it and your Datadog site (e.g. `datadoghq.com`) for later. -### 5. Configure a new webhook in dbt Cloud -See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**. - -Set the webhook URL to the host name you created earlier (`APP_NAME.fly.dev`) +## Configure a new webhook in dbt Cloud -Make note of the Webhook Secret Key for later. +1. See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**. +2. Set the webhook URL to the host name you created earlier (`APP_NAME.fly.dev`) +3. Make note of the Webhook Secret Key for later. *Do not test the endpoint*; it won't work until you have stored the auth keys (next step) -### 6. Store secrets +## Store secrets + The application requires four secrets to be set, using these names: - `DBT_CLOUD_SERVICE_TOKEN`: a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens) with at least the `Metdata Only` permission. - `DBT_CLOUD_AUTH_TOKEN`: the Secret Key for the dbt Cloud webhook you created earlier. @@ -109,9 +112,10 @@ The application requires four secrets to be set, using these names: - `DD_SITE`: The Datadog site for your organisation, e.g. `datadoghq.com`. Set these secrets as follows, replacing `abc123` etc with actual values: -```shell -flyctl secrets set DBT_CLOUD_SERVICE_TOKEN=abc123 DBT_CLOUD_AUTH_TOKEN=def456 DD_API_KEY=ghi789 DD_SITE=datadoghq.com -``` + ```shell + flyctl secrets set DBT_CLOUD_SERVICE_TOKEN=abc123 DBT_CLOUD_AUTH_TOKEN=def456 DD_API_KEY=ghi789 DD_SITE=datadoghq.com + ``` + +## Deploy your app -### 7. Deploy your app After you set your secrets, fly.io will redeploy your application. When it has completed successfully, go back to the dbt Cloud webhook settings and click **Test Endpoint**. diff --git a/website/docs/guides/orchestration/webhooks/serverless-pagerduty.md b/website/docs/guides/serverless-pagerduty.md similarity index 87% rename from website/docs/guides/orchestration/webhooks/serverless-pagerduty.md rename to website/docs/guides/serverless-pagerduty.md index 5455af60110..31436221be5 100644 --- a/website/docs/guides/orchestration/webhooks/serverless-pagerduty.md +++ b/website/docs/guides/serverless-pagerduty.md @@ -1,10 +1,18 @@ --- -title: "Create PagerDuty alarms from failed dbt Cloud tasks" -id: webhooks-guide-serverless-pagerduty -slug: serverless-pagerduty -description: Configure a serverless app to create PagerDuty alarms +title: "Trigger PagerDuty alarms when dbt Cloud jobs fail" +id: serverless-pagerduty +description: Use webhooks to configure a serverless app to trigger PagerDuty alarms +hoverSnippet: Learn how to configure a serverless app that uses webhooks to trigger PagerDuty alarms. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction + This guide will teach you how to build and host a basic Python app which will monitor dbt Cloud jobs and create PagerDuty alarms based on failure. To do this, when a dbt Cloud job completes it will: - Check for any failed nodes (e.g. non-passing tests or errored models), and - create a PagerDuty alarm based on those nodes by calling the PagerDuty Events API. Events are deduplicated per run ID. @@ -13,20 +21,20 @@ This guide will teach you how to build and host a basic Python app which will mo In this example, we will use fly.io for hosting/running the service. fly.io is a platform for running full stack apps without provisioning servers etc. This level of usage should comfortably fit inside of the Free tier. You can also use an alternative tool such as [AWS Lambda](https://adem.sh/blog/tutorial-fastapi-aws-lambda-serverless) or [Google Cloud Run](https://github.com/sekR4/FastAPI-on-Google-Cloud-Run). -## Prerequisites +### Prerequisites + This guide assumes some familiarity with: - [dbt Cloud Webhooks](/docs/deploy/webhooks) - CLI apps - Deploying code to a serverless code runner like fly.io or AWS Lambda -## Integration steps -### 1. Clone the `dbt-cloud-webhooks-pagerduty` repo +## Clone the `dbt-cloud-webhooks-pagerduty` repo [This repository](https://github.com/dpguthrie/dbt-cloud-webhooks-pagerduty) contains the sample code for validating a webhook and creating events in PagerDuty. -### 2. Install `flyctl` and sign up for fly.io +## Install `flyctl` and sign up for fly.io Follow the directions for your OS in the [fly.io docs](https://fly.io/docs/hands-on/install-flyctl/), then from your command line, run the following commands: @@ -46,7 +54,7 @@ Your console should show `successfully logged in as YOUR_EMAIL` when you're done flyctl auth login ``` -### 3. Launch your fly.io app +## Launch your fly.io app Launching your app publishes it to the web and makes it ready to catch webhook events: ```shell flyctl launch @@ -87,12 +95,12 @@ Wrote config file fly.toml
-### 4. Create a PagerDuty integration application +## Create a PagerDuty integration application See [PagerDuty's guide](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview#getting-started) for full instructions. Make note of the integration key for later. -### 5. Configure a new webhook in dbt Cloud +## Configure a new webhook in dbt Cloud See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**. Set the webhook URL to the host name you created earlier (`APP_NAME.fly.dev`) @@ -101,7 +109,7 @@ Make note of the Webhook Secret Key for later. *Do not test the endpoint*; it won't work until you have stored the auth keys (next step) -### 6. Store secrets +## Store secrets The application requires three secrets to be set, using these names: - `DBT_CLOUD_SERVICE_TOKEN`: a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens) with at least the `Metdata Only` permission. - `DBT_CLOUD_AUTH_TOKEN`: the Secret Key for the dbt Cloud webhook you created earlier. @@ -112,5 +120,6 @@ Set these secrets as follows, replacing `abc123` etc with actual values: flyctl secrets set DBT_CLOUD_SERVICE_TOKEN=abc123 DBT_CLOUD_AUTH_TOKEN=def456 PD_ROUTING_KEY=ghi789 ``` -### 7. Deploy your app -After you set your secrets, fly.io will redeploy your application. When it has completed successfully, go back to the dbt Cloud webhook settings and click **Test Endpoint**. \ No newline at end of file +## Deploy your app + +After you set your secrets, fly.io will redeploy your application. When it has completed successfully, go back to the dbt Cloud webhook settings and click **Test Endpoint**. diff --git a/website/docs/guides/orchestration/webhooks/zapier-ms-teams.md b/website/docs/guides/zapier-ms-teams.md similarity index 92% rename from website/docs/guides/orchestration/webhooks/zapier-ms-teams.md rename to website/docs/guides/zapier-ms-teams.md index 148e16b2469..cb62a0edc33 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-ms-teams.md +++ b/website/docs/guides/zapier-ms-teams.md @@ -2,8 +2,16 @@ title: "Post to Microsoft Teams when a job finishes" id: webhooks-guide-zapier-ms-teams slug: zapier-ms-teams -description: Use Zapier and the dbt Cloud API to post to Microsoft Teams +description: Use Zapier and dbt Cloud webhooks to post to Microsoft Teams when a job finishes running +hoverSnippet: Learn how to use Zapier with dbt Cloud webhooks to post in Microsoft Teams when a job finishes running. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction This guide will show you how to set up an integration between dbt Cloud jobs and Microsoft Teams using [dbt Cloud Webhooks](/docs/deploy/webhooks) and Zapier, similar to the [native Slack integration](/docs/deploy/job-notifications#slack-notifications). @@ -14,19 +22,20 @@ When a dbt Cloud job finishes running, the integration will: - Post a summary to a Microsoft Teams channel. ![Screenshot of a message in MS Teams showing a summary of a dbt Cloud run which failed](/img/guides/orchestration/webhooks/zapier-ms-teams/ms-teams-ui.png) -## Prerequisites + +### Prerequisites In order to set up the integration, you should have familiarity with: - [dbt Cloud Webhooks](/docs/deploy/webhooks) - Zapier -## Integration steps -### 1. Set up the connection between Zapier and Microsoft Teams + +## Set up the connection between Zapier and Microsoft Teams * Install the [Zapier app in Microsoft Teams](https://appsource.microsoft.com/en-us/product/office/WA200002044) and [grant Zapier access to your account](https://zapier.com/blog/how-to-automate-microsoft-teams/). **Note**: To receive the message, add the Zapier app to the team's channel during installation. -### 2. Create a new Zap in Zapier +## Create a new Zap in Zapier Use **Webhooks by Zapier** as the Trigger, and **Catch Raw Hook** as the Event. If you don't intend to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook) (not recommended!) then you can choose **Catch Hook** instead. Press **Continue**, then copy the webhook URL. @@ -34,6 +43,7 @@ Press **Continue**, then copy the webhook URL. ![Screenshot of the Zapier UI, showing the webhook URL ready to be copied](/img/guides/orchestration/webhooks/zapier-common/catch-raw-hook.png) ### 3. Configure a new webhook in dbt Cloud + See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Choose either **Run completed** or **Run errored**, but not both, or you'll get double messages when a run fails. Make note of the Webhook Secret Key for later. @@ -42,14 +52,15 @@ Once you've tested the endpoint in dbt Cloud, go back to Zapier and click **Test The sample body's values are hard-coded and not reflective of your project, but they give Zapier a correctly-shaped object during development. -### 4. Store secrets +## Store secrets + In the next step, you will need the Webhook Secret Key from the prior step, and a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens). Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps), which prevents your keys from being displayed in plaintext in the Zap code. You will be able to access them via the [StoreClient utility](https://help.zapier.com/hc/en-us/articles/8496293969549-Store-data-from-code-steps-with-StoreClient). -### 5. Add a code action +## Add a code action Select **Code by Zapier** as the App, and **Run Python** as the Event. In the **Set up action** area, add two items to **Input Data**: `raw_body` and `auth_header`. Map those to the `1. Raw Body` and `1. Headers Http Authorization` fields from the **Catch Raw Hook** step above. @@ -141,19 +152,21 @@ for step in run_data_results['run_steps']: output = {'outcome_message': outcome_message} ``` -### 6. Add the Microsoft Teams action +## Add the Microsoft Teams action + Select **Microsoft Teams** as the App, and **Send Channel Message** as the Action. In the **Set up action** area, choose the team and channel. Set the **Message Text Format** to **markdown**, then put **2. Outcome Message** from the Run Python in Code by Zapier output into the **Message Text** field. ![Screenshot of the Zapier UI, showing the mappings of prior steps to an MS Teams message](/img/guides/orchestration/webhooks/zapier-ms-teams/ms-teams-zap-config.png) -### 7. Test and deploy +## Test and deploy + As you have gone through each step, you should have tested the outputs, so you can now try posting a message into your Teams channel. When you're happy with it, remember to ensure that your `run_id` and `account_id` are no longer hardcoded, then publish your Zap. -## Other notes +### Other notes - If you post to a chat instead of a team channel, you don't need to add the Zapier app to Microsoft Teams. - If you post to a chat instead of a team channel, note that markdown is not supported and you will need to remove the markdown formatting. - If you chose the **Catch Hook** trigger instead of **Catch Raw Hook**, you will need to pass each required property from the webhook as an input instead of running `json.loads()` against the raw body. You will also need to remove the validation code. diff --git a/website/docs/guides/orchestration/webhooks/zapier-new-cloud-job.md b/website/docs/guides/zapier-new-cloud-job.md similarity index 89% rename from website/docs/guides/orchestration/webhooks/zapier-new-cloud-job.md rename to website/docs/guides/zapier-new-cloud-job.md index 0764c6c7911..b16fa94bc21 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-new-cloud-job.md +++ b/website/docs/guides/zapier-new-cloud-job.md @@ -1,28 +1,34 @@ --- title: "Trigger a dbt Cloud job after a run finishes" -id: webhooks-guide-zapier-new-cloud-job -slug: zapier-new-cloud-job -description: Use Zapier to interact with the dbt Cloud API +id: zapier-new-cloud-job +description: Use Zapier to trigger a dbt Cloud job once a run completes. +hoverSnippet: Learn how to use Zapier to trigger a dbt Cloud job once a run completes. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction + This guide will show you how to trigger a dbt Cloud job based on the successful completion of a different job. This can be useful when you need to trigger a job in a different project. Remember that dbt works best when it understands the whole context of the it has been asked to run, so use this ability judiciously. -## Prerequisites +### Prerequisites In order to set up the integration, you should have familiarity with: - [dbt Cloud Webhooks](/docs/deploy/webhooks) - Zapier -## Integration steps - -### 1. Create a new Zap in Zapier +## Create a new Zap in Zapier Use **Webhooks by Zapier** as the Trigger, and **Catch Raw Hook** as the Event. If you don't intend to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook) (not recommended!) then you can choose **Catch Hook** instead. Press **Continue**, then copy the webhook URL. ![Screenshot of the Zapier UI, showing the webhook URL ready to be copied](/img/guides/orchestration/webhooks/zapier-common/catch-raw-hook.png) -### 2. Configure a new webhook in dbt Cloud +## Configure a new webhook in dbt Cloud See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**, and you need to change the **Jobs** list to only contain the job you want to trigger the next run. Make note of the Webhook Secret Key for later. @@ -31,14 +37,14 @@ Once you've tested the endpoint in dbt Cloud, go back to Zapier and click **Test The sample body's values are hard-coded and not reflective of your project, but they give Zapier a correctly-shaped object during development. -### 3. Store secrets +## Store secrets In the next step, you will need the Webhook Secret Key from the prior step, and a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens). Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps), which prevents your keys from being displayed in plaintext in the Zap code. You will be able to access them via the [StoreClient utility](https://help.zapier.com/hc/en-us/articles/8496293969549-Store-data-from-code-steps-with-StoreClient). -### 4. Add a code action +## Add a code action Select **Code by Zapier** as the App, and **Run Python** as the Event. In the **Set up action** area, add two items to **Input Data**: `raw_body` and `auth_header`. Map those to the `1. Raw Body` and `1. Headers Http Authorization` fields from the **Catch Raw Hook** step above. @@ -87,5 +93,6 @@ if hook_data['runStatus'] == "Success": return ``` -### 5. Test and deploy +## Test and deploy + When you're happy with it, remember to ensure that your `account_id` is no longer hardcoded, then publish your Zap. diff --git a/website/docs/guides/orchestration/webhooks/zapier-refresh-mode-report.md b/website/docs/guides/zapier-refresh-mode-report.md similarity index 90% rename from website/docs/guides/orchestration/webhooks/zapier-refresh-mode-report.md rename to website/docs/guides/zapier-refresh-mode-report.md index f682baae8e2..0ffcec9c96d 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-refresh-mode-report.md +++ b/website/docs/guides/zapier-refresh-mode-report.md @@ -1,10 +1,18 @@ --- title: "Refresh a Mode dashboard when a job completes" -id: webhooks-guide-zapier-refresh-mode-report -slug: zapier-refresh-mode-report -description: Use Zapier to trigger a Mode dashboard refresh +id: zapier-refresh-mode-report +description: Use Zapier to trigger a Mode dashboard refresh when a dbt Cloud job completes +hoverSnippet: Learn how to use Zapier to trigger a Mode dashboard refresh when a dbt Cloud job completes. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction + This guide will teach you how to refresh a Mode dashboard when a dbt Cloud job has completed successfully and there is fresh data available. The integration will: - Receive a webhook notification in Zapier @@ -12,23 +20,21 @@ This guide will teach you how to refresh a Mode dashboard when a dbt Cloud job h Although we are using the Mode API for a concrete example, the principles are readily transferrable to your [tool](https://learn.hex.tech/docs/develop-logic/hex-api/api-reference#operation/RunProject) [of](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset) [choice](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_workbook_now). -## Prerequisites +### Prerequisites In order to set up the integration, you should have familiarity with: - [dbt Cloud Webhooks](/docs/deploy/webhooks) - Zapier - The [Mode API](https://mode.com/developer/api-reference/introduction/) -## Integration steps - -### 1. Create a new Zap in Zapier +## Create a new Zap in Zapier Use **Webhooks by Zapier** as the Trigger, and **Catch Raw Hook** as the Event. If you don't intend to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook) (not recommended!) then you can choose **Catch Hook** instead. Press **Continue**, then copy the webhook URL. ![Screenshot of the Zapier UI, showing the webhook URL ready to be copied](/img/guides/orchestration/webhooks/zapier-common/catch-raw-hook.png) -### 2. Configure a new webhook in dbt Cloud +## Configure a new webhook in dbt Cloud See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**, and you need to change the **Jobs** list to only contain any jobs whose completion should trigger a report refresh. Make note of the Webhook Secret Key for later. @@ -37,20 +43,19 @@ Once you've tested the endpoint in dbt Cloud, go back to Zapier and click **Test The sample body's values are hard-coded and not reflective of your project, but they give Zapier a correctly-shaped object during development. -### 3. Store secrets +## Store secrets In the next step, you will need the Webhook Secret Key from the prior step, and a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens), as well as a [Mode API token and secret](https://mode.com/developer/api-reference/authentication/). Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps), which prevents your keys from being displayed in plaintext in the Zap code. You will be able to access them via the [StoreClient utility](https://help.zapier.com/hc/en-us/articles/8496293969549-Store-data-from-code-steps-with-StoreClient). - This guide assumes the names for the secret keys are: `DBT_WEBHOOK_KEY`, `MODE_API_TOKEN`, and `MODE_API_SECRET`. If you are using different names, make sure you update all references to them in the sample code. This guide uses a short-lived code action to store the secrets, but you can also use a tool like Postman to interact with the [REST API](https://store.zapier.com/) or create a separate Zap and call the [Set Value Action](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps#3-set-a-value-in-your-store-0-3). -#### a. Create a Storage by Zapier connection +### a. Create a Storage by Zapier connection If you haven't already got one, go to and create a new connection. Remember the UUID secret you generate for later. -#### b. Add a temporary code step +### b. Add a temporary code step Choose **Run Python** as the Event. Run the following code: ```python store = StoreClient('abc123') #replace with your UUID secret @@ -60,7 +65,7 @@ store.set('MODE_API_SECRET', 'abc123') #replace with your Mode API Secret ``` Test the step. You can delete this Action when the test succeeds. The key will remain stored as long as it is accessed at least once every three months. -### 4. Add a code action +## Add a code action Select **Code by Zapier** as the App, and **Run Python** as the Event. In the **Set up action** area, add two items to **Input Data**: `raw_body` and `auth_header`. Map those to the `1. Raw Body` and `1. Headers Http Authorization` fields from the **Catch Raw Hook** step above. @@ -124,5 +129,5 @@ if hook_data['runStatus'] == "Success": return ``` -### 5. Test and deploy -You can iterate on the Code step by modifying the code and then running the test again. When you're happy with it, you can publish your Zap. \ No newline at end of file +## Test and deploy +You can iterate on the Code step by modifying the code and then running the test again. When you're happy with it, you can publish your Zap. diff --git a/website/docs/guides/orchestration/webhooks/zapier-refresh-tableau-workbook.md b/website/docs/guides/zapier-refresh-tableau-workbook.md similarity index 92% rename from website/docs/guides/orchestration/webhooks/zapier-refresh-tableau-workbook.md rename to website/docs/guides/zapier-refresh-tableau-workbook.md index 52a9ae63523..6e8621659f0 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-refresh-tableau-workbook.md +++ b/website/docs/guides/zapier-refresh-tableau-workbook.md @@ -1,16 +1,24 @@ --- title: "Refresh Tableau workbook with extracts after a job finishes" -id: webhooks-guide-zapier-refresh-tableau-workbook -slug: zapier-refresh-tableau-workbook -description: Use Zapier to trigger a Tableau workbook refresh +id: zapier-refresh-tableau-workbook +description: Use Zapier to trigger a Tableau workbook refresh once a dbt Cloud job completes successfully +hoverSnippet: Learn how to use Zapier to trigger a Tableau workbook refresh once a dbt Cloud job completes successfully. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction + This guide will teach you how to refresh a Tableau workbook that leverages [extracts](https://help.tableau.com/current/pro/desktop/en-us/extracting_data.htm) when a dbt Cloud job has completed successfully and there is fresh data available. The integration will: - Receive a webhook notification in Zapier - Trigger a refresh of a Tableau workbook -## Prerequisites +### Prerequisites To set up the integration, you need to be familiar with: @@ -19,19 +27,18 @@ To set up the integration, you need to be familiar with: - The [Tableau API](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api.htm) - The [version](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_versions.htm#rest_api_versioning) of Tableau's REST API that is compatible with your server -## Integration steps -### 1. Obtain authentication credentials from Tableau +## Obtain authentication credentials from Tableau To authenticate with the Tableau API, obtain a [Personal Access Token](https://help.tableau.com/current/server/en-us/security_personal_access_tokens.htm) from your Tableau Server/Cloud instance. In addition, make sure your Tableau workbook uses data sources that allow refresh access, which is usually set when publishing. -### 2. Create a new Zap in Zapier +## Create a new Zap in Zapier To trigger an action with the delivery of a webhook in Zapier, you'll want to create a new Zap with **Webhooks by Zapier** as the Trigger and **Catch Raw Hook** as the Event. However, if you choose not to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook), which isn't recommended, you can choose **Catch Hook** instead. Press **Continue**, then copy the webhook URL. ![Screenshot of the Zapier UI, showing the webhook URL ready to be copied](/img/guides/orchestration/webhooks/zapier-common/catch-raw-hook.png) -### 3. Configure a new webhook in dbt Cloud +## Configure a new webhook in dbt Cloud To set up a webhook subscription for dbt Cloud, follow the instructions in [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription). For the event, choose **Run completed** and modify the **Jobs** list to include only the jobs that should trigger a report refresh. Remember to save the Webhook Secret Key for later. Paste in the webhook URL obtained from Zapier in step 2 into the **Endpoint** field and test the endpoint. @@ -40,7 +47,7 @@ Once you've tested the endpoint in dbt Cloud, go back to Zapier and click **Test The sample body's values are hard-coded and not reflective of your project, but they give Zapier a correctly-shaped object during development. -### 4. Store secrets +## Store secrets In the next step, you will need the Webhook Secret Key from the prior step, and your Tableau authentication credentials and details. Specifically, you'll need your Tableau server/site URL, server/site name, PAT name, and PAT secret. Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps), which prevents your keys from being displayed in plaintext in the Zap code. You will be able to access them via the [StoreClient utility](https://help.zapier.com/hc/en-us/articles/8496293969549-Store-data-from-code-steps-with-StoreClient). @@ -49,11 +56,11 @@ This guide assumes the names for the secret keys are: `DBT_WEBHOOK_KEY`, `TABLEA This guide uses a short-lived code action to store the secrets, but you can also use a tool like Postman to interact with the [REST API](https://store.zapier.com/) or create a separate Zap and call the [Set Value Action](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps#3-set-a-value-in-your-store-0-3). -#### a. Create a Storage by Zapier connection +### a. Create a Storage by Zapier connection Create a new connection at https://zapier.com/app/connections/storage if you don't already have one and remember the UUID secret you generate for later. -#### b. Add a temporary code step +### b. Add a temporary code step Choose **Run Python** as the Event and input the following code: @@ -68,7 +75,7 @@ store.set('TABLEAU_API_TOKEN_SECRET', 'abc123') #replace with your Tableau API S Test the step to run the code. You can delete this action when the test succeeds. The keys will remain stored as long as it is accessed at least once every three months. -### 5. Add a code action +## Add a code action Select **Code by Zapier** as the App, and **Run Python** as the Event. In the **Set up action** area, add two items to **Input Data**: `raw_body` and `auth_header`. Map those to the `1. Raw Body` and `1. Headers Http Authorization` fields from the **Catch Raw Hook** step above. @@ -161,5 +168,5 @@ refresh_trigger = requests.post(refresh_url, data=json.dumps(refresh_data), head return {"message": "Workbook refresh has been queued"} ``` -### 6. Test and deploy +## Test and deploy To make changes to your code, you can modify it and test it again. When you're happy with it, you can publish your Zap. From e032c5b0df8fb13d3970c028a46f08e5ee03a455 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:22:00 -0800 Subject: [PATCH 058/152] movign more guide content --- .../1-overview-dbt-python-snowpark.md | 20 +++++++++---- .../webhooks => }/zapier-slack.md | 29 ++++++++++--------- 2 files changed, 30 insertions(+), 19 deletions(-) rename website/docs/guides/{orchestration/webhooks => }/zapier-slack.md (95%) diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md index b03cb2ca013..b3843d49922 100644 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md +++ b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md @@ -1,31 +1,40 @@ --- title: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" -id: "1-overview-dbt-python-snowpark" +id: "dbt-python-snowpark" description: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" +hoverSnippet: Learn how to use a webhook or Slack message to trigger Zapier to post error context in Slack when a job fails. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Webhooks'] +level: 'Advanced' +recently_updated: true --- +## Introduction + The focus of this workshop will be to demonstrate how we can use both *SQL and python together* in the same workflow to run *both analytics and machine learning models* on dbt Cloud. All code in today’s workshop can be found on [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1). -## What you'll use during the lab +### What you'll use during the lab - A [Snowflake account](https://trial.snowflake.com/) with ACCOUNTADMIN access - A [dbt Cloud account](https://www.getdbt.com/signup/) -## What you'll learn +### What you'll learn - How to build scalable data transformation pipelines using dbt, and Snowflake using SQL and Python - How to leverage copying data into Snowflake from a public S3 bucket -## What you need to know +### What you need to know - Basic to intermediate SQL and python. - Basic understanding of dbt fundamentals. We recommend the [dbt Fundamentals course](https://courses.getdbt.com/collections) if you're interested. - High level machine learning process (encoding, training, testing) - Simple ML algorithms — we will use logistic regression to keep the focus on the *workflow*, not algorithms! -## What you'll build +### What you'll build - A set of data analytics and prediction pipelines using Formula 1 data leveraging dbt and Snowflake, making use of best practices like data quality tests and code promotion between environments - We will create insights for: @@ -36,3 +45,4 @@ All code in today’s workshop can be found on [GitHub](https://github.com/dbt-l As inputs, we are going to leverage Formula 1 datasets hosted on a dbt Labs public S3 bucket. We will create a Snowflake Stage for our CSV files then use Snowflake’s `COPY INTO` function to copy the data in from our CSV files into tables. The Formula 1 is available on [Kaggle](https://www.kaggle.com/datasets/rohanrao/formula-1-world-championship-1950-2020). The data is originally compiled from the [Ergast Developer API](http://ergast.com/mrd/). Overall we are going to set up the environments, build scalable pipelines in dbt, establish data tests, and promote code to production. + diff --git a/website/docs/guides/orchestration/webhooks/zapier-slack.md b/website/docs/guides/zapier-slack.md similarity index 95% rename from website/docs/guides/orchestration/webhooks/zapier-slack.md rename to website/docs/guides/zapier-slack.md index f4eaff89728..d103e4aa541 100644 --- a/website/docs/guides/orchestration/webhooks/zapier-slack.md +++ b/website/docs/guides/zapier-slack.md @@ -1,8 +1,8 @@ --- title: "Post to Slack with error context when a job fails" id: zapier-slack -description: Use Zapier and the dbt Cloud API to post error context to Slack -hoverSnippet: Learn how to use Zapier to trigger a dbt Cloud job once a run completes. +description: Use a webhook or Slack message to trigger Zapier and post error context in Slack when a job fails +hoverSnippet: Learn how to use a webhook or Slack message to trigger Zapier to post error context in Slack when a job fails. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true @@ -11,6 +11,8 @@ level: 'Advanced' recently_updated: true --- +## Introduction + This guide will show you how to set up an integration between dbt Cloud jobs and Slack using [dbt Cloud webhooks](/docs/deploy/webhooks) and Zapier. It builds on the native [native Slack integration](/docs/deploy/job-notifications#slack-notifications) by attaching error message details of models and tests in a thread. Note: Because there is not a webhook for Run Cancelled, you may want to keep the standard Slack integration installed to receive those notifications. You could also use the [alternative integration](#alternate-approach) that augments the native integration without replacing it. @@ -23,21 +25,20 @@ When a dbt Cloud job finishes running, the integration will: - Create a threaded message attached to that post which contains any reasons that the job failed ![Screenshot of a message in Slack showing a summary of a dbt Cloud run which failed](/img/guides/orchestration/webhooks/zapier-slack/slack-thread-example.png) -## Prerequisites + +### Prerequisites In order to set up the integration, you should have familiarity with: - [dbt Cloud webhooks](/docs/deploy/webhooks) - Zapier -## Integration steps - -### 1. Create a new Zap in Zapier -Use **Webhooks by Zapier** as the Trigger, and **Catch Raw Hook** as the Event. If you don't intend to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook) (not recommended!) then you can choose **Catch Hook** instead. -Click **Continue**, then copy the webhook URL. +## Create a new Zap in Zapier +1. Use **Webhooks by Zapier** as the Trigger, and **Catch Raw Hook** as the Event. If you don't intend to [validate the authenticity of your webhook](/docs/deploy/webhooks#validate-a-webhook) (not recommended!) then you can choose **Catch Hook** instead. +2. Click **Continue**, then copy the webhook URL. ![Screenshot of the Zapier UI, showing the webhook URL ready to be copied](/img/guides/orchestration/webhooks/zapier-common/catch-raw-hook.png) -### 2. Configure a new webhook in dbt Cloud +## Configure a new webhook in dbt Cloud See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Choose **Run completed** as the Event. You can alternatively choose **Run errored**, but you will need to account for the fact that the necessary metadata [might not be available immediately](/docs/deploy/webhooks#completed-errored-event-difference). Remember the Webhook Secret Key for later. @@ -46,7 +47,7 @@ Once you've tested the endpoint in dbt Cloud, go back to Zapier and click **Test The sample body's values are hardcoded and not reflective of your project, but they give Zapier a correctly-shaped object during development. -### 3. Store secrets +## Store secrets In the next step, you will need the Webhook Secret Key from the prior step, and a dbt Cloud [user token](https://docs.getdbt.com/docs/dbt-cloud-apis/user-tokens) or [service account token](https://docs.getdbt.com/docs/dbt-cloud-apis/service-tokens). Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8496293271053-Save-and-retrieve-data-from-Zaps). This prevents your keys from being displayed as plaintext in the Zap code. You can access them with the [StoreClient utility](https://help.zapier.com/hc/en-us/articles/8496293969549-Store-data-from-code-steps-with-StoreClient). @@ -54,7 +55,7 @@ Zapier allows you to [store secrets](https://help.zapier.com/hc/en-us/articles/8 -### 4. Add a code action +## Add a code action Select **Code by Zapier** as the App, and **Run Python** as the Event. In the **Set up action** section, add two items to **Input Data**: `raw_body` and `auth_header`. Map those to the `1. Raw Body` and `1. Headers Http Authorization` fields from the previous **Catch Raw Hook** step. @@ -159,7 +160,7 @@ send_error_thread = len(threaded_errors_post) > 0 output = {'step_summary_post': step_summary_post, 'send_error_thread': send_error_thread, 'threaded_errors_post': threaded_errors_post} ``` -### 5. Add Slack actions in Zapier +## Add Slack actions in Zapier Select **Slack** as the App, and **Send Channel Message** as the Action. In the **Action** section, choose which **Channel** to post to. Set the **Message Text** field to **2. Step Summary Post** from the Run Python in Code by Zapier output. @@ -176,11 +177,11 @@ Add another **Send Channel Message in Slack** action. In the **Action** section, ![Screenshot of the Zapier UI, showing the mappings of prior steps to a Slack message](/img/guides/orchestration/webhooks/zapier-slack/thread-slack-config.png) -### 7. Test and deploy +## Test and deploy When you're done testing your Zap, make sure that your `run_id` and `account_id` are no longer hardcoded in the Code step, then publish your Zap. -## Alternate approach +## Alternately, use a dbt Cloud app Slack message to trigger Zapier Instead of using a webhook as your trigger, you can keep the existing dbt Cloud app installed in your Slack workspace and use its messages being posted to your channel as the trigger. In this case, you can skip validating the webhook and only need to load the context from the thread. From 7c98a9856df3da1291974f7ff8a5cd0ffd9350e8 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:45:25 -0800 Subject: [PATCH 059/152] movign snowpark --- .../dbt-python-snowpark/1-overview-dbt-python-snowpark.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md index b3843d49922..6fb436c5bc4 100644 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md +++ b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md @@ -46,3 +46,4 @@ As inputs, we are going to leverage Formula 1 datasets hosted on a dbt Labs publ Overall we are going to set up the environments, build scalable pipelines in dbt, establish data tests, and promote code to production. +;' From a16454eeb692d82d162d422f45e472d9c18e02ed Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:11:13 -0800 Subject: [PATCH 060/152] move more guides --- .../1-what-are-adapters.md | 100 - .../2-prerequisites-for-a-new-adapter.md | 46 - .../3-building-a-new-adapter.md | 411 ---- .../4-testing-a-new-adapter.md | 494 ----- .../adapter-development/adapter-development | 1 - .../adapter-development/create-adapters.md | 1066 +++++++++ .../1-overview-dbt-python-snowpark.md | 49 - .../10-python-transformations.md | 150 -- .../11-machine-learning-prep.md | 225 -- .../12-machine-learning-training-testing.md | 251 --- .../dbt-python-snowpark/13-testing.md | 136 -- .../dbt-python-snowpark/14-documentation.md | 29 - .../dbt-python-snowpark/15-deployment.md | 50 - .../2-snowflake-configuration.md | 27 - .../3-connect-to-data-source.md | 192 -- .../dbt-python-snowpark/4-configure-dbt.md | 27 - .../5-development-schema-name.md | 46 - .../6-foundational-structure.md | 80 - .../dbt-python-snowpark/7-folder-structure.md | 27 - .../8-sources-and-staging.md | 334 --- .../9-sql-transformations.md | 299 --- website/docs/guides/dbt-python-snowpark.md | 1946 +++++++++++++++++ .../sl-partner-integration-guide.md | 17 +- 23 files changed, 3025 insertions(+), 2978 deletions(-) delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/1-what-are-adapters.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/adapter-development create mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-testing.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/13-testing.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/15-deployment.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md delete mode 100644 website/docs/guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations.md create mode 100644 website/docs/guides/dbt-python-snowpark.md rename website/docs/guides/{dbt-ecosystem => }/sl-partner-integration-guide.md (97%) diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/1-what-are-adapters.md b/website/docs/guides/dbt-ecosystem/adapter-development/1-what-are-adapters.md deleted file mode 100644 index 0959dbee707..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/1-what-are-adapters.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: "What are adapters? Why do we need them?" -id: "1-what-are-adapters" ---- - -Adapters are an essential component of dbt. At their most basic level, they are how dbt Core connects with the various supported data platforms. At a higher-level, dbt Core adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt Core. - - Navigating and developing around the nuances of different databases can be daunting, but you are not alone. Visit [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel for additional help beyond the documentation. - -## All databases are not the same - -There's a tremendous amount of work that goes into creating a database. Here is a high-level list of typical database layers (from the outermost layer moving inwards): -- SQL API -- Client Library / Driver -- Server Connection Manager -- Query parser -- Query optimizer -- Runtime -- Storage Access Layer -- Storage - -There's a lot more there than just SQL as a language. Databases (and data warehouses) are so popular because you can abstract away a great deal of the complexity from your brain to the database itself. This enables you to focus more on the data. - -dbt allows for further abstraction and standardization of the outermost layers of a database (SQL API, client library, connection manager) into a framework that both: - - Opens database technology to less technical users (a large swath of a DBA's role has been automated, similar to how the vast majority of folks with websites today no longer have to be "[webmasters](https://en.wikipedia.org/wiki/Webmaster)"). - - Enables more meaningful conversations about how data warehousing should be done. - -This is where dbt adapters become critical. - -## What needs to be adapted? - -dbt adapters are responsible for _adapting_ dbt's standard functionality to a particular database. Our prototypical database and adapter are PostgreSQL and dbt-postgres, and most of our adapters are somewhat based on the functionality described in dbt-postgres. - -Connecting dbt to a new database will require a new adapter to be built or an existing adapter to be extended. - -The outermost layers of a database map roughly to the areas in which the dbt adapter framework encapsulates inter-database differences. - -### SQL API - -Even amongst ANSI-compliant databases, there are differences in the SQL grammar. -Here are some categories and examples of SQL statements that can be constructed differently: - - -| Category | Area of differences | Examples | -|----------------------------------------------|--------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Statement syntax | The use of `IF EXISTS` |
  • `IF EXISTS, DROP TABLE`
  • `DROP
  • IF EXISTS` | -| Workflow definition & semantics | Incremental updates |
  • `MERGE`
  • `DELETE; INSERT`
  • | -| Relation and column attributes/configuration | Database-specific materialization configs |
  • `DIST = ROUND_ROBIN` (Synapse)
  • `DIST = EVEN` (Redshift)
  • | -| Permissioning | Grant statements that can only take one grantee at a time vs those that accept lists of grantees |
  • `grant SELECT on table dinner.corn to corn_kid, everyone`
  • `grant SELECT on table dinner.corn to corn_kid; grant SELECT on table dinner.corn to everyone`
  • | - -### Python Client Library & Connection Manager - -The other big category of inter-database differences comes with how the client connects to the database and executes queries against the connection. To integrate with dbt, a data platform must have a pre-existing python client library or support ODBC, using a generic python library like pyodbc. - -| Category | Area of differences | Examples | -|------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------| -| Credentials & authentication | Authentication |
  • Username & password
  • MFA with `boto3` or Okta token
  • | -| Connection opening/closing | Create a new connection to db |
  • `psycopg2.connect(connection_string)`
  • `google.cloud.bigquery.Client(...)`
  • | -| Inserting local data | Load seed .`csv` files into Python memory |
  • `google.cloud.bigquery.Client.load_table_from_file(...)` (BigQuery)
  • `INSERT ... INTO VALUES ...` prepared statement (most other databases)
  • | - - -## How dbt encapsulates and abstracts these differences - -Differences between databases are encoded into discrete areas: - -| Components | Code Path | Function | -|------------------|---------------------------------------------------|-------------------------------------------------------------------------------| -| Python Classes | `adapters/` | Configuration (See above [Python classes](##python classes) | -| Macros | `include//macros/adapters/` | SQL API & statement syntax (for example, how to create schema or how to get table info) | -| Materializations | `include//macros/materializations/` | Table/view/snapshot/ workflow definitions | - - -### Python Classes - -These classes implement all the methods responsible for: -- Connecting to a database and issuing queries. -- Providing dbt with database-specific configuration information. - -| Class | Description | -|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| AdapterClass | High-level configuration type conversion and any database-specific python methods needed | -| AdapterCredentials | Typed dictionary of possible profiles and associated methods | -| AdapterConnectionManager | All the methods responsible for connecting to a database and issuing queries | -| AdapterRelation | How relation names should be rendered, printed, and quoted. Do relation names use all three parts? `catalog.model_name` (two-part name) or `database.schema.model_name` (three-part name) | -| AdapterColumn | How names should be rendered, and database-specific properties | - -### Macros - -A set of *macros* responsible for generating SQL that is compliant with the target database. - -### Materializations - -A set of *materializations* and their corresponding helper macros defined in dbt using jinja and SQL. They codify for dbt how model files should be persisted into the database. - -## Adapter Architecture - - -Below is a diagram of how dbt-postgres, the adapter at the center of dbt-core, works. - - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md index 28cd8935937..ca531d04692 100644 --- a/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md +++ b/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md @@ -3,50 +3,4 @@ title: "Prerequisites for a new adapter" id: "2-prerequisites-for-a-new-adapter" --- -To learn what an adapter is and they role they serve, see [What are adapters?](1-what-are-adapters) -It is very important that make sure that you have the right skills, and to understand the level of difficulty required to make an adapter for your data platform. - -## Pre-Requisite Data Warehouse Features - -The more you can answer Yes to the below questions, the easier your adapter development (and user-) experience will be. See the [New Adapter Information Sheet wiki](https://github.com/dbt-labs/dbt-core/wiki/New-Adapter-Information-Sheet) for even more specific questions. - -### Training -- the developer (and any product managers) ideally will have substantial experience as an end-user of dbt. If not, it is highly advised that you at least take the [dbt Fundamentals](https://courses.getdbt.com/courses/fundamentals) and [Advanced Materializations](https://courses.getdbt.com/courses/advanced-materializations) course. - -### Database -- Does the database complete transactions fast enough for interactive development? -- Can you execute SQL against the data platform? -- Is there a concept of schemas? -- Does the data platform support ANSI SQL, or at least a subset? -### Driver / Connection Library -- Is there a Python-based driver for interacting with the database that is db API 2.0 compliant (e.g. Psycopg2 for Postgres, pyodbc for SQL Server) -- Does it support: prepared statements, multiple statements, or single sign on token authorization to the data platform? - -### Open source software -- Does your organization have an established process for publishing open source software? - - -It is easiest to build an adapter for dbt when the following the /platform in question has: -- a conventional ANSI-SQL interface (or as close to it as possible), -- a mature connection library/SDK that uses ODBC or Python DB 2 API, and -- a way to enable developers to iterate rapidly with both quick reads and writes - - -## Maintaining your new adapter - -When your adapter becomes more popular, and people start using it, you may quickly become the maintainer of an increasingly popular open source project. With this new role, comes some unexpected responsibilities that not only include code maintenance, but also working with a community of users and contributors. To help people understand what to expect of your project, you should communicate your intentions early and often in your adapter documentation or README. Answer questions like, Is this experimental work that people should use at their own risk? Or is this production-grade code that you're committed to maintaining into the future? - -### Keeping the code compatible with dbt Core - -New minor version releases of `dbt-core` may include changes to the Python interface for adapter plugins, as well as new or updated test cases. The maintainers of `dbt-core` will clearly communicate these changes in documentation and release notes, and they will aim for backwards compatibility whenever possible. - -Patch releases of `dbt-core` will _not_ include breaking changes to adapter-facing code. For more details, see ["About dbt Core versions"](/docs/dbt-versions/core). - -### Versioning and releasing your adapter - -We strongly encourage you to adopt the following approach when versioning and releasing your plugin: -- The minor version of your plugin should match the minor version in `dbt-core` (e.g. 1.1.x). -- Aim to release a new version of your plugin for each new minor version of `dbt-core` (once every three months). -- While your plugin is new, and you're iterating on features, aim to offer backwards compatibility and deprecation notices for at least one minor version. As your plugin matures, aim to leave backwards compatibility and deprecation notices in place until the next major version (dbt Core v2). -- Release patch versions of your plugins whenever needed. These patch releases should contain fixes _only_. diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md index 43826ca4b1d..e882906da75 100644 --- a/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md +++ b/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md @@ -3,414 +3,3 @@ title: "Building a new adapter" id: "3-building-a-new-adapter" --- -:::tip -Before you build your adapter, we strongly encourage you to first learn dbt as an end user, learn [what an adapter is and the role they serve](1-what-are-adapters), as well as [data platform prerequisites](2-prerequisites-for-a-new-adapter) -::: - - -This guide will walk you through the first creating the necessary adapter classes and macros, and provide some resources to help you validate that your new adapter is working correctly. Once the adapter is passing most of the functional tests (see ["Testing a new adapter"](4-testing-a-new-adapter) -), please let the community know that is available to use by adding the adapter to the ["Supported Data Platforms"](/docs/supported-data-platforms) page by following the steps given in [Documenting your adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter). - -For any questions you may have, don't hesitate to ask in the [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel. The community is very helpful and likely has experienced a similar issue as you. - -## Scaffolding a new adapter - To create a new adapter plugin from scratch, you can use the [dbt-database-adapter-scaffold](https://github.com/dbt-labs/dbt-database-adapter-scaffold) to trigger an interactive session which will generate a scaffolding for you to build upon. - - Example usage: - - ``` - $ cookiecutter gh:dbt-labs/dbt-database-adapter-scaffold - ``` - -The generated boilerplate starting project will include a basic adapter plugin file structure, examples of macros, high level method descriptions, etc. - -One of the most important choices you will make during the cookiecutter generation will revolve around the field for `is_sql_adapter` which is a boolean used to correctly apply imports for either a `SQLAdapter` or `BaseAdapter`. Knowing which you will need requires a deeper knowledge of your selected database but a few good guides for the choice are. -- Does your database have a complete SQL API? Can it perform tasks using SQL such as creating schemas, dropping schemas, querying an `information_schema` for metadata calls? If so, it is more likely to be a SQLAdapter where you set `is_sql_adapter` to `True`. -- Most adapters do fall under SQL adapters which is why we chose it as the default `True` value. -- It is very possible to build out a fully functional `BaseAdapter`. This will require a little more ground work as it doesn't come with some prebuilt methods the `SQLAdapter` class provides. See `dbt-bigquery` as a good guide. - -## Implementation Details - -Regardless if you decide to use the cookiecutter template or manually create the plugin, this section will go over each method that is required to be implemented. The table below provides a high-level overview of the classes, methods, and macros you may have to define for your data platform. - -| file | component | purpose | -|---------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `./setup.py` | `setup()` function | adapter meta-data (package name, version, author, homepage, etc) | -| `myadapter/dbt/adapters/myadapter/__init__.py` | `AdapterPlugin` | bundle all the information below into a dbt plugin | -| `myadapter/dbt/adapters/myadapter/connections.py` | `MyAdapterCredentials` class | parameters to connect to and configure the database, via a the chosen Python driver | -| `myadapter/dbt/adapters/myadapter/connections.py` | `MyAdapterConnectionManager` class | telling dbt how to interact with the database w.r.t opening/closing connections, executing queries, and fetching data. Effectively a wrapper around the db API or driver. | -| `myadapter/dbt/include/bigquery/` | a dbt project of macro "overrides" in the format of "myadapter__" | any differences in SQL syntax for regular db operations will be modified here from the global_project (e.g. "Create Table As Select", "Get all relations in the current schema", etc) | -| `myadapter/dbt/adapters/myadapter/impl.py` | `MyAdapterConfig` | database- and relation-level configs and | -| `myadapter/dbt/adapters/myadapter/impl.py` | `MyAdapterAdapter` | for changing _how_ dbt performs operations like macros and other needed Python functionality | -| `myadapter/dbt/adapters/myadapter/column.py` | `MyAdapterColumn` | for defining database-specific column such as datatype mappings | - -### Editing `setup.py` - -Edit the file at `myadapter/setup.py` and fill in the missing information. - -You can skip this step if you passed the arguments for `email`, `url`, `author`, and `dependencies` to the cookiecutter template script. If you plan on having nested macro folder structures, you may need to add entries to `package_data` so your macro source files get installed. - -### Editing the connection manager - -Edit the connection manager at `myadapter/dbt/adapters/myadapter/connections.py`. This file is defined in the sections below. - -#### The Credentials class - -The credentials class defines all of the database-specific credentials (e.g. `username` and `password`) that users will need in the [connection profile](/docs/supported-data-platforms) for your new adapter. Each credentials contract should subclass dbt.adapters.base.Credentials, and be implemented as a python dataclass. - -Note that the base class includes required database and schema fields, as dbt uses those values internally. - -For example, if your adapter requires a host, integer port, username string, and password string, but host is the only required field, you'd add definitions for those new properties to the class as types, like this: - - - -```python - -from dataclasses import dataclass -from typing import Optional - -from dbt.adapters.base import Credentials - - -@dataclass -class MyAdapterCredentials(Credentials): - host: str - port: int = 1337 - username: Optional[str] = None - password: Optional[str] = None - - @property - def type(self): - return 'myadapter' - - @property - def unique_field(self): - """ - Hashed and included in anonymous telemetry to track adapter adoption. - Pick a field that can uniquely identify one team/organization building with this adapter - """ - return self.host - - def _connection_keys(self): - """ - List of keys to display in the `dbt debug` output. - """ - return ('host', 'port', 'database', 'username') -``` - - - -There are a few things you can do to make it easier for users when connecting to your database: -- Be sure to implement the Credentials' `_connection_keys` method shown above. This method will return the keys that should be displayed in the output of the `dbt debug` command. As a general rule, it's good to return all the arguments used in connecting to the actual database except the password (even optional arguments). -- Create a `profile_template.yml` to enable configuration prompts for a brand-new user setting up a connection profile via the [`dbt init` command](/reference/commands/init). See more details [below](#other-files). -- You may also want to define an `ALIASES` mapping on your Credentials class to include any config names you want users to be able to use in place of 'database' or 'schema'. For example if everyone using the MyAdapter database calls their databases "collections", you might do: - - - -```python -@dataclass -class MyAdapterCredentials(Credentials): - host: str - port: int = 1337 - username: Optional[str] = None - password: Optional[str] = None - - ALIASES = { - 'collection': 'database', - } -``` - - - -Then users can use `collection` OR `database` in their `profiles.yml`, `dbt_project.yml`, or `config()` calls to set the database. - -#### `ConnectionManager` class methods - -Once credentials are configured, you'll need to implement some connection-oriented methods. They are enumerated in the SQLConnectionManager docstring, but an overview will also be provided here. - -**Methods to implement:** -- `open` -- `get_response` -- `cancel` -- `exception_handler` -- `standardize_grants_dict` - -##### `open(cls, connection)` - -`open()` is a classmethod that gets a connection object (which could be in any state, but will have a `Credentials` object with the attributes you defined above) and moves it to the 'open' state. - -Generally this means doing the following: - - if the connection is open already, log and return it. - - If a database needed changes to the underlying connection before re-use, that would happen here - - create a connection handle using the underlying database library using the credentials - - on success: - - set connection.state to `'open'` - - set connection.handle to the handle object - - this is what must have a `cursor()` method that returns a cursor! - - on error: - - set connection.state to `'fail'` - - set connection.handle to `None` - - raise a `dbt.exceptions.FailedToConnectException` with the error and any other relevant information - -For example: - - - -```python - @classmethod - def open(cls, connection): - if connection.state == 'open': - logger.debug('Connection is already open, skipping open.') - return connection - - credentials = connection.credentials - - try: - handle = myadapter_library.connect( - host=credentials.host, - port=credentials.port, - username=credentials.username, - password=credentials.password, - catalog=credentials.database - ) - connection.state = 'open' - connection.handle = handle - return connection -``` - - - -##### `get_response(cls, cursor)` - -`get_response` is a classmethod that gets a cursor object and returns adapter-specific information about the last executed command. The return value should be an `AdapterResponse` object that includes items such as `code`, `rows_affected`, `bytes_processed`, and a summary `_message` for logging to stdout. - - - -```python - @classmethod - def get_response(cls, cursor) -> AdapterResponse: - code = cursor.sqlstate or "OK" - rows = cursor.rowcount - status_message = f"{code} {rows}" - return AdapterResponse( - _message=status_message, - code=code, - rows_affected=rows - ) -``` - - - -##### `cancel(self, connection)` - -`cancel` is an instance method that gets a connection object and attempts to cancel any ongoing queries, which is database dependent. Some databases don't support the concept of cancellation, they can simply implement it via 'pass' and their adapter classes should implement an `is_cancelable` that returns False - On ctrl+c connections may remain running. This method must be implemented carefully, as the affected connection will likely be in use in a different thread. - - - -```python - def cancel(self, connection): - tid = connection.handle.transaction_id() - sql = 'select cancel_transaction({})'.format(tid) - logger.debug("Cancelling query '{}' ({})".format(connection_name, pid)) - _, cursor = self.add_query(sql, 'master') - res = cursor.fetchone() - logger.debug("Canceled query '{}': {}".format(connection_name, res)) -``` - - - -##### `exception_handler(self, sql, connection_name='master')` - -`exception_handler` is an instance method that returns a context manager that will handle exceptions raised by running queries, catch them, log appropriately, and then raise exceptions dbt knows how to handle. - -If you use the (highly recommended) `@contextmanager` decorator, you only have to wrap a `yield` inside a `try` block, like so: - - - -```python - @contextmanager - def exception_handler(self, sql: str): - try: - yield - except myadapter_library.DatabaseError as exc: - self.release(connection_name) - - logger.debug('myadapter error: {}'.format(str(e))) - raise dbt.exceptions.DatabaseException(str(exc)) - except Exception as exc: - logger.debug("Error running SQL: {}".format(sql)) - logger.debug("Rolling back transaction.") - self.release(connection_name) - raise dbt.exceptions.RuntimeException(str(exc)) -``` - - - -##### `standardize_grants_dict(self, grants_table: agate.Table) -> dict` - -`standardize_grants_dict` is an method that returns the dbt-standardized grants dictionary that matches how users configure grants now in dbt. The input is the result of `SHOW GRANTS ON {{model}}` call loaded into an agate table. - -If there's any massaging of agate table containing the results, of `SHOW GRANTS ON {{model}}`, that can't easily be accomplished in SQL, it can be done here. For example, the SQL to show grants *should* filter OUT any grants TO the current user/role (e.g. OWNERSHIP). If that's not possible in SQL, it can be done in this method instead. - - - -```python - @available - def standardize_grants_dict(self, grants_table: agate.Table) -> dict: - """ - :param grants_table: An agate table containing the query result of - the SQL returned by get_show_grant_sql - :return: A standardized dictionary matching the `grants` config - :rtype: dict - """ - grants_dict: Dict[str, List[str]] = {} - for row in grants_table: - grantee = row["grantee"] - privilege = row["privilege_type"] - if privilege in grants_dict.keys(): - grants_dict[privilege].append(grantee) - else: - grants_dict.update({privilege: [grantee]}) - return grants_dict -``` - - - -### Editing the adapter implementation - -Edit the connection manager at `myadapter/dbt/adapters/myadapter/impl.py` - -Very little is required to implement the adapter itself. On some adapters, you will not need to override anything. On others, you'll likely need to override some of the ``convert_*`` classmethods, or override the `is_cancelable` classmethod on others to return `False`. - - -#### `datenow()` - -This classmethod provides the adapter's canonical date function. This is not used but is required– anyway on all adapters. - - - -```python - @classmethod - def date_function(cls): - return 'datenow()' -``` - - - -### Editing SQL logic - -dbt implements specific SQL operations using jinja macros. While reasonable defaults are provided for many such operations (like `create_schema`, `drop_schema`, `create_table`, etc), you may need to override one or more of macros when building a new adapter. - -#### Required macros - -The following macros must be implemented, but you can override their behavior for your adapter using the "dispatch" pattern described below. Macros marked (required) do not have a valid default implementation, and are required for dbt to operate. - -- `alter_column_type` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/columns.sql#L37-L55)) -- `check_schema_exists` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L43-L55)) -- `create_schema` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/schema.sql#L1-L9)) -- `drop_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L34-L42)) -- `drop_schema` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/schema.sql#L12-L20)) -- `get_columns_in_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/columns.sql#L1-L8)) (required) -- `list_relations_without_caching` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L58-L65)) (required) -- `list_schemas` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L29-L40)) -- `rename_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L56-L65)) -- `truncate_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L45-L53)) -- `current_timestamp` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/freshness.sql#L1-L8)) (required) -- `copy_grants` - -#### Adapter dispatch - -Most modern databases support a majority of the standard SQL spec. There are some databases that _do not_ support critical aspects of the SQL spec however, or they provide their own nonstandard mechanisms for implementing the same functionality. To account for these variations in SQL support, dbt provides a mechanism called [multiple dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch) for macros. With this feature, macros can be overridden for specific adapters. This makes it possible to implement high-level methods (like "create ") in a database-specific way. - - - -```jinja2 - -{# dbt will call this macro by name, providing any arguments #} -{% macro create_table_as(temporary, relation, sql) -%} - - {# dbt will dispatch the macro call to the relevant macro #} - {{ return( - adapter.dispatch('create_table_as')(temporary, relation, sql) - ) }} -{%- endmacro %} - - - -{# If no macro matches the specified adapter, "default" will be used #} -{% macro default__create_table_as(temporary, relation, sql) -%} - ... -{%- endmacro %} - - - -{# Example which defines special logic for Redshift #} -{% macro redshift__create_table_as(temporary, relation, sql) -%} - ... -{%- endmacro %} - - - -{# Example which defines special logic for BigQuery #} -{% macro bigquery__create_table_as(temporary, relation, sql) -%} - ... -{%- endmacro %} -``` - - - -The `adapter.dispatch()` macro takes a second argument, `packages`, which represents a set of "search namespaces" in which to find potential implementations of a dispatched macro. This allows users of community-supported adapters to extend or "shim" dispatched macros from common packages, such as `dbt-utils`, with adapter-specific versions in their own project or other installed packages. See: -- "Shim" package examples: [`spark-utils`](https://github.com/dbt-labs/spark-utils), [`tsql-utils`](https://github.com/dbt-msft/tsql-utils) -- [`adapter.dispatch` docs](/reference/dbt-jinja-functions/dispatch) - -#### Overriding adapter methods - -While much of dbt's adapter-specific functionality can be modified in adapter macros, it can also make sense to override adapter methods directly. In this example, assume that a database does not support a `cascade` parameter to `drop schema`. Instead, we can implement an approximation where we drop each relation and then drop the schema. - - - -```python - def drop_schema(self, relation: BaseRelation): - relations = self.list_relations( - database=relation.database, - schema=relation.schema - ) - for relation in relations: - self.drop_relation(relation) - super().drop_schema(relation) -``` - - - -#### Grants Macros - -See [this GitHub discussion](https://github.com/dbt-labs/dbt-core/discussions/5468) for information on the macros required for `GRANT` statements: -### Other files - -#### `profile_template.yml` - -In order to enable the [`dbt init` command](/reference/commands/init) to prompt users when setting up a new project and connection profile, you should include a **profile template**. The filepath needs to be `dbt/include//profile_template.yml`. It's possible to provide hints, default values, and conditional prompts based on connection methods that require different supporting attributes. Users will also be able to include custom versions of this file in their own projects, with fixed values specific to their organization, to support their colleagues when using your dbt adapter for the first time. - -See examples: -- [dbt-postgres](https://github.com/dbt-labs/dbt-core/blob/main/plugins/postgres/dbt/include/postgres/profile_template.yml) -- [dbt-redshift](https://github.com/dbt-labs/dbt-redshift/blob/main/dbt/include/redshift/profile_template.yml) -- [dbt-snowflake](https://github.com/dbt-labs/dbt-snowflake/blob/main/dbt/include/snowflake/profile_template.yml) -- [dbt-bigquery](https://github.com/dbt-labs/dbt-bigquery/blob/main/dbt/include/bigquery/profile_template.yml) - -#### `__version__.py` - -To assure that `dbt --version` provides the latest dbt core version the adapter supports, be sure include a `__version__.py` file. The filepath will be `dbt/adapters//__version__.py`. We recommend using the latest dbt core version and as the adapter is made compatible with later versions, this file will need to be updated. For a sample file, check out this [example](https://github.com/dbt-labs/dbt-snowflake/blob/main/dbt/adapters/snowflake/__version__.py). - -It should be noted that both of these files are included in the bootstrapped output of the `dbt-database-adapter-scaffold` so when using the scaffolding, these files will be included. - -## Testing your new adapter - -This has moved to its own page: ["Testing a new adapter"](4-testing-a-new-adapter) - -## Documenting your new adapter - -This has moved to its own page: ["Documenting a new adapter"](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) - -## Maintaining your new adapter - -This has moved to a new spot: ["Maintaining your new adapter"](2-prerequisites-for-a-new-adapter##maintaining-your-new-adapter) diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md index b1b5072670a..9b1ec2c7e0e 100644 --- a/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md +++ b/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md @@ -3,497 +3,3 @@ title: "Testing a new adapter" id: "4-testing-a-new-adapter" --- -:::info - -Previously, we offered a packaged suite of tests for dbt adapter functionality: [`pytest-dbt-adapter`](https://github.com/dbt-labs/dbt-adapter-tests). We are deprecating that suite, in favor of the newer testing framework outlined in this document. - -::: - -This document has two sections: - -1. "[About the testing framework](#about-the-testing-framework)" describes the standard framework that we maintain for using pytest together with dbt. It includes an example that shows the anatomy of a simple test case. -2. "[Testing your adapter](#testing-your-adapter)" offers a step-by-step guide for using our out-of-the-box suite of "basic" tests, which will validate that your adapter meets a baseline of dbt functionality. - -## Prerequisites - -- Your adapter must be compatible with dbt-core **v1.1** or newer -- You should be familiar with **pytest**: https://docs.pytest.org/ - -## About the testing framework - -dbt-core offers a standard framework for running pre-built functional tests, and for defining your own tests. The core testing framework is built using `pytest`, a mature and standard library for testing Python projects. - -The **[`tests` module](https://github.com/dbt-labs/dbt-core/tree/HEAD/core/dbt/tests)** within `dbt-core` includes basic utilities for setting up pytest + dbt. These are used by all "pre-built" functional tests, and make it possible to quickly write your own tests. - -Those utilities allow you to do three basic things: -1. **Quickly set up a dbt "project."** Define project resources via methods such as `models()` and `seeds()`. Use `project_config_update()` to pass configurations into `dbt_project.yml`. -2. **Define a sequence of dbt commands.** The most important utility is `run_dbt()`, which returns the [results](/reference/dbt-classes#result-objects) of each dbt command. It takes a list of CLI specifiers (subcommand + flags), as well as an optional second argument, `expect_pass=False`, for cases where you expect the command to fail. -3. **Validate the results of those dbt commands.** For example, `check_relations_equal()` asserts that two database objects have the same structure and content. You can also write your own `assert` statements, by inspecting the results of a dbt command, or querying arbitrary database objects with `project.run_sql()`. - -You can see the full suite of utilities, with arguments and annotations, in [`util.py`](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/tests/util.py). You'll also see them crop up across a number of test cases. While all utilities are intended to be reusable, you won't need all of them for every test. In the example below, we'll show a simple test case that uses only a few utilities. - -### Example: a simple test case - -This example will show you the anatomy of a test case using dbt + pytest. We will create reusable components, combine them to form a dbt "project", and define a sequence of dbt commands. Then, we'll use Python `assert` statements to ensure those commands succeed (or fail) as we expect. - -In ["Getting started running basic tests,"](#getting-started-running-basic-tests) we'll offer step-by-step instructions for installing and configuring `pytest`, so that you can run it on your own machine. For now, it's more important to see how the pieces of a test case fit together. - -This example includes a seed, a model, and two tests—one of which will fail. - -1. Define Python strings that will represent the file contents in your dbt project. Defining these in a separate file enables you to reuse the same components across different test cases. The pytest name for this type of reusable component is "fixture." - - - -```python -# seeds/my_seed.csv -my_seed_csv = """ -id,name,some_date -1,Easton,1981-05-20T06:46:51 -2,Lillian,1978-09-03T18:10:33 -3,Jeremiah,1982-03-11T03:59:51 -4,Nolan,1976-05-06T20:21:35 -""".lstrip() - -# models/my_model.sql -my_model_sql = """ -select * from {{ ref('my_seed') }} -union all -select null as id, null as name, null as some_date -""" - -# models/my_model.yml -my_model_yml = """ -version: 2 -models: - - name: my_model - columns: - - name: id - tests: - - unique - - not_null # this test will fail -""" -``` - - - -2. Use the "fixtures" to define the project for your test case. These fixtures are always scoped to the **class**, where the class represents one test case—that is, one dbt project or scenario. (The same test case can be used for one or more actual tests, which we'll see in step 3.) Following the default pytest configurations, the file name must begin with `test_`, and the class name must begin with `Test`. - - - -```python -import pytest -from dbt.tests.util import run_dbt - -# our file contents -from tests.functional.example.fixtures import ( - my_seed_csv, - my_model_sql, - my_model_yml, -) - -# class must begin with 'Test' -class TestExample: - """ - Methods in this class will be of two types: - 1. Fixtures defining the dbt "project" for this test case. - These are scoped to the class, and reused for all tests in the class. - 2. Actual tests, whose names begin with 'test_'. - These define sequences of dbt commands and 'assert' statements. - """ - - # configuration in dbt_project.yml - @pytest.fixture(scope="class") - def project_config_update(self): - return { - "name": "example", - "models": {"+materialized": "view"} - } - - # everything that goes in the "seeds" directory - @pytest.fixture(scope="class") - def seeds(self): - return { - "my_seed.csv": my_seed_csv, - } - - # everything that goes in the "models" directory - @pytest.fixture(scope="class") - def models(self): - return { - "my_model.sql": my_model_sql, - "my_model.yml": my_model_yml, - } - - # continues below -``` - - - -3. Now that we've set up our project, it's time to define a sequence of dbt commands and assertions. We define one or more methods in the same file, on the same class (`TestExampleFailingTest`), whose names begin with `test_`. These methods share the same setup (project scenario) from above, but they can be run independently by pytest—so they shouldn't depend on each other in any way. - - - -```python - # continued from above - - # The actual sequence of dbt commands and assertions - # pytest will take care of all "setup" + "teardown" - def test_run_seed_test(self, project): - """ - Seed, then run, then test. We expect one of the tests to fail - An alternative pattern is to use pytest "xfail" (see below) - """ - # seed seeds - results = run_dbt(["seed"]) - assert len(results) == 1 - # run models - results = run_dbt(["run"]) - assert len(results) == 1 - # test tests - results = run_dbt(["test"], expect_pass = False) # expect failing test - assert len(results) == 2 - # validate that the results include one pass and one failure - result_statuses = sorted(r.status for r in results) - assert result_statuses == ["fail", "pass"] - - @pytest.mark.xfail - def test_build(self, project): - """Expect a failing test""" - # do it all - results = run_dbt(["build"]) -``` - - - -3. Our test is ready to run! The last step is to invoke `pytest` from your command line. We'll walk through the actual setup and configuration of `pytest` in the next section. - - - -```sh -$ python3 -m pytest tests/functional/test_example.py -=========================== test session starts ============================ -platform ... -- Python ..., pytest-..., pluggy-... -rootdir: ... -plugins: ... - -tests/functional/test_example.py .X [100%] - -======================= 1 passed, 1 xpassed in 1.38s ======================= -``` - - - -You can find more ways to run tests, along with a full command reference, in the [pytest usage docs](https://docs.pytest.org/how-to/usage.html). - -We've found the `-s` flag (or `--capture=no`) helpful to print logs from the underlying dbt invocations, and to step into an interactive debugger if you've added one. You can also use environment variables to set [global dbt configs](/reference/global-configs/about-global-configs), such as `DBT_DEBUG` (to show debug-level logs). - -## Testing your adapter - -Anyone who installs `dbt-core`, and wishes to define their own test cases, can use the framework presented in the first section. The framework is especially useful for testing standard dbt behavior across different databases. - -To that end, we have built and made available a [package of reusable adapter test cases](https://github.com/dbt-labs/dbt-core/tree/HEAD/tests/adapter), for creators and maintainers of adapter plugins. These test cases cover basic expected functionality, as well as functionality that frequently requires different implementations across databases. - -For the time being, this package is also located within the `dbt-core` repository, but separate from the `dbt-core` Python package. - -### Categories of tests - -In the course of creating and maintaining your adapter, it's likely that you will end up implementing tests that fall into three broad categories: - -1. **Basic tests** that every adapter plugin is expected to pass. These are defined in `tests.adapter.basic`. Given differences across data platforms, these may require slight modification or reimplementation. Significantly overriding or disabling these tests should be with good reason, since each represents basic functionality expected by dbt users. For example, if your adapter does not support incremental models, you should disable the test, [by marking it with `skip` or `xfail`](https://docs.pytest.org/en/latest/how-to/skipping.html), as well as noting that limitation in any documentation, READMEs, and usage guides that accompany your adapter. - -2. **Optional tests**, for second-order functionality that is common across plugins, but not required for basic use. Your plugin can opt into these test cases by inheriting existing ones, or reimplementing them with adjustments. For now, this category includes all tests located outside the `basic` subdirectory. More tests will be added as we convert older tests defined on dbt-core and mature plugins to use the standard framework. - -3. **Custom tests**, for behavior that is specific to your adapter / data platform. Each has its own specialties and idiosyncracies. We encourage you to use the same `pytest`-based framework, utilities, and fixtures to write your own custom tests for functionality that is unique to your adapter. - -If you run into an issue with the core framework, or the basic/optional test cases—or if you've written a custom test that you believe would be relevant and useful for other adapter plugin developers—please open an issue or PR in the `dbt-core` repository on GitHub. - -## Getting started running basic tests - -In this section, we'll walk through the three steps to start running our basic test cases on your adapter plugin: - -1. Install dependencies -2. Set up and configure pytest -3. Define test cases - -### Install dependencies - -You should already have a virtual environment with `dbt-core` and your adapter plugin installed. You'll also need to install: -- [`pytest`](https://pypi.org/project/pytest/) -- [`dbt-tests-adapter`](https://pypi.org/project/dbt-tests-adapter/), the set of common test cases -- (optional) [`pytest` plugins](https://docs.pytest.org/en/7.0.x/reference/plugin_list.html)--we'll use `pytest-dotenv` below - -Or specify all dependencies in a requirements file like: - - -```txt -pytest -pytest-dotenv -dbt-tests-adapter -``` - - -```sh -pip install -r dev_requirements.txt -``` - -### Set up and configure pytest - -First, set yourself up to run `pytest` by creating a file named `pytest.ini` at the root of your repository: - - - -```python -[pytest] -filterwarnings = - ignore:.*'soft_unicode' has been renamed to 'soft_str'*:DeprecationWarning - ignore:unclosed file .*:ResourceWarning -env_files = - test.env # uses pytest-dotenv plugin - # this allows you to store env vars for database connection in a file named test.env - # rather than passing them in every CLI command, or setting in `PYTEST_ADDOPTS` - # be sure to add "test.env" to .gitignore as well! -testpaths = - tests/functional # name per convention -``` - - - -Then, create a configuration file within your tests directory. In it, you'll want to define all necessary profile configuration for connecting to your data platform in local development and continuous integration. We recommend setting these values with environment variables, since this file will be checked into version control. - - - -```python -import pytest -import os - -# Import the standard functional fixtures as a plugin -# Note: fixtures with session scope need to be local -pytest_plugins = ["dbt.tests.fixtures.project"] - -# The profile dictionary, used to write out profiles.yml -# dbt will supply a unique schema per test, so we do not specify 'schema' here -@pytest.fixture(scope="class") -def dbt_profile_target(): - return { - 'type': '', - 'threads': 1, - 'host': os.getenv('HOST_ENV_VAR_NAME'), - 'user': os.getenv('USER_ENV_VAR_NAME'), - ... - } -``` - - - -### Define test cases - -As in the example above, each test case is defined as a class, and has its own "project" setup. To get started, you can import all basic test cases and try running them without changes. - - - -```python -import pytest - -from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations -from dbt.tests.adapter.basic.test_singular_tests import BaseSingularTests -from dbt.tests.adapter.basic.test_singular_tests_ephemeral import BaseSingularTestsEphemeral -from dbt.tests.adapter.basic.test_empty import BaseEmpty -from dbt.tests.adapter.basic.test_ephemeral import BaseEphemeral -from dbt.tests.adapter.basic.test_incremental import BaseIncremental -from dbt.tests.adapter.basic.test_generic_tests import BaseGenericTests -from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols -from dbt.tests.adapter.basic.test_snapshot_timestamp import BaseSnapshotTimestamp -from dbt.tests.adapter.basic.test_adapter_methods import BaseAdapterMethod - -class TestSimpleMaterializationsMyAdapter(BaseSimpleMaterializations): - pass - - -class TestSingularTestsMyAdapter(BaseSingularTests): - pass - - -class TestSingularTestsEphemeralMyAdapter(BaseSingularTestsEphemeral): - pass - - -class TestEmptyMyAdapter(BaseEmpty): - pass - - -class TestEphemeralMyAdapter(BaseEphemeral): - pass - - -class TestIncrementalMyAdapter(BaseIncremental): - pass - - -class TestGenericTestsMyAdapter(BaseGenericTests): - pass - - -class TestSnapshotCheckColsMyAdapter(BaseSnapshotCheckCols): - pass - - -class TestSnapshotTimestampMyAdapter(BaseSnapshotTimestamp): - pass - - -class TestBaseAdapterMethod(BaseAdapterMethod): - pass -``` - - - - -Finally, run pytest: -```sh -python3 -m pytest tests/functional -``` - -### Modifying test cases - -You may need to make slight modifications in a specific test case to get it passing on your adapter. The mechanism to do this is simple: rather than simply inheriting the "base" test with `pass`, you can redefine any of its fixtures or test methods. - -For instance, on Redshift, we need to explicitly cast a column in the fixture input seed to use data type `varchar(64)`: - - - -```python -import pytest -from dbt.tests.adapter.basic.files import seeds_base_csv, seeds_added_csv, seeds_newcolumns_csv -from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols - -# set the datatype of the name column in the 'added' seed so it -# can hold the '_update' that's added -schema_seed_added_yml = """ -version: 2 -seeds: - - name: added - config: - column_types: - name: varchar(64) -""" - -class TestSnapshotCheckColsRedshift(BaseSnapshotCheckCols): - # Redshift defines the 'name' column such that it's not big enough - # to hold the '_update' added in the test. - @pytest.fixture(scope="class") - def models(self): - return { - "base.csv": seeds_base_csv, - "added.csv": seeds_added_csv, - "seeds.yml": schema_seed_added_yml, - } -``` - - - -As another example, the `dbt-bigquery` adapter asks users to "authorize" replacing a with a by supplying the `--full-refresh` flag. The reason: In the table logic, a view by the same name must first be dropped; if the table query fails, the model will be missing. - -Knowing this possibility, the "base" test case offers a `require_full_refresh` switch on the `test_config` fixture class. For BigQuery, we'll switch it on: - - - -```python -import pytest -from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations - -class TestSimpleMaterializationsBigQuery(BaseSimpleMaterializations): - @pytest.fixture(scope="class") - def test_config(self): - # effect: add '--full-refresh' flag in requisite 'dbt run' step - return {"require_full_refresh": True} -``` - - - -It's always worth asking whether the required modifications represent gaps in perceived or expected dbt functionality. Are these simple implementation details, which any user of this database would understand? Are they limitations worth documenting? - -If, on the other hand, they represent poor assumptions in the "basic" test cases, which fail to account for a common pattern in other types of databases-—please open an issue or PR in the `dbt-core` repository on GitHub. - -### Running with multiple profiles - -Some databases support multiple connection methods, which map to actually different functionality behind the scenes. For instance, the `dbt-spark` adapter supports connections to Apache Spark clusters _and_ Databricks runtimes, which supports additional functionality out of the box, enabled by the Delta file format. - - - -```python -def pytest_addoption(parser): - parser.addoption("--profile", action="store", default="apache_spark", type=str) - - -# Using @pytest.mark.skip_profile('apache_spark') uses the 'skip_by_profile_type' -# autouse fixture below -def pytest_configure(config): - config.addinivalue_line( - "markers", - "skip_profile(profile): skip test for the given profile", - ) - -@pytest.fixture(scope="session") -def dbt_profile_target(request): - profile_type = request.config.getoption("--profile") - elif profile_type == "databricks_sql_endpoint": - target = databricks_sql_endpoint_target() - elif profile_type == "apache_spark": - target = apache_spark_target() - else: - raise ValueError(f"Invalid profile type '{profile_type}'") - return target - -def apache_spark_target(): - return { - "type": "spark", - "host": "localhost", - ... - } - -def databricks_sql_endpoint_target(): - return { - "type": "spark", - "host": os.getenv("DBT_DATABRICKS_HOST_NAME"), - ... - } - -@pytest.fixture(autouse=True) -def skip_by_profile_type(request): - profile_type = request.config.getoption("--profile") - if request.node.get_closest_marker("skip_profile"): - for skip_profile_type in request.node.get_closest_marker("skip_profile").args: - if skip_profile_type == profile_type: - pytest.skip("skipped on '{profile_type}' profile") -``` - - - -If there are tests that _shouldn't_ run for a given profile: - - - -```python -# Snapshots require access to the Delta file format, available on our Databricks connection, -# so let's skip on Apache Spark -@pytest.mark.skip_profile('apache_spark') -class TestSnapshotCheckColsSpark(BaseSnapshotCheckCols): - @pytest.fixture(scope="class") - def project_config_update(self): - return { - "seeds": { - "+file_format": "delta", - }, - "snapshots": { - "+file_format": "delta", - } - } -``` - - - -Finally: -```sh -python3 -m pytest tests/functional --profile apache_spark -python3 -m pytest tests/functional --profile databricks_sql_endpoint -``` diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/adapter-development b/website/docs/guides/dbt-ecosystem/adapter-development/adapter-development deleted file mode 100644 index 8b137891791..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/adapter-development +++ /dev/null @@ -1 +0,0 @@ - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md b/website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md new file mode 100644 index 00000000000..269a9e1ff6a --- /dev/null +++ b/website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md @@ -0,0 +1,1066 @@ +--- +title: "Build, test, document, and promote adapters" +id: "adapter-creation" +hoverSnippet: Learn how to +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Adapter creation'] +level: 'Advanced' +recently_updated: true +--- + +## Introduction + +Adapters are an essential component of dbt. At their most basic level, they are how dbt Core connects with the various supported data platforms. At a higher-level, dbt Core adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt Core. + + Navigating and developing around the nuances of different databases can be daunting, but you are not alone. Visit [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel for additional help beyond the documentation. + +### All databases are not the same + +There's a tremendous amount of work that goes into creating a database. Here is a high-level list of typical database layers (from the outermost layer moving inwards): +- SQL API +- Client Library / Driver +- Server Connection Manager +- Query parser +- Query optimizer +- Runtime +- Storage Access Layer +- Storage + +There's a lot more there than just SQL as a language. Databases (and data warehouses) are so popular because you can abstract away a great deal of the complexity from your brain to the database itself. This enables you to focus more on the data. + +dbt allows for further abstraction and standardization of the outermost layers of a database (SQL API, client library, connection manager) into a framework that both: + - Opens database technology to less technical users (a large swath of a DBA's role has been automated, similar to how the vast majority of folks with websites today no longer have to be "[webmasters](https://en.wikipedia.org/wiki/Webmaster)"). + - Enables more meaningful conversations about how data warehousing should be done. + +This is where dbt adapters become critical. + +### What needs to be adapted? + +dbt adapters are responsible for _adapting_ dbt's standard functionality to a particular database. Our prototypical database and adapter are PostgreSQL and dbt-postgres, and most of our adapters are somewhat based on the functionality described in dbt-postgres. + +Connecting dbt to a new database will require a new adapter to be built or an existing adapter to be extended. + +The outermost layers of a database map roughly to the areas in which the dbt adapter framework encapsulates inter-database differences. + +### SQL API + +Even amongst ANSI-compliant databases, there are differences in the SQL grammar. +Here are some categories and examples of SQL statements that can be constructed differently: + + +| Category | Area of differences | Examples | +|----------------------------------------------|--------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Statement syntax | The use of `IF EXISTS` |
  • `IF
  • EXISTS, DROP TABLE`
  • `DROP
  • IF EXISTS` | +| Workflow definition & semantics | Incremental updates |
  • `MERGE`
  • `DELETE; INSERT`
  • | +| Relation and column attributes/configuration | Database-specific materialization configs |
  • `DIST = ROUND_ROBIN` (Synapse)
  • `DIST = EVEN` (Redshift)
  • | +| Permissioning | Grant statements that can only take one grantee at a time vs those that accept lists of grantees |
  • `grant SELECT on table dinner.corn to corn_kid, everyone`
  • `grant SELECT on table dinner.corn to corn_kid; grant SELECT on table dinner.corn to everyone`
  • | + +### Python Client Library & Connection Manager + +The other big category of inter-database differences comes with how the client connects to the database and executes queries against the connection. To integrate with dbt, a data platform must have a pre-existing python client library or support ODBC, using a generic python library like pyodbc. + +| Category | Area of differences | Examples | +|------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| Credentials & authentication | Authentication |
  • Username & password
  • MFA with `boto3` or Okta token
  • | +| Connection opening/closing | Create a new connection to db |
  • `psycopg2.connect(connection_string)`
  • `google.cloud.bigquery.Client(...)`
  • | +| Inserting local data | Load seed .`csv` files into Python memory |
  • `google.cloud.bigquery.Client.load_table_from_file(...)` (BigQuery)
  • `INSERT ... INTO VALUES ...` prepared statement (most other databases)
  • | + + +### How dbt encapsulates and abstracts these differences + +Differences between databases are encoded into discrete areas: + +| Components | Code Path | Function | +|------------------|---------------------------------------------------|-------------------------------------------------------------------------------| +| Python Classes | `adapters/` | Configuration (See above [Python classes](##python classes) | +| Macros | `include//macros/adapters/` | SQL API & statement syntax (for example, how to create schema or how to get table info) | +| Materializations | `include//macros/materializations/` | Table/view/snapshot/ workflow definitions | + + +#### Python Classes + +These classes implement all the methods responsible for: +- Connecting to a database and issuing queries. +- Providing dbt with database-specific configuration information. + +| Class | Description | +|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| AdapterClass | High-level configuration type conversion and any database-specific python methods needed | +| AdapterCredentials | Typed dictionary of possible profiles and associated methods | +| AdapterConnectionManager | All the methods responsible for connecting to a database and issuing queries | +| AdapterRelation | How relation names should be rendered, printed, and quoted. Do relation names use all three parts? `catalog.model_name` (two-part name) or `database.schema.model_name` (three-part name) | +| AdapterColumn | How names should be rendered, and database-specific properties | + +#### Macros + +A set of *macros* responsible for generating SQL that is compliant with the target database. + +#### Materializations + +A set of *materializations* and their corresponding helper macros defined in dbt using jinja and SQL. They codify for dbt how model files should be persisted into the database. + +### Adapter Architecture + + +Below is a diagram of how dbt-postgres, the adapter at the center of dbt-core, works. + + + +## Prerequisites + +It is very important that you have the right skills, and understand the level of difficulty required to make an adapter for your data platform. + +The more you can answer Yes to the below questions, the easier your adapter development (and user-) experience will be. See the [New Adapter Information Sheet wiki](https://github.com/dbt-labs/dbt-core/wiki/New-Adapter-Information-Sheet) for even more specific questions. + +### Training + +- the developer (and any product managers) ideally will have substantial experience as an end-user of dbt. If not, it is highly advised that you at least take the [dbt Fundamentals](https://courses.getdbt.com/courses/fundamentals) and [Advanced Materializations](https://courses.getdbt.com/courses/advanced-materializations) course. + +### Database + +- Does the database complete transactions fast enough for interactive development? +- Can you execute SQL against the data platform? +- Is there a concept of schemas? +- Does the data platform support ANSI SQL, or at least a subset? + +### Driver / Connection Library + +- Is there a Python-based driver for interacting with the database that is db API 2.0 compliant (e.g. Psycopg2 for Postgres, pyodbc for SQL Server) +- Does it support: prepared statements, multiple statements, or single sign on token authorization to the data platform? + +### Open source software + +- Does your organization have an established process for publishing open source software? + +It is easiest to build an adapter for dbt when the following the /platform in question has: + +- a conventional ANSI-SQL interface (or as close to it as possible), +- a mature connection library/SDK that uses ODBC or Python DB 2 API, and +- a way to enable developers to iterate rapidly with both quick reads and writes + +### Maintaining your new adapter + +When your adapter becomes more popular, and people start using it, you may quickly become the maintainer of an increasingly popular open source project. With this new role, comes some unexpected responsibilities that not only include code maintenance, but also working with a community of users and contributors. To help people understand what to expect of your project, you should communicate your intentions early and often in your adapter documentation or README. Answer questions like, Is this experimental work that people should use at their own risk? Or is this production-grade code that you're committed to maintaining into the future? + +#### Keeping the code compatible with dbt Core + +New minor version releases of `dbt-core` may include changes to the Python interface for adapter plugins, as well as new or updated test cases. The maintainers of `dbt-core` will clearly communicate these changes in documentation and release notes, and they will aim for backwards compatibility whenever possible. + +Patch releases of `dbt-core` will _not_ include breaking changes to adapter-facing code. For more details, see ["About dbt Core versions"](/docs/dbt-versions/core). + +#### Versioning and releasing your adapter + +We strongly encourage you to adopt the following approach when versioning and releasing your plugin: + +- The minor version of your plugin should match the minor version in `dbt-core` (e.g. 1.1.x). +- Aim to release a new version of your plugin for each new minor version of `dbt-core` (once every three months). +- While your plugin is new, and you're iterating on features, aim to offer backwards compatibility and deprecation notices for at least one minor version. As your plugin matures, aim to leave backwards compatibility and deprecation notices in place until the next major version (dbt Core v2). +- Release patch versions of your plugins whenever needed. These patch releases should contain fixes _only_. + +## Building a new adapter + +This step will walk you through the first creating the necessary adapter classes and macros, and provide some resources to help you validate that your new adapter is working correctly. Make sure you've familiarized yourself with the previous steps in this guide. + +Once the adapter is passing most of the functional tests (see ["Testing a new adapter"](4-testing-a-new-adapter) +), please let the community know that is available to use by adding the adapter to the ["Supported Data Platforms"](/docs/supported-data-platforms) page by following the steps given in [Documenting your adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter). + +For any questions you may have, don't hesitate to ask in the [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel. The community is very helpful and likely has experienced a similar issue as you. + +### Scaffolding a new adapter + + To create a new adapter plugin from scratch, you can use the [dbt-database-adapter-scaffold](https://github.com/dbt-labs/dbt-database-adapter-scaffold) to trigger an interactive session which will generate a scaffolding for you to build upon. + + Example usage: + + ``` + $ cookiecutter gh:dbt-labs/dbt-database-adapter-scaffold + ``` + +The generated boilerplate starting project will include a basic adapter plugin file structure, examples of macros, high level method descriptions, etc. + +One of the most important choices you will make during the cookiecutter generation will revolve around the field for `is_sql_adapter` which is a boolean used to correctly apply imports for either a `SQLAdapter` or `BaseAdapter`. Knowing which you will need requires a deeper knowledge of your selected database but a few good guides for the choice are. + +- Does your database have a complete SQL API? Can it perform tasks using SQL such as creating schemas, dropping schemas, querying an `information_schema` for metadata calls? If so, it is more likely to be a SQLAdapter where you set `is_sql_adapter` to `True`. +- Most adapters do fall under SQL adapters which is why we chose it as the default `True` value. +- It is very possible to build out a fully functional `BaseAdapter`. This will require a little more ground work as it doesn't come with some prebuilt methods the `SQLAdapter` class provides. See `dbt-bigquery` as a good guide. + +### Implementation Details + +Regardless if you decide to use the cookiecutter template or manually create the plugin, this section will go over each method that is required to be implemented. The table below provides a high-level overview of the classes, methods, and macros you may have to define for your data platform. + +| file | component | purpose | +|---------------------------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `./setup.py` | `setup()` function | adapter meta-data (package name, version, author, homepage, etc) | +| `myadapter/dbt/adapters/myadapter/__init__.py` | `AdapterPlugin` | bundle all the information below into a dbt plugin | +| `myadapter/dbt/adapters/myadapter/connections.py` | `MyAdapterCredentials` class | parameters to connect to and configure the database, via a the chosen Python driver | +| `myadapter/dbt/adapters/myadapter/connections.py` | `MyAdapterConnectionManager` class | telling dbt how to interact with the database w.r.t opening/closing connections, executing queries, and fetching data. Effectively a wrapper around the db API or driver. | +| `myadapter/dbt/include/bigquery/` | a dbt project of macro "overrides" in the format of "myadapter__" | any differences in SQL syntax for regular db operations will be modified here from the global_project (e.g. "Create Table As Select", "Get all relations in the current schema", etc) | +| `myadapter/dbt/adapters/myadapter/impl.py` | `MyAdapterConfig` | database- and relation-level configs and | +| `myadapter/dbt/adapters/myadapter/impl.py` | `MyAdapterAdapter` | for changing _how_ dbt performs operations like macros and other needed Python functionality | +| `myadapter/dbt/adapters/myadapter/column.py` | `MyAdapterColumn` | for defining database-specific column such as datatype mappings | + +### Editing `setup.py` + +Edit the file at `myadapter/setup.py` and fill in the missing information. + +You can skip this step if you passed the arguments for `email`, `url`, `author`, and `dependencies` to the cookiecutter template script. If you plan on having nested macro folder structures, you may need to add entries to `package_data` so your macro source files get installed. + +### Editing the connection manager + +Edit the connection manager at `myadapter/dbt/adapters/myadapter/connections.py`. This file is defined in the sections below. + +#### The Credentials class + +The credentials class defines all of the database-specific credentials (e.g. `username` and `password`) that users will need in the [connection profile](/docs/supported-data-platforms) for your new adapter. Each credentials contract should subclass dbt.adapters.base.Credentials, and be implemented as a python dataclass. + +Note that the base class includes required database and schema fields, as dbt uses those values internally. + +For example, if your adapter requires a host, integer port, username string, and password string, but host is the only required field, you'd add definitions for those new properties to the class as types, like this: + + + +```python + +from dataclasses import dataclass +from typing import Optional + +from dbt.adapters.base import Credentials + + +@dataclass +class MyAdapterCredentials(Credentials): + host: str + port: int = 1337 + username: Optional[str] = None + password: Optional[str] = None + + @property + def type(self): + return 'myadapter' + + @property + def unique_field(self): + """ + Hashed and included in anonymous telemetry to track adapter adoption. + Pick a field that can uniquely identify one team/organization building with this adapter + """ + return self.host + + def _connection_keys(self): + """ + List of keys to display in the `dbt debug` output. + """ + return ('host', 'port', 'database', 'username') +``` + + + +There are a few things you can do to make it easier for users when connecting to your database: + +- Be sure to implement the Credentials' `_connection_keys` method shown above. This method will return the keys that should be displayed in the output of the `dbt debug` command. As a general rule, it's good to return all the arguments used in connecting to the actual database except the password (even optional arguments). +- Create a `profile_template.yml` to enable configuration prompts for a brand-new user setting up a connection profile via the [`dbt init` command](/reference/commands/init). See more details [below](#other-files). +- You may also want to define an `ALIASES` mapping on your Credentials class to include any config names you want users to be able to use in place of 'database' or 'schema'. For example if everyone using the MyAdapter database calls their databases "collections", you might do: + + + +```python +@dataclass +class MyAdapterCredentials(Credentials): + host: str + port: int = 1337 + username: Optional[str] = None + password: Optional[str] = None + + ALIASES = { + 'collection': 'database', + } +``` + + + +Then users can use `collection` OR `database` in their `profiles.yml`, `dbt_project.yml`, or `config()` calls to set the database. + +#### `ConnectionManager` class methods + +Once credentials are configured, you'll need to implement some connection-oriented methods. They are enumerated in the SQLConnectionManager docstring, but an overview will also be provided here. + +**Methods to implement:** + +- `open` +- `get_response` +- `cancel` +- `exception_handler` +- `standardize_grants_dict` + +##### `open(cls, connection)` + +`open()` is a classmethod that gets a connection object (which could be in any state, but will have a `Credentials` object with the attributes you defined above) and moves it to the 'open' state. + +Generally this means doing the following: + - if the connection is open already, log and return it. + - If a database needed changes to the underlying connection before re-use, that would happen here + - create a connection handle using the underlying database library using the credentials + - on success: + - set connection.state to `'open'` + - set connection.handle to the handle object + - this is what must have a `cursor()` method that returns a cursor! + - on error: + - set connection.state to `'fail'` + - set connection.handle to `None` + - raise a `dbt.exceptions.FailedToConnectException` with the error and any other relevant information + +For example: + + + +```python + @classmethod + def open(cls, connection): + if connection.state == 'open': + logger.debug('Connection is already open, skipping open.') + return connection + + credentials = connection.credentials + + try: + handle = myadapter_library.connect( + host=credentials.host, + port=credentials.port, + username=credentials.username, + password=credentials.password, + catalog=credentials.database + ) + connection.state = 'open' + connection.handle = handle + return connection +``` + + + +##### `get_response(cls, cursor)` + +`get_response` is a classmethod that gets a cursor object and returns adapter-specific information about the last executed command. The return value should be an `AdapterResponse` object that includes items such as `code`, `rows_affected`, `bytes_processed`, and a summary `_message` for logging to stdout. + + + +```python + @classmethod + def get_response(cls, cursor) -> AdapterResponse: + code = cursor.sqlstate or "OK" + rows = cursor.rowcount + status_message = f"{code} {rows}" + return AdapterResponse( + _message=status_message, + code=code, + rows_affected=rows + ) +``` + + + +##### `cancel(self, connection)` + +`cancel` is an instance method that gets a connection object and attempts to cancel any ongoing queries, which is database dependent. Some databases don't support the concept of cancellation, they can simply implement it via 'pass' and their adapter classes should implement an `is_cancelable` that returns False - On ctrl+c connections may remain running. This method must be implemented carefully, as the affected connection will likely be in use in a different thread. + + + +```python + def cancel(self, connection): + tid = connection.handle.transaction_id() + sql = 'select cancel_transaction({})'.format(tid) + logger.debug("Cancelling query '{}' ({})".format(connection_name, pid)) + _, cursor = self.add_query(sql, 'master') + res = cursor.fetchone() + logger.debug("Canceled query '{}': {}".format(connection_name, res)) +``` + + + +##### `exception_handler(self, sql, connection_name='master')` + +`exception_handler` is an instance method that returns a context manager that will handle exceptions raised by running queries, catch them, log appropriately, and then raise exceptions dbt knows how to handle. + +If you use the (highly recommended) `@contextmanager` decorator, you only have to wrap a `yield` inside a `try` block, like so: + + + +```python + @contextmanager + def exception_handler(self, sql: str): + try: + yield + except myadapter_library.DatabaseError as exc: + self.release(connection_name) + + logger.debug('myadapter error: {}'.format(str(e))) + raise dbt.exceptions.DatabaseException(str(exc)) + except Exception as exc: + logger.debug("Error running SQL: {}".format(sql)) + logger.debug("Rolling back transaction.") + self.release(connection_name) + raise dbt.exceptions.RuntimeException(str(exc)) +``` + + + +##### `standardize_grants_dict(self, grants_table: agate.Table) -> dict` + +`standardize_grants_dict` is an method that returns the dbt-standardized grants dictionary that matches how users configure grants now in dbt. The input is the result of `SHOW GRANTS ON {{model}}` call loaded into an agate table. + +If there's any massaging of agate table containing the results, of `SHOW GRANTS ON {{model}}`, that can't easily be accomplished in SQL, it can be done here. For example, the SQL to show grants _should_ filter OUT any grants TO the current user/role (e.g. OWNERSHIP). If that's not possible in SQL, it can be done in this method instead. + + + +```python + @available + def standardize_grants_dict(self, grants_table: agate.Table) -> dict: + """ + :param grants_table: An agate table containing the query result of + the SQL returned by get_show_grant_sql + :return: A standardized dictionary matching the `grants` config + :rtype: dict + """ + grants_dict: Dict[str, List[str]] = {} + for row in grants_table: + grantee = row["grantee"] + privilege = row["privilege_type"] + if privilege in grants_dict.keys(): + grants_dict[privilege].append(grantee) + else: + grants_dict.update({privilege: [grantee]}) + return grants_dict +``` + + + +### Editing the adapter implementation + +Edit the connection manager at `myadapter/dbt/adapters/myadapter/impl.py` + +Very little is required to implement the adapter itself. On some adapters, you will not need to override anything. On others, you'll likely need to override some of the ``convert_*`` classmethods, or override the `is_cancelable` classmethod on others to return `False`. + +#### `datenow()` + +This classmethod provides the adapter's canonical date function. This is not used but is required– anyway on all adapters. + + + +```python + @classmethod + def date_function(cls): + return 'datenow()' +``` + + + +### Editing SQL logic + +dbt implements specific SQL operations using jinja macros. While reasonable defaults are provided for many such operations (like `create_schema`, `drop_schema`, `create_table`, etc), you may need to override one or more of macros when building a new adapter. + +#### Required macros + +The following macros must be implemented, but you can override their behavior for your adapter using the "dispatch" pattern described below. Macros marked (required) do not have a valid default implementation, and are required for dbt to operate. + +- `alter_column_type` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/columns.sql#L37-L55)) +- `check_schema_exists` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L43-L55)) +- `create_schema` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/schema.sql#L1-L9)) +- `drop_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L34-L42)) +- `drop_schema` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/schema.sql#L12-L20)) +- `get_columns_in_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/columns.sql#L1-L8)) (required) +- `list_relations_without_caching` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L58-L65)) (required) +- `list_schemas` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/metadata.sql#L29-L40)) +- `rename_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L56-L65)) +- `truncate_relation` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/relation.sql#L45-L53)) +- `current_timestamp` ([source](https://github.com/dbt-labs/dbt-core/blob/f988f76fccc1878aaf8d8631c05be3e9104b3b9a/core/dbt/include/global_project/macros/adapters/freshness.sql#L1-L8)) (required) +- `copy_grants` + +#### Adapter dispatch + +Most modern databases support a majority of the standard SQL spec. There are some databases that _do not_ support critical aspects of the SQL spec however, or they provide their own nonstandard mechanisms for implementing the same functionality. To account for these variations in SQL support, dbt provides a mechanism called [multiple dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch) for macros. With this feature, macros can be overridden for specific adapters. This makes it possible to implement high-level methods (like "create ") in a database-specific way. + + + +```jinja2 + +{# dbt will call this macro by name, providing any arguments #} +{% macro create_table_as(temporary, relation, sql) -%} + + {# dbt will dispatch the macro call to the relevant macro #} + {{ return( + adapter.dispatch('create_table_as')(temporary, relation, sql) + ) }} +{%- endmacro %} + + + +{# If no macro matches the specified adapter, "default" will be used #} +{% macro default__create_table_as(temporary, relation, sql) -%} + ... +{%- endmacro %} + + + +{# Example which defines special logic for Redshift #} +{% macro redshift__create_table_as(temporary, relation, sql) -%} + ... +{%- endmacro %} + + + +{# Example which defines special logic for BigQuery #} +{% macro bigquery__create_table_as(temporary, relation, sql) -%} + ... +{%- endmacro %} +``` + + + +The `adapter.dispatch()` macro takes a second argument, `packages`, which represents a set of "search namespaces" in which to find potential implementations of a dispatched macro. This allows users of community-supported adapters to extend or "shim" dispatched macros from common packages, such as `dbt-utils`, with adapter-specific versions in their own project or other installed packages. See: + +- "Shim" package examples: [`spark-utils`](https://github.com/dbt-labs/spark-utils), [`tsql-utils`](https://github.com/dbt-msft/tsql-utils) +- [`adapter.dispatch` docs](/reference/dbt-jinja-functions/dispatch) + +#### Overriding adapter methods + +While much of dbt's adapter-specific functionality can be modified in adapter macros, it can also make sense to override adapter methods directly. In this example, assume that a database does not support a `cascade` parameter to `drop schema`. Instead, we can implement an approximation where we drop each relation and then drop the schema. + + + +```python + def drop_schema(self, relation: BaseRelation): + relations = self.list_relations( + database=relation.database, + schema=relation.schema + ) + for relation in relations: + self.drop_relation(relation) + super().drop_schema(relation) +``` + + + +#### Grants Macros + +See [this GitHub discussion](https://github.com/dbt-labs/dbt-core/discussions/5468) for information on the macros required for `GRANT` statements: + +### Other files + +#### `profile_template.yml` + +In order to enable the [`dbt init` command](/reference/commands/init) to prompt users when setting up a new project and connection profile, you should include a **profile template**. The filepath needs to be `dbt/include//profile_template.yml`. It's possible to provide hints, default values, and conditional prompts based on connection methods that require different supporting attributes. Users will also be able to include custom versions of this file in their own projects, with fixed values specific to their organization, to support their colleagues when using your dbt adapter for the first time. + +See examples: + +- [dbt-postgres](https://github.com/dbt-labs/dbt-core/blob/main/plugins/postgres/dbt/include/postgres/profile_template.yml) +- [dbt-redshift](https://github.com/dbt-labs/dbt-redshift/blob/main/dbt/include/redshift/profile_template.yml) +- [dbt-snowflake](https://github.com/dbt-labs/dbt-snowflake/blob/main/dbt/include/snowflake/profile_template.yml) +- [dbt-bigquery](https://github.com/dbt-labs/dbt-bigquery/blob/main/dbt/include/bigquery/profile_template.yml) + +#### `__version__.py` + +To assure that `dbt --version` provides the latest dbt core version the adapter supports, be sure include a `__version__.py` file. The filepath will be `dbt/adapters//__version__.py`. We recommend using the latest dbt core version and as the adapter is made compatible with later versions, this file will need to be updated. For a sample file, check out this [example](https://github.com/dbt-labs/dbt-snowflake/blob/main/dbt/adapters/snowflake/__version__.py). + +It should be noted that both of these files are included in the bootstrapped output of the `dbt-database-adapter-scaffold` so when using the scaffolding, these files will be included. + +## Testing your adapter + +:::info + +Previously, we offered a packaged suite of tests for dbt adapter functionality: [`pytest-dbt-adapter`](https://github.com/dbt-labs/dbt-adapter-tests). We are deprecating that suite, in favor of the newer testing framework outlined in this document. + +::: + +This document has two sections: + +1. "[About the testing framework](#about-the-testing-framework)" describes the standard framework that we maintain for using pytest together with dbt. It includes an example that shows the anatomy of a simple test case. +2. "[Testing your adapter](#testing-your-adapter)" offers a step-by-step guide for using our out-of-the-box suite of "basic" tests, which will validate that your adapter meets a baseline of dbt functionality. + +### Testing prerequisites + +- Your adapter must be compatible with dbt-core **v1.1** or newer +- You should be familiar with **pytest**: + +### About the testing framework + +dbt-core offers a standard framework for running pre-built functional tests, and for defining your own tests. The core testing framework is built using `pytest`, a mature and standard library for testing Python projects. + +The **[`tests` module](https://github.com/dbt-labs/dbt-core/tree/HEAD/core/dbt/tests)** within `dbt-core` includes basic utilities for setting up pytest + dbt. These are used by all "pre-built" functional tests, and make it possible to quickly write your own tests. + +Those utilities allow you to do three basic things: + +1. **Quickly set up a dbt "project."** Define project resources via methods such as `models()` and `seeds()`. Use `project_config_update()` to pass configurations into `dbt_project.yml`. +2. **Define a sequence of dbt commands.** The most important utility is `run_dbt()`, which returns the [results](/reference/dbt-classes#result-objects) of each dbt command. It takes a list of CLI specifiers (subcommand + flags), as well as an optional second argument, `expect_pass=False`, for cases where you expect the command to fail. +3. **Validate the results of those dbt commands.** For example, `check_relations_equal()` asserts that two database objects have the same structure and content. You can also write your own `assert` statements, by inspecting the results of a dbt command, or querying arbitrary database objects with `project.run_sql()`. + +You can see the full suite of utilities, with arguments and annotations, in [`util.py`](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/tests/util.py). You'll also see them crop up across a number of test cases. While all utilities are intended to be reusable, you won't need all of them for every test. In the example below, we'll show a simple test case that uses only a few utilities. + +#### Example: a simple test case + +This example will show you the anatomy of a test case using dbt + pytest. We will create reusable components, combine them to form a dbt "project", and define a sequence of dbt commands. Then, we'll use Python `assert` statements to ensure those commands succeed (or fail) as we expect. + +In ["Getting started running basic tests,"](#getting-started-running-basic-tests) we'll offer step-by-step instructions for installing and configuring `pytest`, so that you can run it on your own machine. For now, it's more important to see how the pieces of a test case fit together. + +This example includes a seed, a model, and two tests—one of which will fail. + +1. Define Python strings that will represent the file contents in your dbt project. Defining these in a separate file enables you to reuse the same components across different test cases. The pytest name for this type of reusable component is "fixture." + + + +```python +# seeds/my_seed.csv +my_seed_csv = """ +id,name,some_date +1,Easton,1981-05-20T06:46:51 +2,Lillian,1978-09-03T18:10:33 +3,Jeremiah,1982-03-11T03:59:51 +4,Nolan,1976-05-06T20:21:35 +""".lstrip() + +# models/my_model.sql +my_model_sql = """ +select * from {{ ref('my_seed') }} +union all +select null as id, null as name, null as some_date +""" + +# models/my_model.yml +my_model_yml = """ +version: 2 +models: + - name: my_model + columns: + - name: id + tests: + - unique + - not_null # this test will fail +""" +``` + + + +2. Use the "fixtures" to define the project for your test case. These fixtures are always scoped to the **class**, where the class represents one test case—that is, one dbt project or scenario. (The same test case can be used for one or more actual tests, which we'll see in step 3.) Following the default pytest configurations, the file name must begin with `test_`, and the class name must begin with `Test`. + + + +```python +import pytest +from dbt.tests.util import run_dbt + +# our file contents +from tests.functional.example.fixtures import ( + my_seed_csv, + my_model_sql, + my_model_yml, +) + +# class must begin with 'Test' +class TestExample: + """ + Methods in this class will be of two types: + 1. Fixtures defining the dbt "project" for this test case. + These are scoped to the class, and reused for all tests in the class. + 2. Actual tests, whose names begin with 'test_'. + These define sequences of dbt commands and 'assert' statements. + """ + + # configuration in dbt_project.yml + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "name": "example", + "models": {"+materialized": "view"} + } + + # everything that goes in the "seeds" directory + @pytest.fixture(scope="class") + def seeds(self): + return { + "my_seed.csv": my_seed_csv, + } + + # everything that goes in the "models" directory + @pytest.fixture(scope="class") + def models(self): + return { + "my_model.sql": my_model_sql, + "my_model.yml": my_model_yml, + } + + # continues below +``` + + + +3. Now that we've set up our project, it's time to define a sequence of dbt commands and assertions. We define one or more methods in the same file, on the same class (`TestExampleFailingTest`), whose names begin with `test_`. These methods share the same setup (project scenario) from above, but they can be run independently by pytest—so they shouldn't depend on each other in any way. + + + +```python + # continued from above + + # The actual sequence of dbt commands and assertions + # pytest will take care of all "setup" + "teardown" + def test_run_seed_test(self, project): + """ + Seed, then run, then test. We expect one of the tests to fail + An alternative pattern is to use pytest "xfail" (see below) + """ + # seed seeds + results = run_dbt(["seed"]) + assert len(results) == 1 + # run models + results = run_dbt(["run"]) + assert len(results) == 1 + # test tests + results = run_dbt(["test"], expect_pass = False) # expect failing test + assert len(results) == 2 + # validate that the results include one pass and one failure + result_statuses = sorted(r.status for r in results) + assert result_statuses == ["fail", "pass"] + + @pytest.mark.xfail + def test_build(self, project): + """Expect a failing test""" + # do it all + results = run_dbt(["build"]) +``` + + + +3. Our test is ready to run! The last step is to invoke `pytest` from your command line. We'll walk through the actual setup and configuration of `pytest` in the next section. + + + +```sh +$ python3 -m pytest tests/functional/test_example.py +=========================== test session starts ============================ +platform ... -- Python ..., pytest-..., pluggy-... +rootdir: ... +plugins: ... + +tests/functional/test_example.py .X [100%] + +======================= 1 passed, 1 xpassed in 1.38s ======================= +``` + + + +You can find more ways to run tests, along with a full command reference, in the [pytest usage docs](https://docs.pytest.org/how-to/usage.html). + +We've found the `-s` flag (or `--capture=no`) helpful to print logs from the underlying dbt invocations, and to step into an interactive debugger if you've added one. You can also use environment variables to set [global dbt configs](/reference/global-configs/about-global-configs), such as `DBT_DEBUG` (to show debug-level logs). + +### Testing this adapter + +Anyone who installs `dbt-core`, and wishes to define their own test cases, can use the framework presented in the first section. The framework is especially useful for testing standard dbt behavior across different databases. + +To that end, we have built and made available a [package of reusable adapter test cases](https://github.com/dbt-labs/dbt-core/tree/HEAD/tests/adapter), for creators and maintainers of adapter plugins. These test cases cover basic expected functionality, as well as functionality that frequently requires different implementations across databases. + +For the time being, this package is also located within the `dbt-core` repository, but separate from the `dbt-core` Python package. + +### Categories of tests + +In the course of creating and maintaining your adapter, it's likely that you will end up implementing tests that fall into three broad categories: + +1. **Basic tests** that every adapter plugin is expected to pass. These are defined in `tests.adapter.basic`. Given differences across data platforms, these may require slight modification or reimplementation. Significantly overriding or disabling these tests should be with good reason, since each represents basic functionality expected by dbt users. For example, if your adapter does not support incremental models, you should disable the test, [by marking it with `skip` or `xfail`](https://docs.pytest.org/en/latest/how-to/skipping.html), as well as noting that limitation in any documentation, READMEs, and usage guides that accompany your adapter. + +2. **Optional tests**, for second-order functionality that is common across plugins, but not required for basic use. Your plugin can opt into these test cases by inheriting existing ones, or reimplementing them with adjustments. For now, this category includes all tests located outside the `basic` subdirectory. More tests will be added as we convert older tests defined on dbt-core and mature plugins to use the standard framework. + +3. **Custom tests**, for behavior that is specific to your adapter / data platform. Each has its own specialties and idiosyncracies. We encourage you to use the same `pytest`-based framework, utilities, and fixtures to write your own custom tests for functionality that is unique to your adapter. + +If you run into an issue with the core framework, or the basic/optional test cases—or if you've written a custom test that you believe would be relevant and useful for other adapter plugin developers—please open an issue or PR in the `dbt-core` repository on GitHub. + +### Getting started running basic tests + +In this section, we'll walk through the three steps to start running our basic test cases on your adapter plugin: + +1. Install dependencies +2. Set up and configure pytest +3. Define test cases + +### Install dependencies + +You should already have a virtual environment with `dbt-core` and your adapter plugin installed. You'll also need to install: + +- [`pytest`](https://pypi.org/project/pytest/) +- [`dbt-tests-adapter`](https://pypi.org/project/dbt-tests-adapter/), the set of common test cases +- (optional) [`pytest` plugins](https://docs.pytest.org/en/7.0.x/reference/plugin_list.html)--we'll use `pytest-dotenv` below + +Or specify all dependencies in a requirements file like: + + +```txt +pytest +pytest-dotenv +dbt-tests-adapter +``` + + + +```sh +pip install -r dev_requirements.txt +``` + +### Set up and configure pytest + +First, set yourself up to run `pytest` by creating a file named `pytest.ini` at the root of your repository: + + + +```python +[pytest] +filterwarnings = + ignore:.*'soft_unicode' has been renamed to 'soft_str'*:DeprecationWarning + ignore:unclosed file .*:ResourceWarning +env_files = + test.env # uses pytest-dotenv plugin + # this allows you to store env vars for database connection in a file named test.env + # rather than passing them in every CLI command, or setting in `PYTEST_ADDOPTS` + # be sure to add "test.env" to .gitignore as well! +testpaths = + tests/functional # name per convention +``` + + + +Then, create a configuration file within your tests directory. In it, you'll want to define all necessary profile configuration for connecting to your data platform in local development and continuous integration. We recommend setting these values with environment variables, since this file will be checked into version control. + + + +```python +import pytest +import os + +# Import the standard functional fixtures as a plugin +# Note: fixtures with session scope need to be local +pytest_plugins = ["dbt.tests.fixtures.project"] + +# The profile dictionary, used to write out profiles.yml +# dbt will supply a unique schema per test, so we do not specify 'schema' here +@pytest.fixture(scope="class") +def dbt_profile_target(): + return { + 'type': '', + 'threads': 1, + 'host': os.getenv('HOST_ENV_VAR_NAME'), + 'user': os.getenv('USER_ENV_VAR_NAME'), + ... + } +``` + + + +### Define test cases + +As in the example above, each test case is defined as a class, and has its own "project" setup. To get started, you can import all basic test cases and try running them without changes. + + + +```python +import pytest + +from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations +from dbt.tests.adapter.basic.test_singular_tests import BaseSingularTests +from dbt.tests.adapter.basic.test_singular_tests_ephemeral import BaseSingularTestsEphemeral +from dbt.tests.adapter.basic.test_empty import BaseEmpty +from dbt.tests.adapter.basic.test_ephemeral import BaseEphemeral +from dbt.tests.adapter.basic.test_incremental import BaseIncremental +from dbt.tests.adapter.basic.test_generic_tests import BaseGenericTests +from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols +from dbt.tests.adapter.basic.test_snapshot_timestamp import BaseSnapshotTimestamp +from dbt.tests.adapter.basic.test_adapter_methods import BaseAdapterMethod + +class TestSimpleMaterializationsMyAdapter(BaseSimpleMaterializations): + pass + + +class TestSingularTestsMyAdapter(BaseSingularTests): + pass + + +class TestSingularTestsEphemeralMyAdapter(BaseSingularTestsEphemeral): + pass + + +class TestEmptyMyAdapter(BaseEmpty): + pass + + +class TestEphemeralMyAdapter(BaseEphemeral): + pass + + +class TestIncrementalMyAdapter(BaseIncremental): + pass + + +class TestGenericTestsMyAdapter(BaseGenericTests): + pass + + +class TestSnapshotCheckColsMyAdapter(BaseSnapshotCheckCols): + pass + + +class TestSnapshotTimestampMyAdapter(BaseSnapshotTimestamp): + pass + + +class TestBaseAdapterMethod(BaseAdapterMethod): + pass +``` + + + +Finally, run pytest: + +```sh +python3 -m pytest tests/functional +``` + +### Modifying test cases + +You may need to make slight modifications in a specific test case to get it passing on your adapter. The mechanism to do this is simple: rather than simply inheriting the "base" test with `pass`, you can redefine any of its fixtures or test methods. + +For instance, on Redshift, we need to explicitly cast a column in the fixture input seed to use data type `varchar(64)`: + + + +```python +import pytest +from dbt.tests.adapter.basic.files import seeds_base_csv, seeds_added_csv, seeds_newcolumns_csv +from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols + +# set the datatype of the name column in the 'added' seed so it +# can hold the '_update' that's added +schema_seed_added_yml = """ +version: 2 +seeds: + - name: added + config: + column_types: + name: varchar(64) +""" + +class TestSnapshotCheckColsRedshift(BaseSnapshotCheckCols): + # Redshift defines the 'name' column such that it's not big enough + # to hold the '_update' added in the test. + @pytest.fixture(scope="class") + def models(self): + return { + "base.csv": seeds_base_csv, + "added.csv": seeds_added_csv, + "seeds.yml": schema_seed_added_yml, + } +``` + + + +As another example, the `dbt-bigquery` adapter asks users to "authorize" replacing a with a by supplying the `--full-refresh` flag. The reason: In the table logic, a view by the same name must first be dropped; if the table query fails, the model will be missing. + +Knowing this possibility, the "base" test case offers a `require_full_refresh` switch on the `test_config` fixture class. For BigQuery, we'll switch it on: + + + +```python +import pytest +from dbt.tests.adapter.basic.test_base import BaseSimpleMaterializations + +class TestSimpleMaterializationsBigQuery(BaseSimpleMaterializations): + @pytest.fixture(scope="class") + def test_config(self): + # effect: add '--full-refresh' flag in requisite 'dbt run' step + return {"require_full_refresh": True} +``` + + + +It's always worth asking whether the required modifications represent gaps in perceived or expected dbt functionality. Are these simple implementation details, which any user of this database would understand? Are they limitations worth documenting? + +If, on the other hand, they represent poor assumptions in the "basic" test cases, which fail to account for a common pattern in other types of databases-—please open an issue or PR in the `dbt-core` repository on GitHub. + +### Running with multiple profiles + +Some databases support multiple connection methods, which map to actually different functionality behind the scenes. For instance, the `dbt-spark` adapter supports connections to Apache Spark clusters _and_ Databricks runtimes, which supports additional functionality out of the box, enabled by the Delta file format. + + + +```python +def pytest_addoption(parser): + parser.addoption("--profile", action="store", default="apache_spark", type=str) + + +# Using @pytest.mark.skip_profile('apache_spark') uses the 'skip_by_profile_type' +# autouse fixture below +def pytest_configure(config): + config.addinivalue_line( + "markers", + "skip_profile(profile): skip test for the given profile", + ) + +@pytest.fixture(scope="session") +def dbt_profile_target(request): + profile_type = request.config.getoption("--profile") + elif profile_type == "databricks_sql_endpoint": + target = databricks_sql_endpoint_target() + elif profile_type == "apache_spark": + target = apache_spark_target() + else: + raise ValueError(f"Invalid profile type '{profile_type}'") + return target + +def apache_spark_target(): + return { + "type": "spark", + "host": "localhost", + ... + } + +def databricks_sql_endpoint_target(): + return { + "type": "spark", + "host": os.getenv("DBT_DATABRICKS_HOST_NAME"), + ... + } + +@pytest.fixture(autouse=True) +def skip_by_profile_type(request): + profile_type = request.config.getoption("--profile") + if request.node.get_closest_marker("skip_profile"): + for skip_profile_type in request.node.get_closest_marker("skip_profile").args: + if skip_profile_type == profile_type: + pytest.skip("skipped on '{profile_type}' profile") +``` + + + +If there are tests that _shouldn't_ run for a given profile: + + + +```python +# Snapshots require access to the Delta file format, available on our Databricks connection, +# so let's skip on Apache Spark +@pytest.mark.skip_profile('apache_spark') +class TestSnapshotCheckColsSpark(BaseSnapshotCheckCols): + @pytest.fixture(scope="class") + def project_config_update(self): + return { + "seeds": { + "+file_format": "delta", + }, + "snapshots": { + "+file_format": "delta", + } + } +``` + + + +Finally: + +```sh +python3 -m pytest tests/functional --profile apache_spark +python3 -m pytest tests/functional --profile databricks_sql_endpoint +``` diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md deleted file mode 100644 index 6fb436c5bc4..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" -id: "dbt-python-snowpark" -description: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" -hoverSnippet: Learn how to use a webhook or Slack message to trigger Zapier to post error context in Slack when a job fails. -# time_to_complete: '30 minutes' commenting out until we test -icon: 'guides' -hide_table_of_contents: true -tags: ['Webhooks'] -level: 'Advanced' -recently_updated: true ---- - -## Introduction - -The focus of this workshop will be to demonstrate how we can use both *SQL and python together* in the same workflow to run *both analytics and machine learning models* on dbt Cloud. - -All code in today’s workshop can be found on [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1). - -### What you'll use during the lab - -- A [Snowflake account](https://trial.snowflake.com/) with ACCOUNTADMIN access -- A [dbt Cloud account](https://www.getdbt.com/signup/) - -### What you'll learn - -- How to build scalable data transformation pipelines using dbt, and Snowflake using SQL and Python -- How to leverage copying data into Snowflake from a public S3 bucket - -### What you need to know - -- Basic to intermediate SQL and python. -- Basic understanding of dbt fundamentals. We recommend the [dbt Fundamentals course](https://courses.getdbt.com/collections) if you're interested. -- High level machine learning process (encoding, training, testing) -- Simple ML algorithms — we will use logistic regression to keep the focus on the *workflow*, not algorithms! - -### What you'll build - -- A set of data analytics and prediction pipelines using Formula 1 data leveraging dbt and Snowflake, making use of best practices like data quality tests and code promotion between environments -- We will create insights for: - 1. Finding the lap time average and rolling average through the years (is it generally trending up or down)? - 2. Which constructor has the fastest pit stops in 2021? - 3. Predicting the position of each driver given using a decade of data (2010 - 2020) - -As inputs, we are going to leverage Formula 1 datasets hosted on a dbt Labs public S3 bucket. We will create a Snowflake Stage for our CSV files then use Snowflake’s `COPY INTO` function to copy the data in from our CSV files into tables. The Formula 1 is available on [Kaggle](https://www.kaggle.com/datasets/rohanrao/formula-1-world-championship-1950-2020). The data is originally compiled from the [Ergast Developer API](http://ergast.com/mrd/). - -Overall we are going to set up the environments, build scalable pipelines in dbt, establish data tests, and promote code to production. - -;' diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations.md deleted file mode 100644 index 446981214e3..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: "Python transformations!" -id: "10-python-transformations" -description: "Python transformations" ---- - -Up until now, SQL has been driving the project (car pun intended) for data cleaning and hierarchical joining. Now it’s time for Python to take the wheel (car pun still intended) for the rest of our lab! For more information about running Python models on dbt, check out our [docs](/docs/build/python-models). To learn more about dbt python works under the hood, check out [Snowpark for Python](https://docs.snowflake.com/en/developer-guide/snowpark/python/index.html), which makes running dbt Python models possible. - -There are quite a few differences between SQL and Python in terms of the dbt syntax and DDL, so we’ll be breaking our code and model runs down further for our python models. - -## Pit stop analysis - -First, we want to find out: which constructor had the fastest pit stops in 2021? (constructor is a Formula 1 team that builds or “constructs” the car). - -1. Create a new file called `fastest_pit_stops_by_constructor.py` in our `aggregates` (this is the first time we are using the `.py` extension!). -2. Copy the following code into the file: - ```python - import numpy as np - import pandas as pd - - def model(dbt, session): - # dbt configuration - dbt.config(packages=["pandas","numpy"]) - - # get upstream data - pit_stops_joined = dbt.ref("pit_stops_joined").to_pandas() - - # provide year so we do not hardcode dates - year=2021 - - # describe the data - pit_stops_joined["PIT_STOP_SECONDS"] = pit_stops_joined["PIT_STOP_MILLISECONDS"]/1000 - fastest_pit_stops = pit_stops_joined[(pit_stops_joined["RACE_YEAR"]==year)].groupby(by="CONSTRUCTOR_NAME")["PIT_STOP_SECONDS"].describe().sort_values(by='mean') - fastest_pit_stops.reset_index(inplace=True) - fastest_pit_stops.columns = fastest_pit_stops.columns.str.upper() - - return fastest_pit_stops.round(2) - ``` - -3. Let’s break down what this code is doing step by step: - - First, we are importing the Python libraries that we are using. A *library* is a reusable chunk of code that someone else wrote that you may want to include in your programs/projects. We are using `numpy` and `pandas`in this Python model. This is similar to a dbt *package*, but our Python libraries do *not* persist across the entire project. - - Defining a function called `model` with the parameter `dbt` and `session`. The parameter `dbt` is a class compiled by dbt, which enables you to run your Python code in the context of your dbt project and DAG. The parameter `session` is a class representing your Snowflake’s connection to the Python backend. The `model` function *must return a single DataFrame*. You can see that all the data transformation happening is within the body of the `model` function that the `return` statement is tied to. - - Then, within the context of our dbt model library, we are passing in a configuration of which packages we need using `dbt.config(packages=["pandas","numpy"])`. - - Use the `.ref()` function to retrieve the data frame `pit_stops_joined` that we created in our last step using SQL. We cast this to a pandas dataframe (by default it's a Snowpark Dataframe). - - Create a variable named `year` so we aren’t passing a hardcoded value. - - Generate a new column called `PIT_STOP_SECONDS` by dividing the value of `PIT_STOP_MILLISECONDS` by 1000. - - Create our final data frame `fastest_pit_stops` that holds the records where year is equal to our year variable (2021 in this case), then group the data frame by `CONSTRUCTOR_NAME` and use the `describe()` and `sort_values()` and in descending order. This will make our first row in the new aggregated data frame the team with the fastest pit stops over an entire competition year. - - Finally, it resets the index of the `fastest_pit_stops` data frame. The `reset_index()` method allows you to reset the index back to the default 0, 1, 2, etc indexes. By default, this method will keep the "old" indexes in a column named "index"; to avoid this, use the drop parameter. Think of this as keeping your data “flat and square” as opposed to “tiered”. If you are new to Python, now might be a good time to [learn about indexes for 5 minutes](https://towardsdatascience.com/the-basics-of-indexing-and-slicing-python-lists-2d12c90a94cf) since it's the foundation of how Python retrieves, slices, and dices data. The `inplace` argument means we override the existing data frame permanently. Not to fear! This is what we want to do to avoid dealing with multi-indexed dataframes! - - Convert our Python column names to all uppercase using `.upper()`, so Snowflake recognizes them. - - Finally we are returning our dataframe with 2 decimal places for all the columns using the `round()` method. -4. Zooming out a bit, what are we doing differently here in Python from our typical SQL code: - - Method chaining is a technique in which multiple methods are called on an object in a single statement, with each method call modifying the result of the previous one. The methods are called in a chain, with the output of one method being used as the input for the next one. The technique is used to simplify the code and make it more readable by eliminating the need for intermediate variables to store the intermediate results. - - The way you see method chaining in Python is the syntax `.().()`. For example, `.describe().sort_values(by='mean')` where the `.describe()` method is chained to `.sort_values()`. - - The `.describe()` method is used to generate various summary statistics of the dataset. It's used on pandas dataframe. It gives a quick and easy way to get the summary statistics of your dataset without writing multiple lines of code. - - The `.sort_values()` method is used to sort a pandas dataframe or a series by one or multiple columns. The method sorts the data by the specified column(s) in ascending or descending order. It is the pandas equivalent to `order by` in SQL. - - We won’t go as in depth for our subsequent scripts, but will continue to explain at a high level what new libraries, functions, and methods are doing. - -5. Build the model using the UI which will **execute**: - ```bash - dbt run --select fastest_pit_stops_by_constructor - ``` - in the command bar. - - Let’s look at some details of our first Python model to see what our model executed. There two major differences we can see while running a Python model compared to an SQL model: - - - Our Python model was executed as a stored procedure. Snowflake needs a way to know that it's meant to execute this code in a Python runtime, instead of interpreting in a SQL runtime. We do this by creating a Python stored proc, called by a SQL command. - - The `snowflake-snowpark-python` library has been picked up to execute our Python code. Even though this wasn’t explicitly stated this is picked up by the dbt class object because we need our Snowpark package to run Python! - - Python models take a bit longer to run than SQL models, however we could always speed this up by using [Snowpark-optimized Warehouses](https://docs.snowflake.com/en/user-guide/warehouses-snowpark-optimized.html) if we wanted to. Our data is sufficiently small, so we won’t worry about creating a separate warehouse for Python versus SQL files today. - - - The rest of our **Details** output gives us information about how dbt and Snowpark for Python are working together to define class objects and apply a specific set of methods to run our models. - - So which constructor had the fastest pit stops in 2021? Let’s look at our data to find out! - -6. We can't preview Python models directly, so let’s create a new file using the **+** button or the Control-n shortcut to create a new scratchpad. -7. Reference our Python model: - ```sql - select * from {{ ref('fastest_pit_stops_by_constructor') }} - ``` - and preview the output: - - - Not only did Red Bull have the fastest average pit stops by nearly 40 seconds, they also had the smallest standard deviation, meaning they are both fastest and most consistent teams in pit stops. By using the `.describe()` method we were able to avoid verbose SQL requiring us to create a line of code per column and repetitively use the `PERCENTILE_COUNT()` function. - - Now we want to find the lap time average and rolling average through the years (is it generally trending up or down)? - -8. Create a new file called `lap_times_moving_avg.py` in our `aggregates` folder. -9. Copy the following code into the file: - ```python - import pandas as pd - - def model(dbt, session): - # dbt configuration - dbt.config(packages=["pandas"]) - - # get upstream data - lap_times = dbt.ref("int_lap_times_years").to_pandas() - - # describe the data - lap_times["LAP_TIME_SECONDS"] = lap_times["LAP_TIME_MILLISECONDS"]/1000 - lap_time_trends = lap_times.groupby(by="RACE_YEAR")["LAP_TIME_SECONDS"].mean().to_frame() - lap_time_trends.reset_index(inplace=True) - lap_time_trends["LAP_MOVING_AVG_5_YEARS"] = lap_time_trends["LAP_TIME_SECONDS"].rolling(5).mean() - lap_time_trends.columns = lap_time_trends.columns.str.upper() - - return lap_time_trends.round(1) - ``` - -10. Breaking down our code a bit: - - We’re only using the `pandas` library for this model and casting it to a pandas data frame `.to_pandas()`. - - Generate a new column called `LAP_TIMES_SECONDS` by dividing the value of `LAP_TIME_MILLISECONDS` by 1000. - - Create the final dataframe. Get the lap time per year. Calculate the mean series and convert to a data frame. - - Reset the index. - - Calculate the rolling 5 year mean. - - Round our numeric columns to one decimal place. -11. Now, run this model by using the UI **Run model** or - ```bash - dbt run --select lap_times_moving_avg - ``` - in the command bar. - -12. Once again previewing the output of our data using the same steps for our `fastest_pit_stops_by_constructor` model. - - - We can see that it looks like lap times are getting consistently faster over time. Then in 2010 we see an increase occur! Using outside subject matter context, we know that significant rule changes were introduced to Formula 1 in 2010 and 2011 causing slower lap times. - -13. Now is a good time to checkpoint and commit our work to Git. Click **Commit and push** and give your commit a message like `aggregate python models` before moving on. - -## The dbt model, .source(), .ref() and .config() functions - -Let’s take a step back before starting machine learning to both review and go more in-depth at the methods that make running dbt python models possible. If you want to know more outside of this lab’s explanation read the documentation [here](/docs/build/python-models?version=1.3). - -- dbt model(dbt, session). For starters, each Python model lives in a .py file in your models/ folder. It defines a function named `model()`, which takes two parameters: - - dbt — A class compiled by dbt Core, unique to each model, enables you to run your Python code in the context of your dbt project and DAG. - - session — A class representing your data platform’s connection to the Python backend. The session is needed to read in tables as DataFrames and to write DataFrames back to tables. In PySpark, by convention, the SparkSession is named spark, and available globally. For consistency across platforms, we always pass it into the model function as an explicit argument called session. -- The `model()` function must return a single DataFrame. On Snowpark (Snowflake), this can be a Snowpark or pandas DataFrame. -- `.source()` and `.ref()` functions. Python models participate fully in dbt's directed acyclic graph (DAG) of transformations. If you want to read directly from a raw source table, use `dbt.source()`. We saw this in our earlier section using SQL with the source function. These functions have the same execution, but with different syntax. Use the `dbt.ref()` method within a Python model to read data from other models (SQL or Python). These methods return DataFrames pointing to the upstream source, model, seed, or snapshot. -- `.config()`. Just like SQL models, there are three ways to configure Python models: - - In a dedicated `.yml` file, within the `models/` directory - - Within the model's `.py` file, using the `dbt.config()` method - - Calling the `dbt.config()` method will set configurations for your model within your `.py` file, similar to the `{{ config() }} macro` in `.sql` model files: - ```python - def model(dbt, session): - - # setting configuration - dbt.config(materialized="table") - ``` - - There's a limit to how complex you can get with the `dbt.config()` method. It accepts only literal values (strings, booleans, and numeric types). Passing another function or a more complex data structure is not possible. The reason is that dbt statically analyzes the arguments to `.config()` while parsing your model without executing your Python code. If you need to set a more complex configuration, we recommend you define it using the config property in a [YAML file](/reference/resource-properties/config). Learn more about configurations [here](/reference/model-configs). diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep.md deleted file mode 100644 index bde163b59db..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: "Machine Learning prep: cleaning, encoding, and splits, oh my!" -id: "11-machine-learning-prep" -description: "Machine Learning prep" ---- -Now that we’ve gained insights and business intelligence about Formula 1 at a descriptive level, we want to extend our capabilities into prediction. We’re going to take the scenario where we censor the data. This means that we will pretend that we will train a model using earlier data and apply it to future data. In practice, this means we’ll take data from 2010-2019 to train our model and then predict 2020 data. - -In this section, we’ll be preparing our data to predict the final race position of a driver. - -At a high level we’ll be: - -- Creating new prediction features and filtering our dataset to active drivers -- Encoding our data (algorithms like numbers) and simplifying our target variable called `position` -- Splitting our dataset into training, testing, and validation - -## ML data prep - -1. To keep our project organized, we’ll need to create two new subfolders in our `ml` directory. Under the `ml` folder, make the subfolders `prep` and `train_predict`. -2. Create a new file under `ml/prep` called `ml_data_prep`. Copy the following code into the file and **Save**. - ```python - import pandas as pd - - def model(dbt, session): - # dbt configuration - dbt.config(packages=["pandas"]) - - # get upstream data - fct_results = dbt.ref("fct_results").to_pandas() - - # provide years so we do not hardcode dates in filter command - start_year=2010 - end_year=2020 - - # describe the data for a full decade - data = fct_results.loc[fct_results['RACE_YEAR'].between(start_year, end_year)] - - # convert string to an integer - data['POSITION'] = data['POSITION'].astype(float) - - # we cannot have nulls if we want to use total pit stops - data['TOTAL_PIT_STOPS_PER_RACE'] = data['TOTAL_PIT_STOPS_PER_RACE'].fillna(0) - - # some of the constructors changed their name over the year so replacing old names with current name - mapping = {'Force India': 'Racing Point', 'Sauber': 'Alfa Romeo', 'Lotus F1': 'Renault', 'Toro Rosso': 'AlphaTauri'} - data['CONSTRUCTOR_NAME'].replace(mapping, inplace=True) - - # create confidence metrics for drivers and constructors - dnf_by_driver = data.groupby('DRIVER').sum()['DNF_FLAG'] - driver_race_entered = data.groupby('DRIVER').count()['DNF_FLAG'] - driver_dnf_ratio = (dnf_by_driver/driver_race_entered) - driver_confidence = 1-driver_dnf_ratio - driver_confidence_dict = dict(zip(driver_confidence.index,driver_confidence)) - - dnf_by_constructor = data.groupby('CONSTRUCTOR_NAME').sum()['DNF_FLAG'] - constructor_race_entered = data.groupby('CONSTRUCTOR_NAME').count()['DNF_FLAG'] - constructor_dnf_ratio = (dnf_by_constructor/constructor_race_entered) - constructor_relaiblity = 1-constructor_dnf_ratio - constructor_relaiblity_dict = dict(zip(constructor_relaiblity.index,constructor_relaiblity)) - - data['DRIVER_CONFIDENCE'] = data['DRIVER'].apply(lambda x:driver_confidence_dict[x]) - data['CONSTRUCTOR_RELAIBLITY'] = data['CONSTRUCTOR_NAME'].apply(lambda x:constructor_relaiblity_dict[x]) - - #removing retired drivers and constructors - active_constructors = ['Renault', 'Williams', 'McLaren', 'Ferrari', 'Mercedes', - 'AlphaTauri', 'Racing Point', 'Alfa Romeo', 'Red Bull', - 'Haas F1 Team'] - active_drivers = ['Daniel Ricciardo', 'Kevin Magnussen', 'Carlos Sainz', - 'Valtteri Bottas', 'Lance Stroll', 'George Russell', - 'Lando Norris', 'Sebastian Vettel', 'Kimi Räikkönen', - 'Charles Leclerc', 'Lewis Hamilton', 'Daniil Kvyat', - 'Max Verstappen', 'Pierre Gasly', 'Alexander Albon', - 'Sergio Pérez', 'Esteban Ocon', 'Antonio Giovinazzi', - 'Romain Grosjean','Nicholas Latifi'] - - # create flags for active drivers and constructors so we can filter downstream - data['ACTIVE_DRIVER'] = data['DRIVER'].apply(lambda x: int(x in active_drivers)) - data['ACTIVE_CONSTRUCTOR'] = data['CONSTRUCTOR_NAME'].apply(lambda x: int(x in active_constructors)) - - return data - ``` -3. As usual, let’s break down what we are doing in this Python model: - - We’re first referencing our upstream `fct_results` table and casting it to a pandas dataframe. - - Filtering on years 2010-2020 since we’ll need to clean all our data we are using for prediction (both training and testing). - - Filling in empty data for `total_pit_stops` and making a mapping active constructors and drivers to avoid erroneous predictions - - ⚠️ You might be wondering why we didn’t do this upstream in our `fct_results` table! The reason for this is that we want our machine learning cleanup to reflect the year 2020 for our predictions and give us an up-to-date team name. However, for business intelligence purposes we can keep the historical data at that point in time. Instead of thinking of one table as “one source of truth” we are creating different datasets fit for purpose: one for historical descriptions and reporting and another for relevant predictions. - - Create new confidence features for drivers and constructors - - Generate flags for the constructors and drivers that were active in 2020 -4. Execute the following in the command bar: - ```bash - dbt run --select ml_data_prep - ``` -5. There are more aspects we could consider for this project, such as normalizing the driver confidence by the number of races entered. Including this would help account for a driver’s history and consider whether they are a new or long-time driver. We’re going to keep it simple for now, but these are some of the ways we can expand and improve our machine learning dbt projects. Breaking down our machine learning prep model: - - Lambda functions — We use some lambda functions to transform our data without having to create a fully-fledged function using the `def` notation. So what exactly are lambda functions? - - In Python, a lambda function is a small, anonymous function defined using the keyword "lambda". Lambda functions are used to perform a quick operation, such as a mathematical calculation or a transformation on a list of elements. They are often used in conjunction with higher-order functions, such as `apply`, `map`, `filter`, and `reduce`. - - `.apply()` method — We used `.apply()` to pass our functions into our lambda expressions to the columns and perform this multiple times in our code. Let’s explain apply a little more: - - The `.apply()` function in the pandas library is used to apply a function to a specified axis of a DataFrame or a Series. In our case the function we used was our lambda function! - - The `.apply()` function takes two arguments: the first is the function to be applied, and the second is the axis along which the function should be applied. The axis can be specified as 0 for rows or 1 for columns. We are using the default value of 0 so we aren’t explicitly writing it in the code. This means that the function will be applied to each *row* of the DataFrame or Series. -6. Let’s look at the preview of our clean dataframe after running our `ml_data_prep` model: - - -## Covariate encoding - -In this next part, we’ll be performing covariate encoding. Breaking down this phrase a bit, a *covariate* is a variable that is relevant to the outcome of a study or experiment, and *encoding* refers to the process of converting data (such as text or categorical variables) into a numerical format that can be used as input for a model. This is necessary because most machine learning algorithms can only work with numerical data. Algorithms don’t speak languages, have eyes to see images, etc. so we encode our data into numbers so algorithms can perform tasks by using calculations they otherwise couldn’t. - -🧠 We’ll think about this as : “algorithms like numbers”. - -1. Create a new file under `ml/prep` called `covariate_encoding` copy the code below and save. - ```python - import pandas as pd - import numpy as np - from sklearn.preprocessing import StandardScaler,LabelEncoder,OneHotEncoder - from sklearn.linear_model import LogisticRegression - - def model(dbt, session): - # dbt configuration - dbt.config(packages=["pandas","numpy","scikit-learn"]) - - # get upstream data - data = dbt.ref("ml_data_prep").to_pandas() - - # list out covariates we want to use in addition to outcome variable we are modeling - position - covariates = data[['RACE_YEAR','CIRCUIT_NAME','GRID','CONSTRUCTOR_NAME','DRIVER','DRIVERS_AGE_YEARS','DRIVER_CONFIDENCE','CONSTRUCTOR_RELAIBLITY','TOTAL_PIT_STOPS_PER_RACE','ACTIVE_DRIVER','ACTIVE_CONSTRUCTOR', 'POSITION']] - - # filter covariates on active drivers and constructors - # use fil_cov as short for "filtered_covariates" - fil_cov = covariates[(covariates['ACTIVE_DRIVER']==1)&(covariates['ACTIVE_CONSTRUCTOR']==1)] - - # Encode categorical variables using LabelEncoder - # TODO: we'll update this to both ohe in the future for non-ordinal variables! - le = LabelEncoder() - fil_cov['CIRCUIT_NAME'] = le.fit_transform(fil_cov['CIRCUIT_NAME']) - fil_cov['CONSTRUCTOR_NAME'] = le.fit_transform(fil_cov['CONSTRUCTOR_NAME']) - fil_cov['DRIVER'] = le.fit_transform(fil_cov['DRIVER']) - fil_cov['TOTAL_PIT_STOPS_PER_RACE'] = le.fit_transform(fil_cov['TOTAL_PIT_STOPS_PER_RACE']) - - # Simply target variable "position" to represent 3 meaningful categories in Formula1 - # 1. Podium position 2. Points for team 3. Nothing - no podium or points! - def position_index(x): - if x<4: - return 1 - if x>10: - return 3 - else : - return 2 - - # we are dropping the columns that we filtered on in addition to our training variable - encoded_data = fil_cov.drop(['ACTIVE_DRIVER','ACTIVE_CONSTRUCTOR'],1) - encoded_data['POSITION_LABEL']= encoded_data['POSITION'].apply(lambda x: position_index(x)) - encoded_data_grouped_target = encoded_data.drop(['POSITION'],1) - - return encoded_data_grouped_target - ``` -2. Execute the following in the command bar: - ```bash - dbt run --select covariate_encoding - ``` -3. In this code, we are using a ton of functions from libraries! This is really cool, because we can utilize code other people have developed and bring it into our project simply by using the `import` function. [Scikit-learn](https://scikit-learn.org/stable/), “sklearn” for short, is an extremely popular data science library. Sklearn contains a wide range of machine learning techniques, including supervised and unsupervised learning algorithms, feature scaling and imputation, as well as tools model evaluation and selection. We’ll be using Sklearn for both preparing our covariates and creating models (our next section). -4. Our dataset is pretty small data so we are good to use pandas and `sklearn`. If you have larger data for your own project in mind, consider `dask` or `category_encoders`. -5. Breaking it down a bit more: - - We’re selecting a subset of variables that will be used as predictors for a driver’s position. - - Filter the dataset to only include rows using the active driver and constructor flags we created in the last step. - - The next step is to use the `LabelEncoder` from scikit-learn to convert the categorical variables `CIRCUIT_NAME`, `CONSTRUCTOR_NAME`, `DRIVER`, and `TOTAL_PIT_STOPS_PER_RACE` into numerical values. - - Create a new variable called `POSITION_LABEL`, which is a derived from our position variable. - - 💭 Why are we changing our position variable? There are 20 total positions in Formula 1 and we are grouping them together to simplify the classification and improve performance. We also want to demonstrate you can create a new function within your dbt model! - - Our new `position_label` variable has meaning: - - In Formula1 if you are in: - - Top 3 you get a “podium” position - - Top 10 you gain points that add to your overall season total - - Below top 10 you get no points! - - We are mapping our original variable position to `position_label` to the corresponding places above to 1,2, and 3 respectively. - - Drop the active driver and constructor flags since they were filter criteria and additionally drop our original position variable. - -## Splitting into training and testing datasets - -Now that we’ve cleaned and encoded our data, we are going to further split in by time. In this step, we will create dataframes to use for training and prediction. We’ll be creating two dataframes 1) using data from 2010-2019 for training, and 2) data from 2020 for new prediction inferences. We’ll create variables called `start_year` and `end_year` so we aren’t filtering on hardcasted values (and can more easily swap them out in the future if we want to retrain our model on different timeframes). - -1. Create a file called `train_test_dataset` copy and save the following code: - ```python - import pandas as pd - - def model(dbt, session): - - # dbt configuration - dbt.config(packages=["pandas"], tags="train") - - # get upstream data - encoding = dbt.ref("covariate_encoding").to_pandas() - - # provide years so we do not hardcode dates in filter command - start_year=2010 - end_year=2019 - - # describe the data for a full decade - train_test_dataset = encoding.loc[encoding['RACE_YEAR'].between(start_year, end_year)] - - return train_test_dataset - ``` - -2. Create a file called `hold_out_dataset_for_prediction` copy and save the following code below. Now we’ll have a dataset with only the year 2020 that we’ll keep as a hold out set that we are going to use similar to a deployment use case. - ```python - import pandas as pd - - def model(dbt, session): - # dbt configuration - dbt.config(packages=["pandas"], tags="predict") - - # get upstream data - encoding = dbt.ref("covariate_encoding").to_pandas() - - # variable for year instead of hardcoding it - year=2020 - - # filter the data based on the specified year - hold_out_dataset = encoding.loc[encoding['RACE_YEAR'] == year] - - return hold_out_dataset - ``` -3. Execute the following in the command bar: - ```bash - dbt run --select train_test_dataset hold_out_dataset_for_prediction - ``` - To run our temporal data split models, we can use this syntax in the command line to run them both at once. Make sure you use a *space* [syntax](/reference/node-selection/syntax) between the model names to indicate you want to run both! -4. **Commit and push** our changes to keep saving our work as we go using `ml data prep and splits` before moving on. - -👏 Now that we’ve finished our machine learning prep work we can move onto the fun part — training and prediction! diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-testing.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-testing.md deleted file mode 100644 index 8b353a85fa3..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-testing.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -title: "Machine Learning: training and prediction " -id: "12-machine-learning-training-prediction" -description: "Machine Learning: training and prediction" ---- - -We’re ready to start training a model to predict the driver’s position. Now is a good time to pause and take a step back and say, usually in ML projects you’ll try multiple algorithms during development and use an evaluation method such as cross validation to determine which algorithm to use. You can definitely do this in your dbt project, but for the content of this lab we’ll have decided on using a logistic regression to predict position (we actually tried some other algorithms using cross validation outside of this lab such as k-nearest neighbors and a support vector classifier but that didn’t perform as well as the logistic regression and a decision tree that overfit). - -There are 3 areas to break down as we go since we are working at the intersection all within one model file: -1. Machine Learning -2. Snowflake and Snowpark -3. dbt Python models - -If you haven’t seen code like this before or use joblib files to save machine learning models, we’ll be going over them at a high level and you can explore the links for more technical in-depth along the way! Because Snowflake and dbt have abstracted away a lot of the nitty gritty about serialization and storing our model object to be called again, we won’t go into too much detail here. There’s *a lot* going on here so take it at your pace! - -## Training and saving a machine learning model - -1. Project organization remains key, so let’s make a new subfolder called `train_predict` under the `ml` folder. -2. Now create a new file called `train_test_position` and copy and save the following code: - - ```python - import snowflake.snowpark.functions as F - from sklearn.model_selection import train_test_split - import pandas as pd - from sklearn.metrics import confusion_matrix, balanced_accuracy_score - import io - from sklearn.linear_model import LogisticRegression - from joblib import dump, load - import joblib - import logging - import sys - from joblib import dump, load - - logger = logging.getLogger("mylog") - - def save_file(session, model, path, dest_filename): - input_stream = io.BytesIO() - joblib.dump(model, input_stream) - session._conn.upload_stream(input_stream, path, dest_filename) - return "successfully created file: " + path - - def model(dbt, session): - dbt.config( - packages = ['numpy','scikit-learn','pandas','numpy','joblib','cachetools'], - materialized = "table", - tags = "train" - ) - # Create a stage in Snowflake to save our model file - session.sql('create or replace stage MODELSTAGE').collect() - - #session._use_scoped_temp_objects = False - version = "1.0" - logger.info('Model training version: ' + version) - - # read in our training and testing upstream dataset - test_train_df = dbt.ref("train_test_dataset") - - # cast snowpark df to pandas df - test_train_pd_df = test_train_df.to_pandas() - target_col = "POSITION_LABEL" - - # split out covariate predictors, x, from our target column position_label, y. - split_X = test_train_pd_df.drop([target_col], axis=1) - split_y = test_train_pd_df[target_col] - - # Split out our training and test data into proportions - X_train, X_test, y_train, y_test = train_test_split(split_X, split_y, train_size=0.7, random_state=42) - train = [X_train, y_train] - test = [X_test, y_test] - # now we are only training our one model to deploy - # we are keeping the focus on the workflows and not algorithms for this lab! - model = LogisticRegression() - - # fit the preprocessing pipeline and the model together - model.fit(X_train, y_train) - y_pred = model.predict_proba(X_test)[:,1] - predictions = [round(value) for value in y_pred] - balanced_accuracy = balanced_accuracy_score(y_test, predictions) - - # Save the model to a stage - save_file(session, model, "@MODELSTAGE/driver_position_"+version, "driver_position_"+version+".joblib" ) - logger.info('Model artifact:' + "@MODELSTAGE/driver_position_"+version+".joblib") - - # Take our pandas training and testing dataframes and put them back into snowpark dataframes - snowpark_train_df = session.write_pandas(pd.concat(train, axis=1, join='inner'), "train_table", auto_create_table=True, create_temp_table=True) - snowpark_test_df = session.write_pandas(pd.concat(test, axis=1, join='inner'), "test_table", auto_create_table=True, create_temp_table=True) - - # Union our training and testing data together and add a column indicating train vs test rows - return snowpark_train_df.with_column("DATASET_TYPE", F.lit("train")).union(snowpark_test_df.with_column("DATASET_TYPE", F.lit("test"))) - ``` - -3. Execute the following in the command bar: - ```bash - dbt run --select train_test_position - ``` -4. Breaking down our Python script here: - - We’re importing some helpful libraries. - - Defining a function called `save_file()` that takes four parameters: `session`, `model`, `path` and `dest_filename` that will save our logistic regression model file. - - `session` — an object representing a connection to Snowflake. - - `model` — an object that needs to be saved. In this case, it's a Python object that is a scikit-learn that can be serialized with joblib. - - `path` — a string representing the directory or bucket location where the file should be saved. - - `dest_filename` — a string representing the desired name of the file. - - Creating our dbt model - - Within this model we are creating a stage called `MODELSTAGE` to place our logistic regression `joblib` model file. This is really important since we need a place to keep our model to reuse and want to ensure it's there. When using Snowpark commands, it's common to see the `.collect()` method to ensure the action is performed. Think of the session as our “start” and collect as our “end” when [working with Snowpark](https://docs.snowflake.com/en/developer-guide/snowpark/python/working-with-dataframes.html) (you can use other ending methods other than collect). - - Using `.ref()` to connect into our `train_test_dataset` model. - - Now we see the machine learning part of our analysis: - - Create new dataframes for our prediction features from our target variable `position_label`. - - Split our dataset into 70% training (and 30% testing), train_size=0.7 with a `random_state` specified to have repeatable results. - - Specify our model is a logistic regression. - - Fit our model. In a logistic regression this means finding the coefficients that will give the least classification error. - - Round our predictions to the nearest integer since logistic regression creates a probability between for each class and calculate a balanced accuracy to account for imbalances in the target variable. - - Right now our model is only in memory, so we need to use our nifty function `save_file` to save our model file to our Snowflake stage. We save our model as a joblib file so Snowpark can easily call this model object back to create predictions. We really don’t need to know much else as a data practitioner unless we want to. It’s worth noting that joblib files aren’t able to be queried directly by SQL. To do this, we would need to transform the joblib file to an SQL querable format such as JSON or CSV (out of scope for this workshop). - - Finally we want to return our dataframe, but create a new column indicating what rows were used for training and those for training. -5. Viewing our output of this model: - - -6. Let’s pop back over to Snowflake and check that our logistic regression model has been stored in our `MODELSTAGE` using the command: - ```sql - list @modelstage - ``` - - -7. To investigate the commands run as part of `train_test_position` script, navigate to Snowflake query history to view it **Activity > Query History**. We can view the portions of query that we wrote such as `create or replace stage MODELSTAGE`, but we also see additional queries that Snowflake uses to interpret python code. - - -## Predicting on new data - -1. Create a new file called `predict_position` and copy and save the following code: - ```python - import logging - import joblib - import pandas as pd - import os - from snowflake.snowpark import types as T - - DB_STAGE = 'MODELSTAGE' - version = '1.0' - # The name of the model file - model_file_path = 'driver_position_'+version - model_file_packaged = 'driver_position_'+version+'.joblib' - - # This is a local directory, used for storing the various artifacts locally - LOCAL_TEMP_DIR = f'/tmp/driver_position' - DOWNLOAD_DIR = os.path.join(LOCAL_TEMP_DIR, 'download') - TARGET_MODEL_DIR_PATH = os.path.join(LOCAL_TEMP_DIR, 'ml_model') - TARGET_LIB_PATH = os.path.join(LOCAL_TEMP_DIR, 'lib') - - # The feature columns that were used during model training - # and that will be used during prediction - FEATURE_COLS = [ - "RACE_YEAR" - ,"CIRCUIT_NAME" - ,"GRID" - ,"CONSTRUCTOR_NAME" - ,"DRIVER" - ,"DRIVERS_AGE_YEARS" - ,"DRIVER_CONFIDENCE" - ,"CONSTRUCTOR_RELAIBLITY" - ,"TOTAL_PIT_STOPS_PER_RACE"] - - def register_udf_for_prediction(p_predictor ,p_session ,p_dbt): - - # The prediction udf - - def predict_position(p_df: T.PandasDataFrame[int, int, int, int, - int, int, int, int, int]) -> T.PandasSeries[int]: - # Snowpark currently does not set the column name in the input dataframe - # The default col names are like 0,1,2,... Hence we need to reset the column - # names to the features that we initially used for training. - p_df.columns = [*FEATURE_COLS] - - # Perform prediction. this returns an array object - pred_array = p_predictor.predict(p_df) - # Convert to series - df_predicted = pd.Series(pred_array) - return df_predicted - - # The list of packages that will be used by UDF - udf_packages = p_dbt.config.get('packages') - - predict_position_udf = p_session.udf.register( - predict_position - ,name=f'predict_position' - ,packages = udf_packages - ) - return predict_position_udf - - def download_models_and_libs_from_stage(p_session): - p_session.file.get(f'@{DB_STAGE}/{model_file_path}/{model_file_packaged}', DOWNLOAD_DIR) - - def load_model(p_session): - # Load the model and initialize the predictor - model_fl_path = os.path.join(DOWNLOAD_DIR, model_file_packaged) - predictor = joblib.load(model_fl_path) - return predictor - - # ------------------------------- - def model(dbt, session): - dbt.config( - packages = ['snowflake-snowpark-python' ,'scipy','scikit-learn' ,'pandas' ,'numpy'], - materialized = "table", - tags = "predict" - ) - session._use_scoped_temp_objects = False - download_models_and_libs_from_stage(session) - predictor = load_model(session) - predict_position_udf = register_udf_for_prediction(predictor, session ,dbt) - - # Retrieve the data, and perform the prediction - hold_out_df = (dbt.ref("hold_out_dataset_for_prediction") - .select(*FEATURE_COLS) - ) - - # Perform prediction. - new_predictions_df = hold_out_df.withColumn("position_predicted" - ,predict_position_udf(*FEATURE_COLS) - ) - - return new_predictions_df - ``` -2. Execute the following in the command bar: - ```bash - dbt run --select predict_position - ``` -3. **Commit and push** our changes to keep saving our work as we go using the commit message `logistic regression model training and application` before moving on. -4. At a high level in this script, we are: - - Retrieving our staged logistic regression model - - Loading the model in - - Placing the model within a user defined function (UDF) to call in line predictions on our driver’s position -5. At a more detailed level: - - Import our libraries. - - Create variables to reference back to the `MODELSTAGE` we just created and stored our model to. - - The temporary file paths we created might look intimidating, but all we’re doing here is programmatically using an initial file path and adding to it to create the following directories: - - LOCAL_TEMP_DIR ➡️ /tmp/driver_position - - DOWNLOAD_DIR ➡️ /tmp/driver_position/download - - TARGET_MODEL_DIR_PATH ➡️ /tmp/driver_position/ml_model - - TARGET_LIB_PATH ➡️ /tmp/driver_position/lib - - Provide a list of our feature columns that we used for model training and will now be used on new data for prediction. - - Next, we are creating our main function `register_udf_for_prediction(p_predictor ,p_session ,p_dbt):`. This function is used to register a user-defined function (UDF) that performs the machine learning prediction. It takes three parameters: `p_predictor` is an instance of the machine learning model, `p_session` is an instance of the Snowflake session, and `p_dbt` is an instance of the dbt library. The function creates a UDF named `predict_churn` which takes a pandas dataframe with the input features and returns a pandas series with the predictions. - - ⚠️ Pay close attention to the whitespace here. We are using a function within a function for this script. - - We have 2 simple functions that are programmatically retrieving our file paths to first get our stored model out of our `MODELSTAGE` and downloaded into the session `download_models_and_libs_from_stage` and then to load the contents of our model in (parameters) in `load_model` to use for prediction. - - Take the model we loaded in and call it `predictor` and wrap it in a UDF. - - Return our dataframe with both the features used to predict and the new label. - -🧠 Another way to read this script is from the bottom up. This can help us progressively see what is going into our final dbt model and work backwards to see how the other functions are being referenced. - -6. Let’s take a look at our predicted position alongside our feature variables. Open a new scratchpad and use the following query. I chose to order by the prediction of who would obtain a podium position: - ```sql - select * from {{ ref('predict_position') }} order by position_predicted - ``` -7. We can see that we created predictions in our final dataset, we are ready to move on to testing! diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/13-testing.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/13-testing.md deleted file mode 100644 index bcda9a775fb..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/13-testing.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: "Testing" -id: "13-testing" -description: "Testing" ---- -We have now completed building all the models for today’s lab, but how do we know if they meet our assertions? Put another way, how do we know the quality of our data models are any good? This brings us to testing! - -We test data models for mainly two reasons: - -- Ensure that our source data is clean on ingestion before we start data modeling/transformation (aka avoid garbage in, garbage out problem). -- Make sure we don’t introduce bugs in the transformation code we wrote (stop ourselves from creating bad joins/fanouts). - -Testing in dbt comes in two flavors: [generic](/docs/build/tests#generic-tests) and [singular](/docs/build/tests#singular-tests). - -You define them in a test block (similar to a macro) and once defined, you can reference them by name in your `.yml` files (applying them to models, columns, sources, snapshots, and seeds). - -You might be wondering: *what about testing Python models?* - -Since the output of our Python models are tables, we can test SQL and Python models the same way! We don’t have to worry about any syntax differences when testing SQL versus Python data models. This means we use `.yml` and `.sql` files to test our entities (tables, views, etc.). Under the hood, dbt is running an SQL query on our tables to see if they meet assertions. If no rows are returned, dbt will surface a passed test. Conversely, if a test results in returned rows, it will fail or warn depending on the configuration (more on that later). - -## Generic tests - -1. To implement generic out-of-the-box tests dbt comes with, we can use YAML files to specify information about our models. To add generic tests to our aggregates model, create a file called `aggregates.yml`, copy the code block below into the file, and save. - - - ```yaml - version: 2 - - models: - - name: fastest_pit_stops_by_constructor - description: Use the python .describe() method to retrieve summary statistics table about pit stops by constructor. Sort by average stop time ascending so the first row returns the fastest constructor. - columns: - - name: constructor_name - description: team that makes the car - tests: - - unique - - - name: lap_times_moving_avg - description: Use the python .rolling() method to calculate the 5 year rolling average of pit stop times alongside the average for each year. - columns: - - name: race_year - description: year of the race - tests: - - relationships: - to: ref('int_lap_times_years') - field: race_year - ``` - -2. Let’s unpack the code we have here. We have both our aggregates models with the model name to know the object we are referencing and the description of the model that we’ll populate in our documentation. At the column level (a level below our model), we are providing the column name followed by our tests. We want to ensure our `constructor_name` is unique since we used a pandas `groupby` on `constructor_name` in the model `fastest_pit_stops_by_constructor`. Next, we want to ensure our `race_year` has referential integrity from the model we selected from `int_lap_times_years` into our subsequent `lap_times_moving_avg` model. -3. Finally, if we want to see how tests were deployed on sources and SQL models, we can look at other files in our project such as the `f1_sources.yml` we created in our Sources and staging section. - -## Using macros for testing - -1. Under your `macros` folder, create a new file and name it `test_all_values_gte_zero.sql`. Copy the code block below and save the file. For clarity, “gte” is an abbreviation for greater than or equal to. - - - ```sql - {% macro test_all_values_gte_zero(table, column) %} - - select * from {{ ref(table) }} where {{ column }} < 0 - - {% endmacro %} - ``` - -2. Macros in Jinja are pieces of code that can be reused multiple times in our SQL models — they are analogous to "functions" in other programming languages, and are extremely useful if you find yourself repeating code across multiple models. -3. We use the `{% macro %}` to indicate the start of the macro and `{% endmacro %}` for the end. The text after the beginning of the macro block is the name we are giving the macro to later call it. In this case, our macro is called `test_all_values_gte_zero`. Macros take in *arguments* to pass through, in this case the `table` and the `column`. In the body of the macro, we see an SQL statement that is using the `ref` function to dynamically select the table and then the column. You can always view macros without having to run them by using `dbt run-operation`. You can learn more [here](https://docs.getdbt.com/reference/commands/run-operation). -4. Great, now we want to reference this macro as a test! Let’s create a new test file called `macro_pit_stops_mean_is_positive.sql` in our `tests` folder. - - - -5. Copy the following code into the file and save: - - ```sql - {{ - config( - enabled=true, - severity='warn', - tags = ['bi'] - ) - }} - - {{ test_all_values_gte_zero('fastest_pit_stops_by_constructor', 'mean') }} - ``` - -6. In our testing file, we are applying some configurations to the test including `enabled`, which is an optional configuration for disabling models, seeds, snapshots, and tests. Our severity is set to `warn` instead of `error`, which means our pipeline will still continue to run. We have tagged our test with `bi` since we are applying this test to one of our bi models. - -Then, in our final line, we are calling the `test_all_values_gte_zero` macro that takes in our table and column arguments and inputting our table `'fastest_pit_stops_by_constructor'` and the column `'mean'`. - -## Custom singular tests to validate Python models - -The simplest way to define a test is by writing the exact SQL that will return failing records. We call these "singular" tests, because they're one-off assertions usable for a single purpose. - -These tests are defined in `.sql` files, typically in your `tests` directory (as defined by your test-paths config). You can use Jinja in SQL models (including ref and source) in the test definition, just like you can when creating models. Each `.sql` file contains one select statement, and it defines one test. - -Let’s add a custom test that asserts that the moving average of the lap time over the last 5 years is greater than zero (it’s impossible to have time less than 0!). It is easy to assume if this is not the case the data has been corrupted. - -1. Create a file `lap_times_moving_avg_assert_positive_or_null.sql` under the `tests` folder. - - -2. Copy the following code and save the file: - - ```sql - {{ - config( - enabled=true, - severity='error', - tags = ['bi'] - ) - }} - - with lap_times_moving_avg as ( select * from {{ ref('lap_times_moving_avg') }} ) - - select * - from lap_times_moving_avg - where lap_moving_avg_5_years < 0 and lap_moving_avg_5_years is not null - ``` - -## Putting all our tests together - -1. Time to run our tests! Altogether, we have created 4 tests for our 2 Python models: - - `fastest_pit_stops_by_constructor` - - Unique `constructor_name` - - Lap times are greater than 0 or null (to allow for the first leading values in a rolling calculation) - - `lap_times_moving_avg` - - Referential test on `race_year` - - Mean pit stop times are greater than or equal to 0 (no negative time values) -2. To run the tests on both our models, we can use this syntax in the command line to run them both at once, similar to how we did our data splits earlier. - Execute the following in the command bar: - ```bash - dbt test --select fastest_pit_stops_by_constructor lap_times_moving_avg - ``` - - -3. All 4 of our tests passed (yay for clean data)! To understand the SQL being run against each of our tables, we can click into the details of the test. -4. Navigating into the **Details** of the `unique_fastest_pit_stops_by_constructor_name`, we can see that each line `constructor_name` should only have one row. - \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation.md deleted file mode 100644 index 95ec8ad242f..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "Documentation" -id: "14-documentation" -description: "Documentation" ---- -When it comes to documentation, dbt brings together both column and model level descriptions that you can provide as well as details from your Snowflake information schema in a static site for consumption by other data team members and stakeholders. - -We are going to revisit 2 areas of our project to understand our documentation: - -- `intermediate.md` file -- `dbt_project.yml` file - -To start, let’s look back at our `intermediate.md` file. We can see that we provided multi-line descriptions for the models in our intermediate models using [docs blocks](/docs/collaborate/documentation#using-docs-blocks). Then we reference these docs blocks in our `.yml` file. Building descriptions with doc blocks in Markdown files gives you the ability to format your descriptions with Markdown and are particularly helpful when building long descriptions, either at the column or model level. In our `dbt_project.yml`, we added `node_colors` at folder levels. - -1. To see all these pieces come together, execute this in the command bar: - ```bash - dbt docs generate - ``` - This will generate the documentation for your project. Click the book button, as shown in the screenshot below to access the docs. - - -2. Go to our project area and view `int_results`. View the description that we created in our doc block. - - -3. View the mini-lineage that looks at the model we are currently selected on (`int_results` in this case). - - -4. In our `dbt_project.yml`, we configured `node_colors` depending on the file directory. Starting in dbt v1.3, we can see how our lineage in our docs looks. By color coding your project, it can help you cluster together similar models or steps and more easily troubleshoot. - \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/15-deployment.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/15-deployment.md deleted file mode 100644 index d9cedb60861..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/15-deployment.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Deployment" -id: "15-deployment" -description: "Deployment" ---- - -Before we jump into deploying our code, let's have a quick primer on environments. Up to this point, all of the work we've done in the dbt Cloud IDE has been in our development environment, with code committed to a feature branch and the models we've built created in our development schema in Snowflake as defined in our Development environment connection. Doing this work on a feature branch, allows us to separate our code from what other coworkers are building and code that is already deemed production ready. Building models in a development schema in Snowflake allows us to separate the database objects we might still be modifying and testing from the database objects running production dashboards or other downstream dependencies. Together, the combination of a Git branch and Snowflake database objects form our environment. - -Now that we've completed testing and documenting our work, we're ready to deploy our code from our development environment to our production environment and this involves two steps: - -- Promoting code from our feature branch to the production branch in our repository. - - Generally, the production branch is going to be named your main branch and there's a review process to go through before merging code to the main branch of a repository. Here we are going to merge without review for ease of this workshop. -- Deploying code to our production environment. - - Once our code is merged to the main branch, we'll need to run dbt in our production environment to build all of our models and run all of our tests. This will allow us to build production-ready objects into our production environment in Snowflake. Luckily for us, the Partner Connect flow has already created our deployment environment and job to facilitate this step. - -1. Before getting started, let's make sure that we've committed all of our work to our feature branch. If you still have work to commit, you'll be able to select the **Commit and push**, provide a message, and then select **Commit** again. -2. Once all of your work is committed, the git workflow button will now appear as **Merge to main**. Select **Merge to main** and the merge process will automatically run in the background. - - -3. When it's completed, you should see the git button read **Create branch** and the branch you're currently looking at will become **main**. -4. Now that all of our development work has been merged to the main branch, we can build our deployment job. Given that our production environment and production job were created automatically for us through Partner Connect, all we need to do here is update some default configurations to meet our needs. -5. In the menu, select **Deploy** **> Environments** - - -6. You should see two environments listed and you'll want to select the **Deployment** environment then **Settings** to modify it. -7. Before making any changes, let's touch on what is defined within this environment. The Snowflake connection shows the credentials that dbt Cloud is using for this environment and in our case they are the same as what was created for us through Partner Connect. Our deployment job will build in our `PC_DBT_DB` database and use the default Partner Connect role and warehouse to do so. The deployment credentials section also uses the info that was created in our Partner Connect job to create the credential connection. However, it is using the same default schema that we've been using as the schema for our development environment. -8. Let's update the schema to create a new schema specifically for our production environment. Click **Edit** to allow you to modify the existing field values. Navigate to **Deployment Credentials >** **schema.** -9. Update the schema name to **production**. Remember to select **Save** after you've made the change. - -10. By updating the schema for our production environment to **production**, it ensures that our deployment job for this environment will build our dbt models in the **production** schema within the `PC_DBT_DB` database as defined in the Snowflake Connection section. -11. Now let's switch over to our production job. Click on the deploy tab again and then select **Jobs**. You should see an existing and preconfigured **Partner Connect Trial Job**. Similar to the environment, click on the job, then select **Settings** to modify it. Let's take a look at the job to understand it before making changes. - - - The Environment section is what connects this job with the environment we want it to run in. This job is already defaulted to use the Deployment environment that we just updated and the rest of the settings we can keep as is. - - The Execution settings section gives us the option to generate docs, run source freshness, and defer to a previous run state. For the purposes of our lab, we're going to keep these settings as is as well and stick with just generating docs. - - The Commands section is where we specify exactly which commands we want to run during this job, and we also want to keep this as is. We want our seed to be uploaded first, then run our models, and finally test them. The order of this is important as well, considering that we need our seed to be created before we can run our incremental model, and we need our models to be created before we can test them. - - Finally, we have the Triggers section, where we have a number of different options for scheduling our job. Given that our data isn't updating regularly here and we're running this job manually for now, we're also going to leave this section alone. - - So, what are we changing then? Just the name! Click **Edit** to allow you to make changes. Then update the name of the job to **Production Job** to denote this as our production deployment job. After that's done, click **Save**. -12. Now let's go to run our job. Clicking on the job name in the path at the top of the screen will take you back to the job run history page where you'll be able to click **Run run** to kick off the job. If you encounter any job failures, try running the job again before further troubleshooting. - - - -13. Let's go over to Snowflake to confirm that everything built as expected in our production schema. Refresh the database objects in your Snowflake account and you should see the production schema now within our default Partner Connect database. If you click into the schema and everything ran successfully, you should be able to see all of the models we developed. - - -## Conclusion - -Fantastic! You’ve finished the workshop! We hope you feel empowered in using both SQL and Python in your dbt Cloud workflows with Snowflake. Having a reliable pipeline to surface both analytics and machine learning is crucial to creating tangible business value from your data. - -For more help and information join our [dbt community Slack](https://www.getdbt.com/community/) which contains more than 50,000 data practitioners today. We have a dedicated slack channel #db-snowflake to Snowflake related content. Happy dbt'ing! \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration.md deleted file mode 100644 index e864c363a44..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Configure Snowflake" -id: "2-snowflake-configuration" -description: "Configure Snowflake" ---- - - -1. Log in to your trial Snowflake account. You can [sign up for a Snowflake Trial Account using this form](https://signup.snowflake.com/) if you don’t have one. -2. Ensure that your account is set up using **AWS** in the **US East (N. Virginia)**. We will be copying the data from a public AWS S3 bucket hosted by dbt Labs in the us-east-1 region. By ensuring our Snowflake environment setup matches our bucket region, we avoid any multi-region data copy and retrieval latency issues. - - - -3. After creating your account and verifying it from your sign-up email, Snowflake will direct you back to the UI called Snowsight. - -4. When Snowsight first opens, your window should look like the following, with you logged in as the ACCOUNTADMIN with demo worksheets open: - - - - -5. Navigate to **Admin > Billing & Terms**. Click **Enable > Acknowledge & Continue** to enable Anaconda Python Packages to run in Snowflake. - - - - - -6. Finally, create a new Worksheet by selecting **+ Worksheet** in the upper right corner. - diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source.md deleted file mode 100644 index 9a41e7f45c5..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: "Connect to data source" -id: "3-connect-to-data-source" -description: "Connect to data source" ---- - -We need to obtain our data source by copying our Formula 1 data into Snowflake tables from a public S3 bucket that dbt Labs hosts. - -1. When a new Snowflake account is created, there should be a preconfigured warehouse in your account named `COMPUTE_WH`. -2. If for any reason your account doesn’t have this warehouse, we can create a warehouse using the following script: - - ```sql - create or replace warehouse COMPUTE_WH with warehouse_size=XSMALL - ``` -3. Rename the worksheet to `data setup script` since we will be placing code in this worksheet to ingest the Formula 1 data. Make sure you are still logged in as the **ACCOUNTADMIN** and select the **COMPUTE_WH** warehouse. - - - -4. Copy the following code into the main body of the Snowflake worksheet. You can also find this setup script under the `setup` folder in the [Git repository](https://github.com/dbt-labs/python-snowpark-formula1/blob/main/setup/setup_script_s3_to_snowflake.sql). The script is long since it's bring in all of the data we'll need today! - - ```sql - -- create and define our formula1 database - create or replace database formula1; - use database formula1; - create or replace schema raw; - use schema raw; - - -- define our file format for reading in the csvs - create or replace file format csvformat - type = csv - field_delimiter =',' - field_optionally_enclosed_by = '"', - skip_header=1; - - -- - create or replace stage formula1_stage - file_format = csvformat - url = 's3://formula1-dbt-cloud-python-demo/formula1-kaggle-data/'; - - -- load in the 8 tables we need for our demo - -- we are first creating the table then copying our data in from s3 - -- think of this as an empty container or shell that we are then filling - create or replace table formula1.raw.circuits ( - CIRCUITID NUMBER(38,0), - CIRCUITREF VARCHAR(16777216), - NAME VARCHAR(16777216), - LOCATION VARCHAR(16777216), - COUNTRY VARCHAR(16777216), - LAT FLOAT, - LNG FLOAT, - ALT NUMBER(38,0), - URL VARCHAR(16777216) - ); - -- copy our data from public s3 bucket into our tables - copy into circuits - from @formula1_stage/circuits.csv - on_error='continue'; - - create or replace table formula1.raw.constructors ( - CONSTRUCTORID NUMBER(38,0), - CONSTRUCTORREF VARCHAR(16777216), - NAME VARCHAR(16777216), - NATIONALITY VARCHAR(16777216), - URL VARCHAR(16777216) - ); - copy into constructors - from @formula1_stage/constructors.csv - on_error='continue'; - - create or replace table formula1.raw.drivers ( - DRIVERID NUMBER(38,0), - DRIVERREF VARCHAR(16777216), - NUMBER VARCHAR(16777216), - CODE VARCHAR(16777216), - FORENAME VARCHAR(16777216), - SURNAME VARCHAR(16777216), - DOB DATE, - NATIONALITY VARCHAR(16777216), - URL VARCHAR(16777216) - ); - copy into drivers - from @formula1_stage/drivers.csv - on_error='continue'; - - create or replace table formula1.raw.lap_times ( - RACEID NUMBER(38,0), - DRIVERID NUMBER(38,0), - LAP NUMBER(38,0), - POSITION FLOAT, - TIME VARCHAR(16777216), - MILLISECONDS NUMBER(38,0) - ); - copy into lap_times - from @formula1_stage/lap_times.csv - on_error='continue'; - - create or replace table formula1.raw.pit_stops ( - RACEID NUMBER(38,0), - DRIVERID NUMBER(38,0), - STOP NUMBER(38,0), - LAP NUMBER(38,0), - TIME VARCHAR(16777216), - DURATION VARCHAR(16777216), - MILLISECONDS NUMBER(38,0) - ); - copy into pit_stops - from @formula1_stage/pit_stops.csv - on_error='continue'; - - create or replace table formula1.raw.races ( - RACEID NUMBER(38,0), - YEAR NUMBER(38,0), - ROUND NUMBER(38,0), - CIRCUITID NUMBER(38,0), - NAME VARCHAR(16777216), - DATE DATE, - TIME VARCHAR(16777216), - URL VARCHAR(16777216), - FP1_DATE VARCHAR(16777216), - FP1_TIME VARCHAR(16777216), - FP2_DATE VARCHAR(16777216), - FP2_TIME VARCHAR(16777216), - FP3_DATE VARCHAR(16777216), - FP3_TIME VARCHAR(16777216), - QUALI_DATE VARCHAR(16777216), - QUALI_TIME VARCHAR(16777216), - SPRINT_DATE VARCHAR(16777216), - SPRINT_TIME VARCHAR(16777216) - ); - copy into races - from @formula1_stage/races.csv - on_error='continue'; - - create or replace table formula1.raw.results ( - RESULTID NUMBER(38,0), - RACEID NUMBER(38,0), - DRIVERID NUMBER(38,0), - CONSTRUCTORID NUMBER(38,0), - NUMBER NUMBER(38,0), - GRID NUMBER(38,0), - POSITION FLOAT, - POSITIONTEXT VARCHAR(16777216), - POSITIONORDER NUMBER(38,0), - POINTS NUMBER(38,0), - LAPS NUMBER(38,0), - TIME VARCHAR(16777216), - MILLISECONDS NUMBER(38,0), - FASTESTLAP NUMBER(38,0), - RANK NUMBER(38,0), - FASTESTLAPTIME VARCHAR(16777216), - FASTESTLAPSPEED FLOAT, - STATUSID NUMBER(38,0) - ); - copy into results - from @formula1_stage/results.csv - on_error='continue'; - - create or replace table formula1.raw.status ( - STATUSID NUMBER(38,0), - STATUS VARCHAR(16777216) - ); - copy into status - from @formula1_stage/status.csv - on_error='continue'; - - ``` -5. Ensure all the commands are selected before running the query — an easy way to do this is to use Ctrl-a to highlight all of the code in the worksheet. Select **run** (blue triangle icon). Notice how the dot next to your **COMPUTE_WH** turns from gray to green as you run the query. The **status** table is the final table of all 8 tables loaded in. - - - -6. Let’s unpack that pretty long query we ran into component parts. We ran this query to load in our 8 Formula 1 tables from a public S3 bucket. To do this, we: - - Created a new database called `formula1` and a schema called `raw` to place our raw (untransformed) data into. - - Defined our file format for our CSV files. Importantly, here we use a parameter called `field_optionally_enclosed_by =` since the string columns in our Formula 1 csv files use quotes. Quotes are used around string values to avoid parsing issues where commas `,` and new lines `/n` in data values could cause data loading errors. - - Created a stage to locate our data we are going to load in. Snowflake Stages are locations where data files are stored. Stages are used to both load and unload data to and from Snowflake locations. Here we are using an external stage, by referencing an S3 bucket. - - Created our tables for our data to be copied into. These are empty tables with the column name and data type. Think of this as creating an empty container that the data will then fill into. - - Used the `copy into` statement for each of our tables. We reference our staged location we created and upon loading errors continue to load in the rest of the data. You should not have data loading errors but if you do, those rows will be skipped and Snowflake will tell you which rows caused errors - -7. Now let's take a look at some of our cool Formula 1 data we just loaded up! - 1. Create a new worksheet by selecting the **+** then **New Worksheet**. - - 2. Navigate to **Database > Formula1 > RAW > Tables**. - 3. Query the data using the following code. There are only 76 rows in the circuits table, so we don’t need to worry about limiting the amount of data we query. - ```sql - select * from formula1.raw.circuits - ``` - 4. Run the query. From here on out, we’ll use the keyboard shortcuts Command-Enter or Control-Enter to run queries and won’t explicitly call out this step. - 5. Review the query results, you should see information about Formula 1 circuits, starting with Albert Park in Australia! - 6. Finally, ensure you have all 8 tables starting with `CIRCUITS` and ending with `STATUS`. Now we are ready to connect into dbt Cloud! - - - - \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt.md deleted file mode 100644 index 21eaa7e8d7f..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Configure dbt" -id: "4-configure-dbt" -description: "Configure dbt" ---- - -1. We are going to be using [Snowflake Partner Connect](https://docs.snowflake.com/en/user-guide/ecosystem-partner-connect.html) to set up a dbt Cloud account. Using this method will allow you to spin up a fully fledged dbt account with your [Snowflake connection](/docs/cloud/connect-data-platform/connect-snowflake), [managed repository](/docs/collaborate/git/managed-repository), environments, and credentials already established. -2. Navigate out of your worksheet back by selecting **home**. -3. In Snowsight, confirm that you are using the **ACCOUNTADMIN** role. -4. Navigate to the **Admin** **> Partner Connect**. Find **dbt** either by using the search bar or navigating the **Data Integration**. Select the **dbt** tile. - -5. You should now see a new window that says **Connect to dbt**. Select **Optional Grant** and add the `FORMULA1` database. This will grant access for your new dbt user role to the FORMULA1 database. - - -6. Ensure the `FORMULA1` is present in your optional grant before clicking **Connect**.  This will create a dedicated dbt user, database, warehouse, and role for your dbt Cloud trial. - - - -7. When you see the **Your partner account has been created** window, click **Activate**. - -8. You should be redirected to a dbt Cloud registration page. Fill out the form. Make sure to save the password somewhere for login in the future. - - - -9. Select **Complete Registration**. You should now be redirected to your dbt Cloud account, complete with a connection to your Snowflake account, a deployment and a development environment, and a sample job. - -10. To help you version control your dbt project, we have connected it to a [managed repository](/docs/collaborate/git/managed-repository), which means that dbt Labs will be hosting your repository for you. This will give you access to a Git workflow without you having to create and host the repository yourself. You will not need to know Git for this workshop; dbt Cloud will help guide you through the workflow. In the future, when you’re developing your own project, [feel free to use your own repository](/docs/cloud/git/connect-github). This will allow you to learn more about features like [Slim CI](/docs/deploy/continuous-integration) builds after this workshop. diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name.md deleted file mode 100644 index f098c47bdad..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "Development schema name and IDE walkthrough" -id: "5-development-schema-name" -description: "Development schema name and IDE walkthrough" ---- - -1. First we are going to change the name of our default schema to where our dbt models will build. By default, the name is `dbt_`. We will change this to `dbt_` to create your own personal development schema. To do this, select **Profile Settings** from the gear icon in the upper right. - - - -2. Navigate to the **Credentials** menu and select **Partner Connect Trial**, which will expand the credentials menu. - - - -3. Click **Edit** and change the name of your schema from `dbt_` to `dbt_YOUR_NAME` replacing `YOUR_NAME` with your initials and name (`hwatson` is used in the lab screenshots). Be sure to click **Save** for your changes! - - -4. We now have our own personal development schema, amazing! When we run our first dbt models they will build into this schema. -5. Let’s open up dbt Cloud’s Integrated Development Environment (IDE) and familiarize ourselves. Choose **Develop** at the top of the UI. - -6. When the IDE is done loading, click **Initialize dbt project**. The initialization process creates a collection of files and folders necessary to run your dbt project. - - -7. After the initialization is finished, you can view the files and folders in the file tree menu. As we move through the workshop we'll be sure to touch on a few key files and folders that we'll work with to build out our project. -8. Next click **Commit and push** to commit the new files and folders from the initialize step. We always want our commit messages to be relevant to the work we're committing, so be sure to provide a message like `initialize project` and select **Commit Changes**. - - - - - -9. [Committing](https://www.atlassian.com/git/tutorials/saving-changes/git-commit) your work here will save it to the managed git repository that was created during the Partner Connect signup. This initial commit is the only commit that will be made directly to our `main` branch and from *here on out we'll be doing all of our work on a development branch*. This allows us to keep our development work separate from our production code. -10. There are a couple of key features to point out about the IDE before we get to work. It is a text editor, an SQL and Python runner, and a CLI with Git version control all baked into one package! This allows you to focus on editing your SQL and Python files, previewing the results with the SQL runner (it even runs Jinja!), and building models at the command line without having to move between different applications. The Git workflow in dbt Cloud allows both Git beginners and experts alike to be able to easily version control all of their work with a couple clicks. - - - -11. Let's run our first dbt models! Two example models are included in your dbt project in the `models/examples` folder that we can use to illustrate how to run dbt at the command line. Type `dbt run` into the command line and click **Enter** on your keyboard. When the run bar expands you'll be able to see the results of the run, where you should see the run complete successfully. - - - -12. The run results allow you to see the code that dbt compiles and sends to Snowflake for execution. To view the logs for this run, select one of the model tabs using the  **>** icon and then **Details**. If you scroll down a bit you'll be able to see the compiled code and how dbt interacts with Snowflake. Given that this run took place in our development environment, the models were created in your development schema. - - - - -13. Now let's switch over to Snowflake to confirm that the objects were actually created. Click on the three dots **…** above your database objects and then **Refresh**. Expand the **PC_DBT_DB** database and you should see your development schema. Select the schema, then **Tables**  and **Views**. Now you should be able to see `MY_FIRST_DBT_MODEL` as a table and `MY_SECOND_DBT_MODEL` as a view. - \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure.md deleted file mode 100644 index 8a938e10c34..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "Foundational structure" -id: "6-foundational-structure" -description: "Foundational structure" ---- - -In this step, we’ll need to create a development branch and set up project level configurations. - -1. To get started with development for our project, we'll need to create a new Git branch for our work. Select **create branch** and name your development branch. We'll call our branch `snowpark_python_workshop` then click **Submit**. -2. The first piece of development we'll do on the project is to update the `dbt_project.yml` file. Every dbt project requires a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. The [dbt_project.yml](/reference/dbt_project.yml) file also contains important information that tells dbt how to operate on your project. -3. Select the `dbt_project.yml` file from the file tree to open it and replace all of the existing contents with the following code below. When you're done, save the file by clicking **save**. You can also use the Command-S or Control-S shortcut from here on out. - - ```yaml - # Name your project! Project names should contain only lowercase characters - # and underscores. A good package name should reflect your organization's - # name or the intended use of these models - name: 'snowflake_dbt_python_formula1' - version: '1.3.0' - require-dbt-version: '>=1.3.0' - config-version: 2 - - # This setting configures which "profile" dbt uses for this project. - profile: 'default' - - # These configurations specify where dbt should look for different types of files. - # The `model-paths` config, for example, states that models in this project can be - # found in the "models/" directory. You probably won't need to change these! - model-paths: ["models"] - analysis-paths: ["analyses"] - test-paths: ["tests"] - seed-paths: ["seeds"] - macro-paths: ["macros"] - snapshot-paths: ["snapshots"] - - target-path: "target" # directory which will store compiled SQL files - clean-targets: # directories to be removed by `dbt clean` - - "target" - - "dbt_packages" - - models: - snowflake_dbt_python_formula1: - staging: - - +docs: - node_color: "CadetBlue" - marts: - +materialized: table - aggregates: - +docs: - node_color: "Maroon" - +tags: "bi" - - core: - +docs: - node_color: "#800080" - intermediate: - +docs: - node_color: "MediumSlateBlue" - ml: - prep: - +docs: - node_color: "Indigo" - train_predict: - +docs: - node_color: "#36454f" - - ``` - -4. The key configurations to point out in the file with relation to the work that we're going to do are in the `models` section. - - `require-dbt-version` — Tells dbt which version of dbt to use for your project. We are requiring 1.3.0 and any newer version to run python models and node colors. - - `materialized` — Tells dbt how to materialize models when compiling the code before it pushes it down to Snowflake. All models in the `marts` folder will be built as tables. - - `tags` — Applies tags at a directory level to all models. All models in the `aggregates` folder will be tagged as `bi` (abbreviation for business intelligence). - - `docs` — Specifies the `node_color` either by the plain color name or a hex value. -5. [Materializations](/docs/build/materializations) are strategies for persisting dbt models in a warehouse, with `tables` and `views` being the most commonly utilized types. By default, all dbt models are materialized as views and other materialization types can be configured in the `dbt_project.yml` file or in a model itself. It’s very important to note *Python models can only be materialized as tables or incremental models.* Since all our Python models exist under `marts`, the following portion of our `dbt_project.yml` ensures no errors will occur when we run our Python models. Starting with [dbt version 1.4](/docs/dbt-versions/core-upgrade/upgrading-to-v1.4#updates-to-python-models), Python files will automatically get materialized as tables even if not explicitly specified. - - ```yaml - marts:     - +materialized: table - ``` - diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md deleted file mode 100644 index c4e3e94b03a..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Folder structure" -id: "7-folder-structure" -description: "Folder structure" ---- -dbt Labs has developed a [project structure guide](/best-practices/how-we-structure/1-guide-overview/) that contains a number of recommendations for how to build the folder structure for your project. Do check out that guide if you want to learn more. Right now we are going to create some folders to organize our files: - -- Sources — This is our Formula 1 dataset and it will be defined in a source YAML file. -- Staging models — These models have a 1:1 with their source table. -- Intermediate — This is where we will be joining some Formula staging models. -- Marts models — Here is where we perform our major transformations. It contains these subfolders: - - aggregates - - core - - ml -1. In your file tree, use your cursor and hover over the `models` subdirectory, click the three dots **…** that appear to the right of the folder name, then select **Create Folder**. We're going to add two new folders to the file path, `staging` and `formula1` (in that order) by typing `staging/formula1` into the file path. - - - - - - If you click into your `models` directory now, you should see the new `staging` folder nested within `models` and the `formula1` folder nested within `staging`. -2. Create two additional folders the same as the last step. Within the `models` subdirectory, create new directories `marts/core`. - -3. We will need to create a few more folders and subfolders using the UI. After you create all the necessary folders, your folder tree should look like this when it's all done: - - - -Remember you can always reference the entire project in [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1) to view the complete folder and file strucutre. diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md deleted file mode 100644 index c56284f5168..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging.md +++ /dev/null @@ -1,334 +0,0 @@ ---- -title: "Sources and staging" -id: "8-sources-and-staging" -description: "Sources and staging" ---- - -In this section, we are going to create our source and staging models. - -Sources allow us to create a dependency between our source database object and our staging models which will help us when we look at later. Also, if your source changes database or schema, you only have to update it in your `f1_sources.yml` file rather than updating all of the models it might be used in. - -Staging models are the base of our project, where we bring all the individual components we're going to use to build our more complex and useful models into the project. - -Since we want to focus on dbt and Python in this workshop, check out our [sources](/docs/build/sources) and [staging](/best-practices/how-we-structure/2-staging) docs if you want to learn more (or take our [dbt Fundamentals](https://courses.getdbt.com/collections) course which covers all of our core functionality). - -## Create sources - -We're going to be using each of our 8 Formula 1 tables from our `formula1` database under the `raw`  schema for our transformations and we want to create those tables as sources in our project. - -1. Create a new file called `f1_sources.yml` with the following file path: `models/staging/formula1/f1_sources.yml`. -2. Then, paste the following code into the file before saving it: - -```yaml -version: 2 - -sources: - - name: formula1 - description: formula 1 datasets with normalized tables - database: formula1 - schema: raw - tables: - - name: circuits - description: One record per circuit, which is the specific race course. - columns: - - name: circuitid - tests: - - unique - - not_null - - name: constructors - description: One record per constructor. Constructors are the teams that build their formula 1 cars. - columns: - - name: constructorid - tests: - - unique - - not_null - - name: drivers - description: One record per driver. This table gives details about the driver. - columns: - - name: driverid - tests: - - unique - - not_null - - name: lap_times - description: One row per lap in each race. Lap times started being recorded in this dataset in 1984 and joined through driver_id. - - name: pit_stops - description: One row per pit stop. Pit stops do not have their own id column, the combination of the race_id and driver_id identify the pit stop. - columns: - - name: stop - tests: - - accepted_values: - values: [1,2,3,4,5,6,7,8] - quote: false - - name: races - description: One race per row. Importantly this table contains the race year to understand trends. - columns: - - name: raceid - tests: - - unique - - not_null - - name: results - columns: - - name: resultid - tests: - - unique - - not_null - description: One row per result. The main table that we join out for grid and position variables. - - name: status - description: One status per row. The status contextualizes whether the race was finished or what issues arose e.g. collisions, engine, etc. - columns: - - name: statusid - tests: - - unique - - not_null -``` - -## Create staging models - -The next step is to set up the staging models for each of the 8 source tables. Given the one-to-one relationship between staging models and their corresponding source tables, we'll build 8 staging models here. We know it’s a lot and in the future, we will seek to update the workshop to make this step less repetitive and more efficient. This step is also a good representation of the real world of data, where you have multiple hierarchical tables that you will need to join together! - -1. Let's go in alphabetical order to easily keep track of all our staging models! Create a new file called `stg_f1_circuits.sql` with this file path `models/staging/formula1/stg_f1_circuits.sql`. Then, paste the following code into the file before saving it: - - ```sql - with - - source as ( - - select * from {{ source('formula1','circuits') }} - - ), - - renamed as ( - select - circuitid as circuit_id, - circuitref as circuit_ref, - name as circuit_name, - location, - country, - lat as latitude, - lng as longitude, - alt as altitude - -- omit the url - from source - ) - select * from renamed - ``` - - All we're doing here is pulling the source data into the model using the `source` function, renaming some columns, and omitting the column `url` with a commented note since we don’t need it for our analysis. - -1. Create `stg_f1_constructors.sql` with this file path `models/staging/formula1/stg_f1_constructors.sql`. Paste the following code into it before saving the file: - - ```sql - with - - source as ( - - select * from {{ source('formula1','constructors') }} - - ), - - renamed as ( - select - constructorid as constructor_id, - constructorref as constructor_ref, - name as constructor_name, - nationality as constructor_nationality - -- omit the url - from source - ) - - select * from renamed - ``` - - We have 6 other stages models to create. We can do this by creating new files, then copy and paste the code into our `staging` folder. - -1. Create `stg_f1_drivers.sql` with this file path `models/staging/formula1/stg_f1_drivers.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','drivers') }} - - ), - - renamed as ( - select - driverid as driver_id, - driverref as driver_ref, - number as driver_number, - code as driver_code, - forename, - surname, - dob as date_of_birth, - nationality as driver_nationality - -- omit the url - from source - ) - - select * from renamed - ``` -1. Create `stg_f1_lap_times.sql` with this file path `models/staging/formula1/stg_f1_lap_times.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','lap_times') }} - - ), - - renamed as ( - select - raceid as race_id, - driverid as driver_id, - lap, - position, - time as lap_time_formatted, - milliseconds as lap_time_milliseconds - from source - ) - - select * from renamed - ``` -1. Create `stg_f1_pit_stops.sql` with this file path `models/staging/formula1/stg_f1_pit_stops.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','pit_stops') }} - - ), - - renamed as ( - select - raceid as race_id, - driverid as driver_id, - stop as stop_number, - lap, - time as lap_time_formatted, - duration as pit_stop_duration_seconds, - milliseconds as pit_stop_milliseconds - from source - ) - - select * from renamed - order by pit_stop_duration_seconds desc - ``` - -1. Create ` stg_f1_races.sql` with this file path `models/staging/formula1/stg_f1_races.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','races') }} - - ), - - renamed as ( - select - raceid as race_id, - year as race_year, - round as race_round, - circuitid as circuit_id, - name as circuit_name, - date as race_date, - to_time(time) as race_time, - -- omit the url - fp1_date as free_practice_1_date, - fp1_time as free_practice_1_time, - fp2_date as free_practice_2_date, - fp2_time as free_practice_2_time, - fp3_date as free_practice_3_date, - fp3_time as free_practice_3_time, - quali_date as qualifying_date, - quali_time as qualifying_time, - sprint_date, - sprint_time - from source - ) - - select * from renamed - ``` -1. Create `stg_f1_results.sql` with this file path `models/staging/formula1/stg_f1_results.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','results') }} - - ), - - renamed as ( - select - resultid as result_id, - raceid as race_id, - driverid as driver_id, - constructorid as constructor_id, - number as driver_number, - grid, - position::int as position, - positiontext as position_text, - positionorder as position_order, - points, - laps, - time as results_time_formatted, - milliseconds as results_milliseconds, - fastestlap as fastest_lap, - rank as results_rank, - fastestlaptime as fastest_lap_time_formatted, - fastestlapspeed::decimal(6,3) as fastest_lap_speed, - statusid as status_id - from source - ) - - select * from renamed - ``` -1. Last one! Create `stg_f1_status.sql` with this file path: `models/staging/formula1/stg_f1_status.sql`: - - ```sql - with - - source as ( - - select * from {{ source('formula1','status') }} - - ), - - renamed as ( - select - statusid as status_id, - status - from source - ) - - select * from renamed - ``` - After the source and all the staging models are complete for each of the 8 tables, your staging folder should look like this: - - - -1. It’s a good time to delete our example folder since these two models are extraneous to our formula1 pipeline and `my_first_model` fails a `not_null` test that we won’t spend time investigating. dbt Cloud will warn us that this folder will be permanently deleted, and we are okay with that so select **Delete**. - - - -1. Now that the staging models are built and saved, it's time to create the models in our development schema in Snowflake. To do this we're going to enter into the command line `dbt build` to run all of the models in our project, which includes the 8 new staging models and the existing example models. - - Your run should complete successfully and you should see green checkmarks next to all of your models in the run results. We built our 8 staging models as views and ran 13 source tests that we configured in the `f1_sources.yml` file with not that much code, pretty cool! - - - - Let's take a quick look in Snowflake, refresh database objects, open our development schema, and confirm that the new models are there. If you can see them, then we're good to go! - - - - Before we move onto the next section, be sure to commit your new models to your Git branch. Click **Commit and push** and give your commit a message like `profile, sources, and staging setup` before moving on. - - \ No newline at end of file diff --git a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations.md b/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations.md deleted file mode 100644 index 262bf0e5e52..00000000000 --- a/website/docs/guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -title: "SQL transformations" -id: "9-sql-transformations" -description: "SQL transformations" ---- - -Now that we have all our sources and staging models done, it's time to move into where dbt shines — transformation! - -We need to: - -- Create some intermediate tables to join tables that aren’t hierarchical -- Create core tables for business intelligence (BI) tool ingestion -- Answer the two questions about: - - fastest pit stops - - lap time trends about our Formula 1 data by creating aggregate models using python! - -## Intermediate models - -We need to join lots of reference tables to our results table to create a human readable dataframe. What does this mean? For example, we don’t only want to have the numeric `status_id` in our table, we want to be able to read in a row of data that a driver could not finish a race due to engine failure (`status_id=5`). - -By now, we are pretty good at creating new files in the correct directories so we won’t cover this in detail. All intermediate models should be created in the path `models/intermediate`. - -1. Create a new file called `int_lap_times_years.sql`. In this model, we are joining our lap time and race information so we can look at lap times over years. In earlier Formula 1 eras, lap times were not recorded (only final results), so we filter out records where lap times are null. - - ```sql - with lap_times as ( - - select * from {{ ref('stg_f1_lap_times') }} - - ), - - races as ( - - select * from {{ ref('stg_f1_races') }} - - ), - - expanded_lap_times_by_year as ( - select - lap_times.race_id, - driver_id, - race_year, - lap, - lap_time_milliseconds - from lap_times - left join races - on lap_times.race_id = races.race_id - where lap_time_milliseconds is not null - ) - - select * from expanded_lap_times_by_year - ``` - -2. Create a file called `in_pit_stops.sql`. Pit stops are a many-to-one (M:1) relationship with our races. We are creating a feature called `total_pit_stops_per_race` by partitioning over our `race_id` and `driver_id`, while preserving individual level pit stops for rolling average in our next section. - - ```sql - with stg_f1__pit_stops as - ( - select * from {{ ref('stg_f1_pit_stops') }} - ), - - pit_stops_per_race as ( - select - race_id, - driver_id, - stop_number, - lap, - lap_time_formatted, - pit_stop_duration_seconds, - pit_stop_milliseconds, - max(stop_number) over (partition by race_id,driver_id) as total_pit_stops_per_race - from stg_f1__pit_stops - ) - - select * from pit_stops_per_race - ``` - -3. Create a file called `int_results.sql`. Here we are using 4 of our tables — `races`, `drivers`, `constructors`, and `status` — to give context to our `results` table. We are now able to calculate a new feature `drivers_age_years` by bringing the `date_of_birth` and `race_year` into the same table. We are also creating a column to indicate if the driver did not finish (dnf) the race, based upon if their `position` was null called, `dnf_flag`. - - ```sql - with results as ( - - select * from {{ ref('stg_f1_results') }} - - ), - - races as ( - - select * from {{ ref('stg_f1_races') }} - - ), - - drivers as ( - - select * from {{ ref('stg_f1_drivers') }} - - ), - - constructors as ( - - select * from {{ ref('stg_f1_constructors') }} - ), - - status as ( - - select * from {{ ref('stg_f1_status') }} - ), - - int_results as ( - select - result_id, - results.race_id, - race_year, - race_round, - circuit_id, - circuit_name, - race_date, - race_time, - results.driver_id, - results.driver_number, - forename ||' '|| surname as driver, - cast(datediff('year', date_of_birth, race_date) as int) as drivers_age_years, - driver_nationality, - results.constructor_id, - constructor_name, - constructor_nationality, - grid, - position, - position_text, - position_order, - points, - laps, - results_time_formatted, - results_milliseconds, - fastest_lap, - results_rank, - fastest_lap_time_formatted, - fastest_lap_speed, - results.status_id, - status, - case when position is null then 1 else 0 end as dnf_flag - from results - left join races - on results.race_id=races.race_id - left join drivers - on results.driver_id = drivers.driver_id - left join constructors - on results.constructor_id = constructors.constructor_id - left join status - on results.status_id = status.status_id - ) - - select * from int_results - ``` -1. Create a *Markdown* file `intermediate.md` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. - - ```markdown - # the intent of this .md is to allow for multi-line long form explanations for our intermediate transformations - - # below are descriptions - {% docs int_results %} In this query we want to join out other important information about the race results to have a human readable table about results, races, drivers, constructors, and status. - We will have 4 left joins onto our results table. {% enddocs %} - - {% docs int_pit_stops %} There are many pit stops within one race, aka a M:1 relationship. - We want to aggregate this so we can properly join pit stop information without creating a fanout. {% enddocs %} - - {% docs int_lap_times_years %} Lap times are done per lap. We need to join them out to the race year to understand yearly lap time trends. {% enddocs %} - ``` -1. Create a *YAML* file `intermediate.yml` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. - - ```yaml - version: 2 - - models: - - name: int_results - description: '{{ doc("int_results") }}' - - name: int_pit_stops - description: '{{ doc("int_pit_stops") }}' - - name: int_lap_times_years - description: '{{ doc("int_lap_times_years") }}' - ``` - That wraps up the intermediate models we need to create our core models! - -## Core models - -1. Create a file `fct_results.sql`. This is what I like to refer to as the “mega table” — a really large denormalized table with all our context added in at row level for human readability. Importantly, we have a table `circuits` that is linked through the table `races`. When we joined `races` to `results` in `int_results.sql` we allowed our tables to make the connection from `circuits` to `results` in `fct_results.sql`. We are only taking information about pit stops at the result level so our join would not cause a [fanout](https://community.looker.com/technical-tips-tricks-1021/what-is-a-fanout-23327). - - ```sql - with int_results as ( - - select * from {{ ref('int_results') }} - - ), - - int_pit_stops as ( - select - race_id, - driver_id, - max(total_pit_stops_per_race) as total_pit_stops_per_race - from {{ ref('int_pit_stops') }} - group by 1,2 - ), - - circuits as ( - - select * from {{ ref('stg_f1_circuits') }} - ), - base_results as ( - select - result_id, - int_results.race_id, - race_year, - race_round, - int_results.circuit_id, - int_results.circuit_name, - circuit_ref, - location, - country, - latitude, - longitude, - altitude, - total_pit_stops_per_race, - race_date, - race_time, - int_results.driver_id, - driver, - driver_number, - drivers_age_years, - driver_nationality, - constructor_id, - constructor_name, - constructor_nationality, - grid, - position, - position_text, - position_order, - points, - laps, - results_time_formatted, - results_milliseconds, - fastest_lap, - results_rank, - fastest_lap_time_formatted, - fastest_lap_speed, - status_id, - status, - dnf_flag - from int_results - left join circuits - on int_results.circuit_id=circuits.circuit_id - left join int_pit_stops - on int_results.driver_id=int_pit_stops.driver_id and int_results.race_id=int_pit_stops.race_id - ) - - select * from base_results - ``` - -1. Create the file `pit_stops_joined.sql`. Our results and pit stops are at different levels of dimensionality (also called grain). Simply put, we have multiple pit stops per a result. Since we are interested in understanding information at the pit stop level with information about race year and constructor, we will create a new table `pit_stops_joined.sql` where each row is per pit stop. Our new table tees up our aggregation in Python. - - ```sql - with base_results as ( - - select * from {{ ref('fct_results') }} - - ), - - pit_stops as ( - - select * from {{ ref('int_pit_stops') }} - - ), - - pit_stops_joined as ( - - select - base_results.race_id, - race_year, - base_results.driver_id, - constructor_id, - constructor_name, - stop_number, - lap, - lap_time_formatted, - pit_stop_duration_seconds, - pit_stop_milliseconds - from base_results - left join pit_stops - on base_results.race_id=pit_stops.race_id and base_results.driver_id=pit_stops.driver_id - ) - select * from pit_stops_joined - ``` - -1. Enter in the command line and execute `dbt build` to build out our entire pipeline to up to this point. Don’t worry about “overriding” your previous models – dbt workflows are designed to be idempotent so we can run them again and expect the same results. - -1. Let’s talk about our lineage so far. It’s looking good 😎. We’ve shown how SQL can be used to make data type, column name changes, and handle hierarchical joins really well; all while building out our automated lineage! - - - -1. Time to **Commit and push** our changes and give your commit a message like `intermediate and fact models` before moving on. diff --git a/website/docs/guides/dbt-python-snowpark.md b/website/docs/guides/dbt-python-snowpark.md new file mode 100644 index 00000000000..8417ec9177b --- /dev/null +++ b/website/docs/guides/dbt-python-snowpark.md @@ -0,0 +1,1946 @@ +--- +title: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" +id: "dbt-python-snowpark" +description: "Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake" +hoverSnippet: Learn how to leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake. +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Snowflake'] +level: 'Intermediate' +recently_updated: true +--- + +## Introduction + +The focus of this workshop will be to demonstrate how we can use both *SQL and python together* in the same workflow to run *both analytics and machine learning models* on dbt Cloud. + +All code in today’s workshop can be found on [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1). + +### What you'll use during the lab + +- A [Snowflake account](https://trial.snowflake.com/) with ACCOUNTADMIN access +- A [dbt Cloud account](https://www.getdbt.com/signup/) + +### What you'll learn + +- How to build scalable data transformation pipelines using dbt, and Snowflake using SQL and Python +- How to leverage copying data into Snowflake from a public S3 bucket + +### What you need to know + +- Basic to intermediate SQL and python. +- Basic understanding of dbt fundamentals. We recommend the [dbt Fundamentals course](https://courses.getdbt.com/collections) if you're interested. +- High level machine learning process (encoding, training, testing) +- Simple ML algorithms — we will use logistic regression to keep the focus on the *workflow*, not algorithms! + +### What you'll build + +- A set of data analytics and prediction pipelines using Formula 1 data leveraging dbt and Snowflake, making use of best practices like data quality tests and code promotion between environments +- We will create insights for: + 1. Finding the lap time average and rolling average through the years (is it generally trending up or down)? + 2. Which constructor has the fastest pit stops in 2021? + 3. Predicting the position of each driver given using a decade of data (2010 - 2020) + +As inputs, we are going to leverage Formula 1 datasets hosted on a dbt Labs public S3 bucket. We will create a Snowflake Stage for our CSV files then use Snowflake’s `COPY INTO` function to copy the data in from our CSV files into tables. The Formula 1 is available on [Kaggle](https://www.kaggle.com/datasets/rohanrao/formula-1-world-championship-1950-2020). The data is originally compiled from the [Ergast Developer API](http://ergast.com/mrd/). + +Overall we are going to set up the environments, build scalable pipelines in dbt, establish data tests, and promote code to production. + +## Configure Snowflake + +1. Log in to your trial Snowflake account. You can [sign up for a Snowflake Trial Account using this form](https://signup.snowflake.com/) if you don’t have one. +2. Ensure that your account is set up using **AWS** in the **US East (N. Virginia)**. We will be copying the data from a public AWS S3 bucket hosted by dbt Labs in the us-east-1 region. By ensuring our Snowflake environment setup matches our bucket region, we avoid any multi-region data copy and retrieval latency issues. + + + +3. After creating your account and verifying it from your sign-up email, Snowflake will direct you back to the UI called Snowsight. + +4. When Snowsight first opens, your window should look like the following, with you logged in as the ACCOUNTADMIN with demo worksheets open: + + + +5. Navigate to **Admin > Billing & Terms**. Click **Enable > Acknowledge & Continue** to enable Anaconda Python Packages to run in Snowflake. + + + + + +6. Finally, create a new Worksheet by selecting **+ Worksheet** in the upper right corner. + + +33 1. Log in to your trial Snowflake account. You can [sign up for a Snowflake Trial Account using this form](https://signup.snowflake.com/) if you don’t have one. +2. Ensure that your account is set up using **AWS** in the **US East (N. Virginia)**. We will be copying the data from a public AWS S3 bucket hosted by dbt Labs in the us-east-1 region. By ensuring our Snowflake environment setup matches our bucket region, we avoid any multi-region data copy and retrieval latency issues. + + + +3. After creating your account and verifying it from your sign-up email, Snowflake will direct you back to the UI called Snowsight. + +4. When Snowsight first opens, your window should look like the following, with you logged in as the ACCOUNTADMIN with demo worksheets open: + + + + +5. Navigate to **Admin > Billing & Terms**. Click **Enable > Acknowledge & Continue** to enable Anaconda Python Packages to run in Snowflake. + + + + + +6. Finally, create a new Worksheet by selecting **+ Worksheet** in the upper right corner. + +## Connect to data source + +We need to obtain our data source by copying our Formula 1 data into Snowflake tables from a public S3 bucket that dbt Labs hosts. + +1. When a new Snowflake account is created, there should be a preconfigured warehouse in your account named `COMPUTE_WH`. +2. If for any reason your account doesn’t have this warehouse, we can create a warehouse using the following script: + + ```sql + create or replace warehouse COMPUTE_WH with warehouse_size=XSMALL + ``` + +3. Rename the worksheet to `data setup script` since we will be placing code in this worksheet to ingest the Formula 1 data. Make sure you are still logged in as the **ACCOUNTADMIN** and select the **COMPUTE_WH** warehouse. + + + +4. Copy the following code into the main body of the Snowflake worksheet. You can also find this setup script under the `setup` folder in the [Git repository](https://github.com/dbt-labs/python-snowpark-formula1/blob/main/setup/setup_script_s3_to_snowflake.sql). The script is long since it's bring in all of the data we'll need today! + + ```sql + -- create and define our formula1 database + create or replace database formula1; + use database formula1; + create or replace schema raw; + use schema raw; + + -- define our file format for reading in the csvs + create or replace file format csvformat + type = csv + field_delimiter =',' + field_optionally_enclosed_by = '"', + skip_header=1; + + -- + create or replace stage formula1_stage + file_format = csvformat + url = 's3://formula1-dbt-cloud-python-demo/formula1-kaggle-data/'; + + -- load in the 8 tables we need for our demo + -- we are first creating the table then copying our data in from s3 + -- think of this as an empty container or shell that we are then filling + create or replace table formula1.raw.circuits ( + CIRCUITID NUMBER(38,0), + CIRCUITREF VARCHAR(16777216), + NAME VARCHAR(16777216), + LOCATION VARCHAR(16777216), + COUNTRY VARCHAR(16777216), + LAT FLOAT, + LNG FLOAT, + ALT NUMBER(38,0), + URL VARCHAR(16777216) + ); + -- copy our data from public s3 bucket into our tables + copy into circuits + from @formula1_stage/circuits.csv + on_error='continue'; + + create or replace table formula1.raw.constructors ( + CONSTRUCTORID NUMBER(38,0), + CONSTRUCTORREF VARCHAR(16777216), + NAME VARCHAR(16777216), + NATIONALITY VARCHAR(16777216), + URL VARCHAR(16777216) + ); + copy into constructors + from @formula1_stage/constructors.csv + on_error='continue'; + + create or replace table formula1.raw.drivers ( + DRIVERID NUMBER(38,0), + DRIVERREF VARCHAR(16777216), + NUMBER VARCHAR(16777216), + CODE VARCHAR(16777216), + FORENAME VARCHAR(16777216), + SURNAME VARCHAR(16777216), + DOB DATE, + NATIONALITY VARCHAR(16777216), + URL VARCHAR(16777216) + ); + copy into drivers + from @formula1_stage/drivers.csv + on_error='continue'; + + create or replace table formula1.raw.lap_times ( + RACEID NUMBER(38,0), + DRIVERID NUMBER(38,0), + LAP NUMBER(38,0), + POSITION FLOAT, + TIME VARCHAR(16777216), + MILLISECONDS NUMBER(38,0) + ); + copy into lap_times + from @formula1_stage/lap_times.csv + on_error='continue'; + + create or replace table formula1.raw.pit_stops ( + RACEID NUMBER(38,0), + DRIVERID NUMBER(38,0), + STOP NUMBER(38,0), + LAP NUMBER(38,0), + TIME VARCHAR(16777216), + DURATION VARCHAR(16777216), + MILLISECONDS NUMBER(38,0) + ); + copy into pit_stops + from @formula1_stage/pit_stops.csv + on_error='continue'; + + create or replace table formula1.raw.races ( + RACEID NUMBER(38,0), + YEAR NUMBER(38,0), + ROUND NUMBER(38,0), + CIRCUITID NUMBER(38,0), + NAME VARCHAR(16777216), + DATE DATE, + TIME VARCHAR(16777216), + URL VARCHAR(16777216), + FP1_DATE VARCHAR(16777216), + FP1_TIME VARCHAR(16777216), + FP2_DATE VARCHAR(16777216), + FP2_TIME VARCHAR(16777216), + FP3_DATE VARCHAR(16777216), + FP3_TIME VARCHAR(16777216), + QUALI_DATE VARCHAR(16777216), + QUALI_TIME VARCHAR(16777216), + SPRINT_DATE VARCHAR(16777216), + SPRINT_TIME VARCHAR(16777216) + ); + copy into races + from @formula1_stage/races.csv + on_error='continue'; + + create or replace table formula1.raw.results ( + RESULTID NUMBER(38,0), + RACEID NUMBER(38,0), + DRIVERID NUMBER(38,0), + CONSTRUCTORID NUMBER(38,0), + NUMBER NUMBER(38,0), + GRID NUMBER(38,0), + POSITION FLOAT, + POSITIONTEXT VARCHAR(16777216), + POSITIONORDER NUMBER(38,0), + POINTS NUMBER(38,0), + LAPS NUMBER(38,0), + TIME VARCHAR(16777216), + MILLISECONDS NUMBER(38,0), + FASTESTLAP NUMBER(38,0), + RANK NUMBER(38,0), + FASTESTLAPTIME VARCHAR(16777216), + FASTESTLAPSPEED FLOAT, + STATUSID NUMBER(38,0) + ); + copy into results + from @formula1_stage/results.csv + on_error='continue'; + + create or replace table formula1.raw.status ( + STATUSID NUMBER(38,0), + STATUS VARCHAR(16777216) + ); + copy into status + from @formula1_stage/status.csv + on_error='continue'; + + ``` + +5. Ensure all the commands are selected before running the query — an easy way to do this is to use Ctrl-a to highlight all of the code in the worksheet. Select **run** (blue triangle icon). Notice how the dot next to your **COMPUTE_WH** turns from gray to green as you run the query. The **status** table is the final table of all 8 tables loaded in. + + + +6. Let’s unpack that pretty long query we ran into component parts. We ran this query to load in our 8 Formula 1 tables from a public S3 bucket. To do this, we: + - Created a new database called `formula1` and a schema called `raw` to place our raw (untransformed) data into. + - Defined our file format for our CSV files. Importantly, here we use a parameter called `field_optionally_enclosed_by =` since the string columns in our Formula 1 csv files use quotes. Quotes are used around string values to avoid parsing issues where commas `,` and new lines `/n` in data values could cause data loading errors. + - Created a stage to locate our data we are going to load in. Snowflake Stages are locations where data files are stored. Stages are used to both load and unload data to and from Snowflake locations. Here we are using an external stage, by referencing an S3 bucket. + - Created our tables for our data to be copied into. These are empty tables with the column name and data type. Think of this as creating an empty container that the data will then fill into. + - Used the `copy into` statement for each of our tables. We reference our staged location we created and upon loading errors continue to load in the rest of the data. You should not have data loading errors but if you do, those rows will be skipped and Snowflake will tell you which rows caused errors + +7. Now let's take a look at some of our cool Formula 1 data we just loaded up! + 1. Create a new worksheet by selecting the **+** then **New Worksheet**. + + 2. Navigate to **Database > Formula1 > RAW > Tables**. + 3. Query the data using the following code. There are only 76 rows in the circuits table, so we don’t need to worry about limiting the amount of data we query. + + ```sql + select * from formula1.raw.circuits + ``` + + 4. Run the query. From here on out, we’ll use the keyboard shortcuts Command-Enter or Control-Enter to run queries and won’t explicitly call out this step. + 5. Review the query results, you should see information about Formula 1 circuits, starting with Albert Park in Australia! + 6. Finally, ensure you have all 8 tables starting with `CIRCUITS` and ending with `STATUS`. Now we are ready to connect into dbt Cloud! + + + +## Configure dbt Cloud + +1. We are going to be using [Snowflake Partner Connect](https://docs.snowflake.com/en/user-guide/ecosystem-partner-connect.html) to set up a dbt Cloud account. Using this method will allow you to spin up a fully fledged dbt account with your [Snowflake connection](/docs/cloud/connect-data-platform/connect-snowflake), [managed repository](/docs/collaborate/git/managed-repository), environments, and credentials already established. +2. Navigate out of your worksheet back by selecting **home**. +3. In Snowsight, confirm that you are using the **ACCOUNTADMIN** role. +4. Navigate to the **Admin** **> Partner Connect**. Find **dbt** either by using the search bar or navigating the **Data Integration**. Select the **dbt** tile. + +5. You should now see a new window that says **Connect to dbt**. Select **Optional Grant** and add the `FORMULA1` database. This will grant access for your new dbt user role to the FORMULA1 database. + + +6. Ensure the `FORMULA1` is present in your optional grant before clicking **Connect**.  This will create a dedicated dbt user, database, warehouse, and role for your dbt Cloud trial. + + + +7. When you see the **Your partner account has been created** window, click **Activate**. + +8. You should be redirected to a dbt Cloud registration page. Fill out the form. Make sure to save the password somewhere for login in the future. + + + +9. Select **Complete Registration**. You should now be redirected to your dbt Cloud account, complete with a connection to your Snowflake account, a deployment and a development environment, and a sample job. + +10. To help you version control your dbt project, we have connected it to a [managed repository](/docs/collaborate/git/managed-repository), which means that dbt Labs will be hosting your repository for you. This will give you access to a Git workflow without you having to create and host the repository yourself. You will not need to know Git for this workshop; dbt Cloud will help guide you through the workflow. In the future, when you’re developing your own project, [feel free to use your own repository](/docs/cloud/git/connect-github). This will allow you to learn more about features like [Slim CI](/docs/deploy/continuous-integration) builds after this workshop. + +## Change development schema name navigate the IDE + +1. First we are going to change the name of our default schema to where our dbt models will build. By default, the name is `dbt_`. We will change this to `dbt_` to create your own personal development schema. To do this, select **Profile Settings** from the gear icon in the upper right. + + + +2. Navigate to the **Credentials** menu and select **Partner Connect Trial**, which will expand the credentials menu. + + + +3. Click **Edit** and change the name of your schema from `dbt_` to `dbt_YOUR_NAME` replacing `YOUR_NAME` with your initials and name (`hwatson` is used in the lab screenshots). Be sure to click **Save** for your changes! + + +4. We now have our own personal development schema, amazing! When we run our first dbt models they will build into this schema. +5. Let’s open up dbt Cloud’s Integrated Development Environment (IDE) and familiarize ourselves. Choose **Develop** at the top of the UI. + +6. When the IDE is done loading, click **Initialize dbt project**. The initialization process creates a collection of files and folders necessary to run your dbt project. + + +7. After the initialization is finished, you can view the files and folders in the file tree menu. As we move through the workshop we'll be sure to touch on a few key files and folders that we'll work with to build out our project. +8. Next click **Commit and push** to commit the new files and folders from the initialize step. We always want our commit messages to be relevant to the work we're committing, so be sure to provide a message like `initialize project` and select **Commit Changes**. + + + + + +9. [Committing](https://www.atlassian.com/git/tutorials/saving-changes/git-commit) your work here will save it to the managed git repository that was created during the Partner Connect signup. This initial commit is the only commit that will be made directly to our `main` branch and from *here on out we'll be doing all of our work on a development branch*. This allows us to keep our development work separate from our production code. +10. There are a couple of key features to point out about the IDE before we get to work. It is a text editor, an SQL and Python runner, and a CLI with Git version control all baked into one package! This allows you to focus on editing your SQL and Python files, previewing the results with the SQL runner (it even runs Jinja!), and building models at the command line without having to move between different applications. The Git workflow in dbt Cloud allows both Git beginners and experts alike to be able to easily version control all of their work with a couple clicks. + + + +11. Let's run our first dbt models! Two example models are included in your dbt project in the `models/examples` folder that we can use to illustrate how to run dbt at the command line. Type `dbt run` into the command line and click **Enter** on your keyboard. When the run bar expands you'll be able to see the results of the run, where you should see the run complete successfully. + + + +12. The run results allow you to see the code that dbt compiles and sends to Snowflake for execution. To view the logs for this run, select one of the model tabs using the  **>** icon and then **Details**. If you scroll down a bit you'll be able to see the compiled code and how dbt interacts with Snowflake. Given that this run took place in our development environment, the models were created in your development schema. + + + +13. Now let's switch over to Snowflake to confirm that the objects were actually created. Click on the three dots **…** above your database objects and then **Refresh**. Expand the **PC_DBT_DB** database and you should see your development schema. Select the schema, then **Tables**  and **Views**. Now you should be able to see `MY_FIRST_DBT_MODEL` as a table and `MY_SECOND_DBT_MODEL` as a view. + + +## Create branch and set up project configs + +In this step, we’ll need to create a development branch and set up project level configurations. + +1. To get started with development for our project, we'll need to create a new Git branch for our work. Select **create branch** and name your development branch. We'll call our branch `snowpark_python_workshop` then click **Submit**. +2. The first piece of development we'll do on the project is to update the `dbt_project.yml` file. Every dbt project requires a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. The [dbt_project.yml](/reference/dbt_project.yml) file also contains important information that tells dbt how to operate on your project. +3. Select the `dbt_project.yml` file from the file tree to open it and replace all of the existing contents with the following code below. When you're done, save the file by clicking **save**. You can also use the Command-S or Control-S shortcut from here on out. + + ```yaml + # Name your project! Project names should contain only lowercase characters + # and underscores. A good package name should reflect your organization's + # name or the intended use of these models + name: 'snowflake_dbt_python_formula1' + version: '1.3.0' + require-dbt-version: '>=1.3.0' + config-version: 2 + + # This setting configures which "profile" dbt uses for this project. + profile: 'default' + + # These configurations specify where dbt should look for different types of files. + # The `model-paths` config, for example, states that models in this project can be + # found in the "models/" directory. You probably won't need to change these! + model-paths: ["models"] + analysis-paths: ["analyses"] + test-paths: ["tests"] + seed-paths: ["seeds"] + macro-paths: ["macros"] + snapshot-paths: ["snapshots"] + + target-path: "target" # directory which will store compiled SQL files + clean-targets: # directories to be removed by `dbt clean` + - "target" + - "dbt_packages" + + models: + snowflake_dbt_python_formula1: + staging: + + +docs: + node_color: "CadetBlue" + marts: + +materialized: table + aggregates: + +docs: + node_color: "Maroon" + +tags: "bi" + + core: + +docs: + node_color: "#800080" + intermediate: + +docs: + node_color: "MediumSlateBlue" + ml: + prep: + +docs: + node_color: "Indigo" + train_predict: + +docs: + node_color: "#36454f" + + ``` + +4. The key configurations to point out in the file with relation to the work that we're going to do are in the `models` section. + - `require-dbt-version` — Tells dbt which version of dbt to use for your project. We are requiring 1.3.0 and any newer version to run python models and node colors. + - `materialized` — Tells dbt how to materialize models when compiling the code before it pushes it down to Snowflake. All models in the `marts` folder will be built as tables. + - `tags` — Applies tags at a directory level to all models. All models in the `aggregates` folder will be tagged as `bi` (abbreviation for business intelligence). + - `docs` — Specifies the `node_color` either by the plain color name or a hex value. +5. [Materializations](/docs/build/materializations) are strategies for persisting dbt models in a warehouse, with `tables` and `views` being the most commonly utilized types. By default, all dbt models are materialized as views and other materialization types can be configured in the `dbt_project.yml` file or in a model itself. It’s very important to note *Python models can only be materialized as tables or incremental models.* Since all our Python models exist under `marts`, the following portion of our `dbt_project.yml` ensures no errors will occur when we run our Python models. Starting with [dbt version 1.4](/docs/dbt-versions/core-upgrade/upgrading-to-v1.4#updates-to-python-models), Python files will automatically get materialized as tables even if not explicitly specified. + + ```yaml + marts:     + +materialized: table + ``` + +## Create folders and organize files + +dbt Labs has developed a [project structure guide](/best-practices/how-we-structure/1-guide-overview/) that contains a number of recommendations for how to build the folder structure for your project. Do check out that guide if you want to learn more. Right now we are going to create some folders to organize our files: + +- Sources — This is our Formula 1 dataset and it will be defined in a source YAML file. +- Staging models — These models have a 1:1 with their source table. +- Intermediate — This is where we will be joining some Formula staging models. +- Marts models — Here is where we perform our major transformations. It contains these subfolders: + - aggregates + - core + - ml + +1. In your file tree, use your cursor and hover over the `models` subdirectory, click the three dots **…** that appear to the right of the folder name, then select **Create Folder**. We're going to add two new folders to the file path, `staging` and `formula1` (in that order) by typing `staging/formula1` into the file path. + + + + + - If you click into your `models` directory now, you should see the new `staging` folder nested within `models` and the `formula1` folder nested within `staging`. +2. Create two additional folders the same as the last step. Within the `models` subdirectory, create new directories `marts/core`. + +3. We will need to create a few more folders and subfolders using the UI. After you create all the necessary folders, your folder tree should look like this when it's all done: + + + +Remember you can always reference the entire project in [GitHub](https://github.com/dbt-labs/python-snowpark-formula1/tree/python-formula1) to view the complete folder and file strucutre. + +## Create source and staging models + +In this section, we are going to create our source and staging models. + +Sources allow us to create a dependency between our source database object and our staging models which will help us when we look at later. Also, if your source changes database or schema, you only have to update it in your `f1_sources.yml` file rather than updating all of the models it might be used in. + +Staging models are the base of our project, where we bring all the individual components we're going to use to build our more complex and useful models into the project. + +Since we want to focus on dbt and Python in this workshop, check out our [sources](/docs/build/sources) and [staging](/best-practices/how-we-structure/2-staging) docs if you want to learn more (or take our [dbt Fundamentals](https://courses.getdbt.com/collections) course which covers all of our core functionality). + +### 1. Create sources + +We're going to be using each of our 8 Formula 1 tables from our `formula1` database under the `raw`  schema for our transformations and we want to create those tables as sources in our project. + +1. Create a new file called `f1_sources.yml` with the following file path: `models/staging/formula1/f1_sources.yml`. +2. Then, paste the following code into the file before saving it: + +```yaml +version: 2 + +sources: + - name: formula1 + description: formula 1 datasets with normalized tables + database: formula1 + schema: raw + tables: + - name: circuits + description: One record per circuit, which is the specific race course. + columns: + - name: circuitid + tests: + - unique + - not_null + - name: constructors + description: One record per constructor. Constructors are the teams that build their formula 1 cars. + columns: + - name: constructorid + tests: + - unique + - not_null + - name: drivers + description: One record per driver. This table gives details about the driver. + columns: + - name: driverid + tests: + - unique + - not_null + - name: lap_times + description: One row per lap in each race. Lap times started being recorded in this dataset in 1984 and joined through driver_id. + - name: pit_stops + description: One row per pit stop. Pit stops do not have their own id column, the combination of the race_id and driver_id identify the pit stop. + columns: + - name: stop + tests: + - accepted_values: + values: [1,2,3,4,5,6,7,8] + quote: false + - name: races + description: One race per row. Importantly this table contains the race year to understand trends. + columns: + - name: raceid + tests: + - unique + - not_null + - name: results + columns: + - name: resultid + tests: + - unique + - not_null + description: One row per result. The main table that we join out for grid and position variables. + - name: status + description: One status per row. The status contextualizes whether the race was finished or what issues arose e.g. collisions, engine, etc. + columns: + - name: statusid + tests: + - unique + - not_null +``` + +### 2. Create staging models + +The next step is to set up the staging models for each of the 8 source tables. Given the one-to-one relationship between staging models and their corresponding source tables, we'll build 8 staging models here. We know it’s a lot and in the future, we will seek to update the workshop to make this step less repetitive and more efficient. This step is also a good representation of the real world of data, where you have multiple hierarchical tables that you will need to join together! + +1. Let's go in alphabetical order to easily keep track of all our staging models! Create a new file called `stg_f1_circuits.sql` with this file path `models/staging/formula1/stg_f1_circuits.sql`. Then, paste the following code into the file before saving it: + + ```sql + with + + source as ( + + select * from {{ source('formula1','circuits') }} + + ), + + renamed as ( + select + circuitid as circuit_id, + circuitref as circuit_ref, + name as circuit_name, + location, + country, + lat as latitude, + lng as longitude, + alt as altitude + -- omit the url + from source + ) + select * from renamed + ``` + + All we're doing here is pulling the source data into the model using the `source` function, renaming some columns, and omitting the column `url` with a commented note since we don’t need it for our analysis. + +1. Create `stg_f1_constructors.sql` with this file path `models/staging/formula1/stg_f1_constructors.sql`. Paste the following code into it before saving the file: + + ```sql + with + + source as ( + + select * from {{ source('formula1','constructors') }} + + ), + + renamed as ( + select + constructorid as constructor_id, + constructorref as constructor_ref, + name as constructor_name, + nationality as constructor_nationality + -- omit the url + from source + ) + + select * from renamed + ``` + + We have 6 other stages models to create. We can do this by creating new files, then copy and paste the code into our `staging` folder. + +1. Create `stg_f1_drivers.sql` with this file path `models/staging/formula1/stg_f1_drivers.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','drivers') }} + + ), + + renamed as ( + select + driverid as driver_id, + driverref as driver_ref, + number as driver_number, + code as driver_code, + forename, + surname, + dob as date_of_birth, + nationality as driver_nationality + -- omit the url + from source + ) + + select * from renamed + ``` + +1. Create `stg_f1_lap_times.sql` with this file path `models/staging/formula1/stg_f1_lap_times.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','lap_times') }} + + ), + + renamed as ( + select + raceid as race_id, + driverid as driver_id, + lap, + position, + time as lap_time_formatted, + milliseconds as lap_time_milliseconds + from source + ) + + select * from renamed + ``` + +1. Create `stg_f1_pit_stops.sql` with this file path `models/staging/formula1/stg_f1_pit_stops.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','pit_stops') }} + + ), + + renamed as ( + select + raceid as race_id, + driverid as driver_id, + stop as stop_number, + lap, + time as lap_time_formatted, + duration as pit_stop_duration_seconds, + milliseconds as pit_stop_milliseconds + from source + ) + + select * from renamed + order by pit_stop_duration_seconds desc + ``` + +1. Create `stg_f1_races.sql` with this file path `models/staging/formula1/stg_f1_races.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','races') }} + + ), + + renamed as ( + select + raceid as race_id, + year as race_year, + round as race_round, + circuitid as circuit_id, + name as circuit_name, + date as race_date, + to_time(time) as race_time, + -- omit the url + fp1_date as free_practice_1_date, + fp1_time as free_practice_1_time, + fp2_date as free_practice_2_date, + fp2_time as free_practice_2_time, + fp3_date as free_practice_3_date, + fp3_time as free_practice_3_time, + quali_date as qualifying_date, + quali_time as qualifying_time, + sprint_date, + sprint_time + from source + ) + + select * from renamed + ``` + +1. Create `stg_f1_results.sql` with this file path `models/staging/formula1/stg_f1_results.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','results') }} + + ), + + renamed as ( + select + resultid as result_id, + raceid as race_id, + driverid as driver_id, + constructorid as constructor_id, + number as driver_number, + grid, + position::int as position, + positiontext as position_text, + positionorder as position_order, + points, + laps, + time as results_time_formatted, + milliseconds as results_milliseconds, + fastestlap as fastest_lap, + rank as results_rank, + fastestlaptime as fastest_lap_time_formatted, + fastestlapspeed::decimal(6,3) as fastest_lap_speed, + statusid as status_id + from source + ) + + select * from renamed + ``` + +1. Last one! Create `stg_f1_status.sql` with this file path: `models/staging/formula1/stg_f1_status.sql`: + + ```sql + with + + source as ( + + select * from {{ source('formula1','status') }} + + ), + + renamed as ( + select + statusid as status_id, + status + from source + ) + + select * from renamed + ``` + + After the source and all the staging models are complete for each of the 8 tables, your staging folder should look like this: + + + +1. It’s a good time to delete our example folder since these two models are extraneous to our formula1 pipeline and `my_first_model` fails a `not_null` test that we won’t spend time investigating. dbt Cloud will warn us that this folder will be permanently deleted, and we are okay with that so select **Delete**. + + + +1. Now that the staging models are built and saved, it's time to create the models in our development schema in Snowflake. To do this we're going to enter into the command line `dbt build` to run all of the models in our project, which includes the 8 new staging models and the existing example models. + + Your run should complete successfully and you should see green checkmarks next to all of your models in the run results. We built our 8 staging models as views and ran 13 source tests that we configured in the `f1_sources.yml` file with not that much code, pretty cool! + + + + Let's take a quick look in Snowflake, refresh database objects, open our development schema, and confirm that the new models are there. If you can see them, then we're good to go! + + + + Before we move onto the next section, be sure to commit your new models to your Git branch. Click **Commit and push** and give your commit a message like `profile, sources, and staging setup` before moving on. + +## Transform SQL + +Now that we have all our sources and staging models done, it's time to move into where dbt shines — transformation! + +We need to: + +- Create some intermediate tables to join tables that aren’t hierarchical +- Create core tables for business intelligence (BI) tool ingestion +- Answer the two questions about: + - fastest pit stops + - lap time trends about our Formula 1 data by creating aggregate models using python! + +### Intermediate models + +We need to join lots of reference tables to our results table to create a human readable dataframe. What does this mean? For example, we don’t only want to have the numeric `status_id` in our table, we want to be able to read in a row of data that a driver could not finish a race due to engine failure (`status_id=5`). + +By now, we are pretty good at creating new files in the correct directories so we won’t cover this in detail. All intermediate models should be created in the path `models/intermediate`. + +1. Create a new file called `int_lap_times_years.sql`. In this model, we are joining our lap time and race information so we can look at lap times over years. In earlier Formula 1 eras, lap times were not recorded (only final results), so we filter out records where lap times are null. + + ```sql + with lap_times as ( + + select * from {{ ref('stg_f1_lap_times') }} + + ), + + races as ( + + select * from {{ ref('stg_f1_races') }} + + ), + + expanded_lap_times_by_year as ( + select + lap_times.race_id, + driver_id, + race_year, + lap, + lap_time_milliseconds + from lap_times + left join races + on lap_times.race_id = races.race_id + where lap_time_milliseconds is not null + ) + + select * from expanded_lap_times_by_year + ``` + +2. Create a file called `in_pit_stops.sql`. Pit stops are a many-to-one (M:1) relationship with our races. We are creating a feature called `total_pit_stops_per_race` by partitioning over our `race_id` and `driver_id`, while preserving individual level pit stops for rolling average in our next section. + + ```sql + with stg_f1__pit_stops as + ( + select * from {{ ref('stg_f1_pit_stops') }} + ), + + pit_stops_per_race as ( + select + race_id, + driver_id, + stop_number, + lap, + lap_time_formatted, + pit_stop_duration_seconds, + pit_stop_milliseconds, + max(stop_number) over (partition by race_id,driver_id) as total_pit_stops_per_race + from stg_f1__pit_stops + ) + + select * from pit_stops_per_race + ``` + +3. Create a file called `int_results.sql`. Here we are using 4 of our tables — `races`, `drivers`, `constructors`, and `status` — to give context to our `results` table. We are now able to calculate a new feature `drivers_age_years` by bringing the `date_of_birth` and `race_year` into the same table. We are also creating a column to indicate if the driver did not finish (dnf) the race, based upon if their `position` was null called, `dnf_flag`. + + ```sql + with results as ( + + select * from {{ ref('stg_f1_results') }} + + ), + + races as ( + + select * from {{ ref('stg_f1_races') }} + + ), + + drivers as ( + + select * from {{ ref('stg_f1_drivers') }} + + ), + + constructors as ( + + select * from {{ ref('stg_f1_constructors') }} + ), + + status as ( + + select * from {{ ref('stg_f1_status') }} + ), + + int_results as ( + select + result_id, + results.race_id, + race_year, + race_round, + circuit_id, + circuit_name, + race_date, + race_time, + results.driver_id, + results.driver_number, + forename ||' '|| surname as driver, + cast(datediff('year', date_of_birth, race_date) as int) as drivers_age_years, + driver_nationality, + results.constructor_id, + constructor_name, + constructor_nationality, + grid, + position, + position_text, + position_order, + points, + laps, + results_time_formatted, + results_milliseconds, + fastest_lap, + results_rank, + fastest_lap_time_formatted, + fastest_lap_speed, + results.status_id, + status, + case when position is null then 1 else 0 end as dnf_flag + from results + left join races + on results.race_id=races.race_id + left join drivers + on results.driver_id = drivers.driver_id + left join constructors + on results.constructor_id = constructors.constructor_id + left join status + on results.status_id = status.status_id + ) + + select * from int_results + ``` + +1. Create a *Markdown* file `intermediate.md` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. + + ```markdown + # the intent of this .md is to allow for multi-line long form explanations for our intermediate transformations + + # below are descriptions + {% docs int_results %} In this query we want to join out other important information about the race results to have a human readable table about results, races, drivers, constructors, and status. + We will have 4 left joins onto our results table. {% enddocs %} + + {% docs int_pit_stops %} There are many pit stops within one race, aka a M:1 relationship. + We want to aggregate this so we can properly join pit stop information without creating a fanout. {% enddocs %} + + {% docs int_lap_times_years %} Lap times are done per lap. We need to join them out to the race year to understand yearly lap time trends. {% enddocs %} + ``` + +1. Create a *YAML* file `intermediate.yml` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. + + ```yaml + version: 2 + + models: + - name: int_results + description: '{{ doc("int_results") }}' + - name: int_pit_stops + description: '{{ doc("int_pit_stops") }}' + - name: int_lap_times_years + description: '{{ doc("int_lap_times_years") }}' + ``` + + That wraps up the intermediate models we need to create our core models! + +### Core models + +1. Create a file `fct_results.sql`. This is what I like to refer to as the “mega table” — a really large denormalized table with all our context added in at row level for human readability. Importantly, we have a table `circuits` that is linked through the table `races`. When we joined `races` to `results` in `int_results.sql` we allowed our tables to make the connection from `circuits` to `results` in `fct_results.sql`. We are only taking information about pit stops at the result level so our join would not cause a [fanout](https://community.looker.com/technical-tips-tricks-1021/what-is-a-fanout-23327). + + ```sql + with int_results as ( + + select * from {{ ref('int_results') }} + + ), + + int_pit_stops as ( + select + race_id, + driver_id, + max(total_pit_stops_per_race) as total_pit_stops_per_race + from {{ ref('int_pit_stops') }} + group by 1,2 + ), + + circuits as ( + + select * from {{ ref('stg_f1_circuits') }} + ), + base_results as ( + select + result_id, + int_results.race_id, + race_year, + race_round, + int_results.circuit_id, + int_results.circuit_name, + circuit_ref, + location, + country, + latitude, + longitude, + altitude, + total_pit_stops_per_race, + race_date, + race_time, + int_results.driver_id, + driver, + driver_number, + drivers_age_years, + driver_nationality, + constructor_id, + constructor_name, + constructor_nationality, + grid, + position, + position_text, + position_order, + points, + laps, + results_time_formatted, + results_milliseconds, + fastest_lap, + results_rank, + fastest_lap_time_formatted, + fastest_lap_speed, + status_id, + status, + dnf_flag + from int_results + left join circuits + on int_results.circuit_id=circuits.circuit_id + left join int_pit_stops + on int_results.driver_id=int_pit_stops.driver_id and int_results.race_id=int_pit_stops.race_id + ) + + select * from base_results + ``` + +1. Create the file `pit_stops_joined.sql`. Our results and pit stops are at different levels of dimensionality (also called grain). Simply put, we have multiple pit stops per a result. Since we are interested in understanding information at the pit stop level with information about race year and constructor, we will create a new table `pit_stops_joined.sql` where each row is per pit stop. Our new table tees up our aggregation in Python. + + ```sql + with base_results as ( + + select * from {{ ref('fct_results') }} + + ), + + pit_stops as ( + + select * from {{ ref('int_pit_stops') }} + + ), + + pit_stops_joined as ( + + select + base_results.race_id, + race_year, + base_results.driver_id, + constructor_id, + constructor_name, + stop_number, + lap, + lap_time_formatted, + pit_stop_duration_seconds, + pit_stop_milliseconds + from base_results + left join pit_stops + on base_results.race_id=pit_stops.race_id and base_results.driver_id=pit_stops.driver_id + ) + select * from pit_stops_joined + ``` + +1. Enter in the command line and execute `dbt build` to build out our entire pipeline to up to this point. Don’t worry about “overriding” your previous models – dbt workflows are designed to be idempotent so we can run them again and expect the same results. + +1. Let’s talk about our lineage so far. It’s looking good 😎. We’ve shown how SQL can be used to make data type, column name changes, and handle hierarchical joins really well; all while building out our automated lineage! + + + +1. Time to **Commit and push** our changes and give your commit a message like `intermediate and fact models` before moving on. + +## Running dbt Python models + +Up until now, SQL has been driving the project (car pun intended) for data cleaning and hierarchical joining. Now it’s time for Python to take the wheel (car pun still intended) for the rest of our lab! For more information about running Python models on dbt, check out our [docs](/docs/build/python-models). To learn more about dbt python works under the hood, check out [Snowpark for Python](https://docs.snowflake.com/en/developer-guide/snowpark/python/index.html), which makes running dbt Python models possible. + +There are quite a few differences between SQL and Python in terms of the dbt syntax and DDL, so we’ll be breaking our code and model runs down further for our python models. + +### Pit stop analysis + +First, we want to find out: which constructor had the fastest pit stops in 2021? (constructor is a Formula 1 team that builds or “constructs” the car). + +1. Create a new file called `fastest_pit_stops_by_constructor.py` in our `aggregates` (this is the first time we are using the `.py` extension!). +2. Copy the following code into the file: + + ```python + import numpy as np + import pandas as pd + + def model(dbt, session): + # dbt configuration + dbt.config(packages=["pandas","numpy"]) + + # get upstream data + pit_stops_joined = dbt.ref("pit_stops_joined").to_pandas() + + # provide year so we do not hardcode dates + year=2021 + + # describe the data + pit_stops_joined["PIT_STOP_SECONDS"] = pit_stops_joined["PIT_STOP_MILLISECONDS"]/1000 + fastest_pit_stops = pit_stops_joined[(pit_stops_joined["RACE_YEAR"]==year)].groupby(by="CONSTRUCTOR_NAME")["PIT_STOP_SECONDS"].describe().sort_values(by='mean') + fastest_pit_stops.reset_index(inplace=True) + fastest_pit_stops.columns = fastest_pit_stops.columns.str.upper() + + return fastest_pit_stops.round(2) + ``` + +3. Let’s break down what this code is doing step by step: + - First, we are importing the Python libraries that we are using. A *library* is a reusable chunk of code that someone else wrote that you may want to include in your programs/projects. We are using `numpy` and `pandas`in this Python model. This is similar to a dbt *package*, but our Python libraries do *not* persist across the entire project. + - Defining a function called `model` with the parameter `dbt` and `session`. The parameter `dbt` is a class compiled by dbt, which enables you to run your Python code in the context of your dbt project and DAG. The parameter `session` is a class representing your Snowflake’s connection to the Python backend. The `model` function *must return a single DataFrame*. You can see that all the data transformation happening is within the body of the `model` function that the `return` statement is tied to. + - Then, within the context of our dbt model library, we are passing in a configuration of which packages we need using `dbt.config(packages=["pandas","numpy"])`. + - Use the `.ref()` function to retrieve the data frame `pit_stops_joined` that we created in our last step using SQL. We cast this to a pandas dataframe (by default it's a Snowpark Dataframe). + - Create a variable named `year` so we aren’t passing a hardcoded value. + - Generate a new column called `PIT_STOP_SECONDS` by dividing the value of `PIT_STOP_MILLISECONDS` by 1000. + - Create our final data frame `fastest_pit_stops` that holds the records where year is equal to our year variable (2021 in this case), then group the data frame by `CONSTRUCTOR_NAME` and use the `describe()` and `sort_values()` and in descending order. This will make our first row in the new aggregated data frame the team with the fastest pit stops over an entire competition year. + - Finally, it resets the index of the `fastest_pit_stops` data frame. The `reset_index()` method allows you to reset the index back to the default 0, 1, 2, etc indexes. By default, this method will keep the "old" indexes in a column named "index"; to avoid this, use the drop parameter. Think of this as keeping your data “flat and square” as opposed to “tiered”. If you are new to Python, now might be a good time to [learn about indexes for 5 minutes](https://towardsdatascience.com/the-basics-of-indexing-and-slicing-python-lists-2d12c90a94cf) since it's the foundation of how Python retrieves, slices, and dices data. The `inplace` argument means we override the existing data frame permanently. Not to fear! This is what we want to do to avoid dealing with multi-indexed dataframes! + - Convert our Python column names to all uppercase using `.upper()`, so Snowflake recognizes them. + - Finally we are returning our dataframe with 2 decimal places for all the columns using the `round()` method. +4. Zooming out a bit, what are we doing differently here in Python from our typical SQL code: + - Method chaining is a technique in which multiple methods are called on an object in a single statement, with each method call modifying the result of the previous one. The methods are called in a chain, with the output of one method being used as the input for the next one. The technique is used to simplify the code and make it more readable by eliminating the need for intermediate variables to store the intermediate results. + - The way you see method chaining in Python is the syntax `.().()`. For example, `.describe().sort_values(by='mean')` where the `.describe()` method is chained to `.sort_values()`. + - The `.describe()` method is used to generate various summary statistics of the dataset. It's used on pandas dataframe. It gives a quick and easy way to get the summary statistics of your dataset without writing multiple lines of code. + - The `.sort_values()` method is used to sort a pandas dataframe or a series by one or multiple columns. The method sorts the data by the specified column(s) in ascending or descending order. It is the pandas equivalent to `order by` in SQL. + + We won’t go as in depth for our subsequent scripts, but will continue to explain at a high level what new libraries, functions, and methods are doing. + +5. Build the model using the UI which will **execute**: + + ```bash + dbt run --select fastest_pit_stops_by_constructor + ``` + + in the command bar. + + Let’s look at some details of our first Python model to see what our model executed. There two major differences we can see while running a Python model compared to an SQL model: + + - Our Python model was executed as a stored procedure. Snowflake needs a way to know that it's meant to execute this code in a Python runtime, instead of interpreting in a SQL runtime. We do this by creating a Python stored proc, called by a SQL command. + - The `snowflake-snowpark-python` library has been picked up to execute our Python code. Even though this wasn’t explicitly stated this is picked up by the dbt class object because we need our Snowpark package to run Python! + + Python models take a bit longer to run than SQL models, however we could always speed this up by using [Snowpark-optimized Warehouses](https://docs.snowflake.com/en/user-guide/warehouses-snowpark-optimized.html) if we wanted to. Our data is sufficiently small, so we won’t worry about creating a separate warehouse for Python versus SQL files today. + + + The rest of our **Details** output gives us information about how dbt and Snowpark for Python are working together to define class objects and apply a specific set of methods to run our models. + + So which constructor had the fastest pit stops in 2021? Let’s look at our data to find out! + +6. We can't preview Python models directly, so let’s create a new file using the **+** button or the Control-n shortcut to create a new scratchpad. +7. Reference our Python model: + + ```sql + select * from {{ ref('fastest_pit_stops_by_constructor') }} + ``` + + and preview the output: + + + Not only did Red Bull have the fastest average pit stops by nearly 40 seconds, they also had the smallest standard deviation, meaning they are both fastest and most consistent teams in pit stops. By using the `.describe()` method we were able to avoid verbose SQL requiring us to create a line of code per column and repetitively use the `PERCENTILE_COUNT()` function. + + Now we want to find the lap time average and rolling average through the years (is it generally trending up or down)? + +8. Create a new file called `lap_times_moving_avg.py` in our `aggregates` folder. +9. Copy the following code into the file: + + ```python + import pandas as pd + + def model(dbt, session): + # dbt configuration + dbt.config(packages=["pandas"]) + + # get upstream data + lap_times = dbt.ref("int_lap_times_years").to_pandas() + + # describe the data + lap_times["LAP_TIME_SECONDS"] = lap_times["LAP_TIME_MILLISECONDS"]/1000 + lap_time_trends = lap_times.groupby(by="RACE_YEAR")["LAP_TIME_SECONDS"].mean().to_frame() + lap_time_trends.reset_index(inplace=True) + lap_time_trends["LAP_MOVING_AVG_5_YEARS"] = lap_time_trends["LAP_TIME_SECONDS"].rolling(5).mean() + lap_time_trends.columns = lap_time_trends.columns.str.upper() + + return lap_time_trends.round(1) + ``` + +10. Breaking down our code a bit: + - We’re only using the `pandas` library for this model and casting it to a pandas data frame `.to_pandas()`. + - Generate a new column called `LAP_TIMES_SECONDS` by dividing the value of `LAP_TIME_MILLISECONDS` by 1000. + - Create the final dataframe. Get the lap time per year. Calculate the mean series and convert to a data frame. + - Reset the index. + - Calculate the rolling 5 year mean. + - Round our numeric columns to one decimal place. +11. Now, run this model by using the UI **Run model** or + + ```bash + dbt run --select lap_times_moving_avg + ``` + + in the command bar. + +12. Once again previewing the output of our data using the same steps for our `fastest_pit_stops_by_constructor` model. + + + We can see that it looks like lap times are getting consistently faster over time. Then in 2010 we see an increase occur! Using outside subject matter context, we know that significant rule changes were introduced to Formula 1 in 2010 and 2011 causing slower lap times. + +13. Now is a good time to checkpoint and commit our work to Git. Click **Commit and push** and give your commit a message like `aggregate python models` before moving on. + +### The dbt model, .source(), .ref() and .config() functions + +Let’s take a step back before starting machine learning to both review and go more in-depth at the methods that make running dbt python models possible. If you want to know more outside of this lab’s explanation read the documentation [here](/docs/build/python-models?version=1.3). + +- dbt model(dbt, session). For starters, each Python model lives in a .py file in your models/ folder. It defines a function named `model()`, which takes two parameters: + - dbt — A class compiled by dbt Core, unique to each model, enables you to run your Python code in the context of your dbt project and DAG. + - session — A class representing your data platform’s connection to the Python backend. The session is needed to read in tables as DataFrames and to write DataFrames back to tables. In PySpark, by convention, the SparkSession is named spark, and available globally. For consistency across platforms, we always pass it into the model function as an explicit argument called session. +- The `model()` function must return a single DataFrame. On Snowpark (Snowflake), this can be a Snowpark or pandas DataFrame. +- `.source()` and `.ref()` functions. Python models participate fully in dbt's directed acyclic graph (DAG) of transformations. If you want to read directly from a raw source table, use `dbt.source()`. We saw this in our earlier section using SQL with the source function. These functions have the same execution, but with different syntax. Use the `dbt.ref()` method within a Python model to read data from other models (SQL or Python). These methods return DataFrames pointing to the upstream source, model, seed, or snapshot. +- `.config()`. Just like SQL models, there are three ways to configure Python models: + - In a dedicated `.yml` file, within the `models/` directory + - Within the model's `.py` file, using the `dbt.config()` method + - Calling the `dbt.config()` method will set configurations for your model within your `.py` file, similar to the `{{ config() }} macro` in `.sql` model files: + + ```python + def model(dbt, session): + + # setting configuration + dbt.config(materialized="table") + ``` + - There's a limit to how complex you can get with the `dbt.config()` method. It accepts only literal values (strings, booleans, and numeric types). Passing another function or a more complex data structure is not possible. The reason is that dbt statically analyzes the arguments to `.config()` while parsing your model without executing your Python code. If you need to set a more complex configuration, we recommend you define it using the config property in a [YAML file](/reference/resource-properties/config). Learn more about configurations [here](/reference/model-configs). + +## Prepare for machine learning: cleaning, encoding, and splits + +Now that we’ve gained insights and business intelligence about Formula 1 at a descriptive level, we want to extend our capabilities into prediction. We’re going to take the scenario where we censor the data. This means that we will pretend that we will train a model using earlier data and apply it to future data. In practice, this means we’ll take data from 2010-2019 to train our model and then predict 2020 data. + +In this section, we’ll be preparing our data to predict the final race position of a driver. + +At a high level we’ll be: + +- Creating new prediction features and filtering our dataset to active drivers +- Encoding our data (algorithms like numbers) and simplifying our target variable called `position` +- Splitting our dataset into training, testing, and validation + +### ML data prep + +1. To keep our project organized, we’ll need to create two new subfolders in our `ml` directory. Under the `ml` folder, make the subfolders `prep` and `train_predict`. +2. Create a new file under `ml/prep` called `ml_data_prep`. Copy the following code into the file and **Save**. + + ```python + import pandas as pd + + def model(dbt, session): + # dbt configuration + dbt.config(packages=["pandas"]) + + # get upstream data + fct_results = dbt.ref("fct_results").to_pandas() + + # provide years so we do not hardcode dates in filter command + start_year=2010 + end_year=2020 + + # describe the data for a full decade + data = fct_results.loc[fct_results['RACE_YEAR'].between(start_year, end_year)] + + # convert string to an integer + data['POSITION'] = data['POSITION'].astype(float) + + # we cannot have nulls if we want to use total pit stops + data['TOTAL_PIT_STOPS_PER_RACE'] = data['TOTAL_PIT_STOPS_PER_RACE'].fillna(0) + + # some of the constructors changed their name over the year so replacing old names with current name + mapping = {'Force India': 'Racing Point', 'Sauber': 'Alfa Romeo', 'Lotus F1': 'Renault', 'Toro Rosso': 'AlphaTauri'} + data['CONSTRUCTOR_NAME'].replace(mapping, inplace=True) + + # create confidence metrics for drivers and constructors + dnf_by_driver = data.groupby('DRIVER').sum()['DNF_FLAG'] + driver_race_entered = data.groupby('DRIVER').count()['DNF_FLAG'] + driver_dnf_ratio = (dnf_by_driver/driver_race_entered) + driver_confidence = 1-driver_dnf_ratio + driver_confidence_dict = dict(zip(driver_confidence.index,driver_confidence)) + + dnf_by_constructor = data.groupby('CONSTRUCTOR_NAME').sum()['DNF_FLAG'] + constructor_race_entered = data.groupby('CONSTRUCTOR_NAME').count()['DNF_FLAG'] + constructor_dnf_ratio = (dnf_by_constructor/constructor_race_entered) + constructor_relaiblity = 1-constructor_dnf_ratio + constructor_relaiblity_dict = dict(zip(constructor_relaiblity.index,constructor_relaiblity)) + + data['DRIVER_CONFIDENCE'] = data['DRIVER'].apply(lambda x:driver_confidence_dict[x]) + data['CONSTRUCTOR_RELAIBLITY'] = data['CONSTRUCTOR_NAME'].apply(lambda x:constructor_relaiblity_dict[x]) + + #removing retired drivers and constructors + active_constructors = ['Renault', 'Williams', 'McLaren', 'Ferrari', 'Mercedes', + 'AlphaTauri', 'Racing Point', 'Alfa Romeo', 'Red Bull', + 'Haas F1 Team'] + active_drivers = ['Daniel Ricciardo', 'Kevin Magnussen', 'Carlos Sainz', + 'Valtteri Bottas', 'Lance Stroll', 'George Russell', + 'Lando Norris', 'Sebastian Vettel', 'Kimi Räikkönen', + 'Charles Leclerc', 'Lewis Hamilton', 'Daniil Kvyat', + 'Max Verstappen', 'Pierre Gasly', 'Alexander Albon', + 'Sergio Pérez', 'Esteban Ocon', 'Antonio Giovinazzi', + 'Romain Grosjean','Nicholas Latifi'] + + # create flags for active drivers and constructors so we can filter downstream + data['ACTIVE_DRIVER'] = data['DRIVER'].apply(lambda x: int(x in active_drivers)) + data['ACTIVE_CONSTRUCTOR'] = data['CONSTRUCTOR_NAME'].apply(lambda x: int(x in active_constructors)) + + return data + ``` + +3. As usual, let’s break down what we are doing in this Python model: + - We’re first referencing our upstream `fct_results` table and casting it to a pandas dataframe. + - Filtering on years 2010-2020 since we’ll need to clean all our data we are using for prediction (both training and testing). + - Filling in empty data for `total_pit_stops` and making a mapping active constructors and drivers to avoid erroneous predictions + - ⚠️ You might be wondering why we didn’t do this upstream in our `fct_results` table! The reason for this is that we want our machine learning cleanup to reflect the year 2020 for our predictions and give us an up-to-date team name. However, for business intelligence purposes we can keep the historical data at that point in time. Instead of thinking of one table as “one source of truth” we are creating different datasets fit for purpose: one for historical descriptions and reporting and another for relevant predictions. + - Create new confidence features for drivers and constructors + - Generate flags for the constructors and drivers that were active in 2020 +4. Execute the following in the command bar: + + ```bash + dbt run --select ml_data_prep + ``` + +5. There are more aspects we could consider for this project, such as normalizing the driver confidence by the number of races entered. Including this would help account for a driver’s history and consider whether they are a new or long-time driver. We’re going to keep it simple for now, but these are some of the ways we can expand and improve our machine learning dbt projects. Breaking down our machine learning prep model: + - Lambda functions — We use some lambda functions to transform our data without having to create a fully-fledged function using the `def` notation. So what exactly are lambda functions? + - In Python, a lambda function is a small, anonymous function defined using the keyword "lambda". Lambda functions are used to perform a quick operation, such as a mathematical calculation or a transformation on a list of elements. They are often used in conjunction with higher-order functions, such as `apply`, `map`, `filter`, and `reduce`. + - `.apply()` method — We used `.apply()` to pass our functions into our lambda expressions to the columns and perform this multiple times in our code. Let’s explain apply a little more: + - The `.apply()` function in the pandas library is used to apply a function to a specified axis of a DataFrame or a Series. In our case the function we used was our lambda function! + - The `.apply()` function takes two arguments: the first is the function to be applied, and the second is the axis along which the function should be applied. The axis can be specified as 0 for rows or 1 for columns. We are using the default value of 0 so we aren’t explicitly writing it in the code. This means that the function will be applied to each *row* of the DataFrame or Series. +6. Let’s look at the preview of our clean dataframe after running our `ml_data_prep` model: + + +### Covariate encoding + +In this next part, we’ll be performing covariate encoding. Breaking down this phrase a bit, a *covariate* is a variable that is relevant to the outcome of a study or experiment, and *encoding* refers to the process of converting data (such as text or categorical variables) into a numerical format that can be used as input for a model. This is necessary because most machine learning algorithms can only work with numerical data. Algorithms don’t speak languages, have eyes to see images, etc. so we encode our data into numbers so algorithms can perform tasks by using calculations they otherwise couldn’t. + +🧠 We’ll think about this as : “algorithms like numbers”. + +1. Create a new file under `ml/prep` called `covariate_encoding` copy the code below and save. + + ```python + import pandas as pd + import numpy as np + from sklearn.preprocessing import StandardScaler,LabelEncoder,OneHotEncoder + from sklearn.linear_model import LogisticRegression + + def model(dbt, session): + # dbt configuration + dbt.config(packages=["pandas","numpy","scikit-learn"]) + + # get upstream data + data = dbt.ref("ml_data_prep").to_pandas() + + # list out covariates we want to use in addition to outcome variable we are modeling - position + covariates = data[['RACE_YEAR','CIRCUIT_NAME','GRID','CONSTRUCTOR_NAME','DRIVER','DRIVERS_AGE_YEARS','DRIVER_CONFIDENCE','CONSTRUCTOR_RELAIBLITY','TOTAL_PIT_STOPS_PER_RACE','ACTIVE_DRIVER','ACTIVE_CONSTRUCTOR', 'POSITION']] + + # filter covariates on active drivers and constructors + # use fil_cov as short for "filtered_covariates" + fil_cov = covariates[(covariates['ACTIVE_DRIVER']==1)&(covariates['ACTIVE_CONSTRUCTOR']==1)] + + # Encode categorical variables using LabelEncoder + # TODO: we'll update this to both ohe in the future for non-ordinal variables! + le = LabelEncoder() + fil_cov['CIRCUIT_NAME'] = le.fit_transform(fil_cov['CIRCUIT_NAME']) + fil_cov['CONSTRUCTOR_NAME'] = le.fit_transform(fil_cov['CONSTRUCTOR_NAME']) + fil_cov['DRIVER'] = le.fit_transform(fil_cov['DRIVER']) + fil_cov['TOTAL_PIT_STOPS_PER_RACE'] = le.fit_transform(fil_cov['TOTAL_PIT_STOPS_PER_RACE']) + + # Simply target variable "position" to represent 3 meaningful categories in Formula1 + # 1. Podium position 2. Points for team 3. Nothing - no podium or points! + def position_index(x): + if x<4: + return 1 + if x>10: + return 3 + else : + return 2 + + # we are dropping the columns that we filtered on in addition to our training variable + encoded_data = fil_cov.drop(['ACTIVE_DRIVER','ACTIVE_CONSTRUCTOR'],1) + encoded_data['POSITION_LABEL']= encoded_data['POSITION'].apply(lambda x: position_index(x)) + encoded_data_grouped_target = encoded_data.drop(['POSITION'],1) + + return encoded_data_grouped_target + ``` + +2. Execute the following in the command bar: + + ```bash + dbt run --select covariate_encoding + ``` + +3. In this code, we are using a ton of functions from libraries! This is really cool, because we can utilize code other people have developed and bring it into our project simply by using the `import` function. [Scikit-learn](https://scikit-learn.org/stable/), “sklearn” for short, is an extremely popular data science library. Sklearn contains a wide range of machine learning techniques, including supervised and unsupervised learning algorithms, feature scaling and imputation, as well as tools model evaluation and selection. We’ll be using Sklearn for both preparing our covariates and creating models (our next section). +4. Our dataset is pretty small data so we are good to use pandas and `sklearn`. If you have larger data for your own project in mind, consider `dask` or `category_encoders`. +5. Breaking it down a bit more: + - We’re selecting a subset of variables that will be used as predictors for a driver’s position. + - Filter the dataset to only include rows using the active driver and constructor flags we created in the last step. + - The next step is to use the `LabelEncoder` from scikit-learn to convert the categorical variables `CIRCUIT_NAME`, `CONSTRUCTOR_NAME`, `DRIVER`, and `TOTAL_PIT_STOPS_PER_RACE` into numerical values. + - Create a new variable called `POSITION_LABEL`, which is a derived from our position variable. + - 💭 Why are we changing our position variable? There are 20 total positions in Formula 1 and we are grouping them together to simplify the classification and improve performance. We also want to demonstrate you can create a new function within your dbt model! + - Our new `position_label` variable has meaning: + - In Formula1 if you are in: + - Top 3 you get a “podium” position + - Top 10 you gain points that add to your overall season total + - Below top 10 you get no points! + - We are mapping our original variable position to `position_label` to the corresponding places above to 1,2, and 3 respectively. + - Drop the active driver and constructor flags since they were filter criteria and additionally drop our original position variable. + +### Splitting into training and testing datasets + +Now that we’ve cleaned and encoded our data, we are going to further split in by time. In this step, we will create dataframes to use for training and prediction. We’ll be creating two dataframes 1) using data from 2010-2019 for training, and 2) data from 2020 for new prediction inferences. We’ll create variables called `start_year` and `end_year` so we aren’t filtering on hardcasted values (and can more easily swap them out in the future if we want to retrain our model on different timeframes). + +1. Create a file called `train_test_dataset` copy and save the following code: + + ```python + import pandas as pd + + def model(dbt, session): + + # dbt configuration + dbt.config(packages=["pandas"], tags="train") + + # get upstream data + encoding = dbt.ref("covariate_encoding").to_pandas() + + # provide years so we do not hardcode dates in filter command + start_year=2010 + end_year=2019 + + # describe the data for a full decade + train_test_dataset = encoding.loc[encoding['RACE_YEAR'].between(start_year, end_year)] + + return train_test_dataset + ``` + +2. Create a file called `hold_out_dataset_for_prediction` copy and save the following code below. Now we’ll have a dataset with only the year 2020 that we’ll keep as a hold out set that we are going to use similar to a deployment use case. + + ```python + import pandas as pd + + def model(dbt, session): + # dbt configuration + dbt.config(packages=["pandas"], tags="predict") + + # get upstream data + encoding = dbt.ref("covariate_encoding").to_pandas() + + # variable for year instead of hardcoding it + year=2020 + + # filter the data based on the specified year + hold_out_dataset = encoding.loc[encoding['RACE_YEAR'] == year] + + return hold_out_dataset + ``` + +3. Execute the following in the command bar: + + ```bash + dbt run --select train_test_dataset hold_out_dataset_for_prediction + ``` + + To run our temporal data split models, we can use this syntax in the command line to run them both at once. Make sure you use a *space* [syntax](/reference/node-selection/syntax) between the model names to indicate you want to run both! +4. **Commit and push** our changes to keep saving our work as we go using `ml data prep and splits` before moving on. + +👏 Now that we’ve finished our machine learning prep work we can move onto the fun part — training and prediction! + + +## Training a model to predict in machine learning + +We’re ready to start training a model to predict the driver’s position. Now is a good time to pause and take a step back and say, usually in ML projects you’ll try multiple algorithms during development and use an evaluation method such as cross validation to determine which algorithm to use. You can definitely do this in your dbt project, but for the content of this lab we’ll have decided on using a logistic regression to predict position (we actually tried some other algorithms using cross validation outside of this lab such as k-nearest neighbors and a support vector classifier but that didn’t perform as well as the logistic regression and a decision tree that overfit). + +There are 3 areas to break down as we go since we are working at the intersection all within one model file: + +1. Machine Learning +2. Snowflake and Snowpark +3. dbt Python models + +If you haven’t seen code like this before or use joblib files to save machine learning models, we’ll be going over them at a high level and you can explore the links for more technical in-depth along the way! Because Snowflake and dbt have abstracted away a lot of the nitty gritty about serialization and storing our model object to be called again, we won’t go into too much detail here. There’s *a lot* going on here so take it at your pace! + +### Training and saving a machine learning model + +1. Project organization remains key, so let’s make a new subfolder called `train_predict` under the `ml` folder. +2. Now create a new file called `train_test_position` and copy and save the following code: + + ```python + import snowflake.snowpark.functions as F + from sklearn.model_selection import train_test_split + import pandas as pd + from sklearn.metrics import confusion_matrix, balanced_accuracy_score + import io + from sklearn.linear_model import LogisticRegression + from joblib import dump, load + import joblib + import logging + import sys + from joblib import dump, load + + logger = logging.getLogger("mylog") + + def save_file(session, model, path, dest_filename): + input_stream = io.BytesIO() + joblib.dump(model, input_stream) + session._conn.upload_stream(input_stream, path, dest_filename) + return "successfully created file: " + path + + def model(dbt, session): + dbt.config( + packages = ['numpy','scikit-learn','pandas','numpy','joblib','cachetools'], + materialized = "table", + tags = "train" + ) + # Create a stage in Snowflake to save our model file + session.sql('create or replace stage MODELSTAGE').collect() + + #session._use_scoped_temp_objects = False + version = "1.0" + logger.info('Model training version: ' + version) + + # read in our training and testing upstream dataset + test_train_df = dbt.ref("train_test_dataset") + + # cast snowpark df to pandas df + test_train_pd_df = test_train_df.to_pandas() + target_col = "POSITION_LABEL" + + # split out covariate predictors, x, from our target column position_label, y. + split_X = test_train_pd_df.drop([target_col], axis=1) + split_y = test_train_pd_df[target_col] + + # Split out our training and test data into proportions + X_train, X_test, y_train, y_test = train_test_split(split_X, split_y, train_size=0.7, random_state=42) + train = [X_train, y_train] + test = [X_test, y_test] + # now we are only training our one model to deploy + # we are keeping the focus on the workflows and not algorithms for this lab! + model = LogisticRegression() + + # fit the preprocessing pipeline and the model together + model.fit(X_train, y_train) + y_pred = model.predict_proba(X_test)[:,1] + predictions = [round(value) for value in y_pred] + balanced_accuracy = balanced_accuracy_score(y_test, predictions) + + # Save the model to a stage + save_file(session, model, "@MODELSTAGE/driver_position_"+version, "driver_position_"+version+".joblib" ) + logger.info('Model artifact:' + "@MODELSTAGE/driver_position_"+version+".joblib") + + # Take our pandas training and testing dataframes and put them back into snowpark dataframes + snowpark_train_df = session.write_pandas(pd.concat(train, axis=1, join='inner'), "train_table", auto_create_table=True, create_temp_table=True) + snowpark_test_df = session.write_pandas(pd.concat(test, axis=1, join='inner'), "test_table", auto_create_table=True, create_temp_table=True) + + # Union our training and testing data together and add a column indicating train vs test rows + return snowpark_train_df.with_column("DATASET_TYPE", F.lit("train")).union(snowpark_test_df.with_column("DATASET_TYPE", F.lit("test"))) + ``` + +3. Execute the following in the command bar: + + ```bash + dbt run --select train_test_position + ``` + +4. Breaking down our Python script here: + - We’re importing some helpful libraries. + - Defining a function called `save_file()` that takes four parameters: `session`, `model`, `path` and `dest_filename` that will save our logistic regression model file. + - `session` — an object representing a connection to Snowflake. + - `model` — an object that needs to be saved. In this case, it's a Python object that is a scikit-learn that can be serialized with joblib. + - `path` — a string representing the directory or bucket location where the file should be saved. + - `dest_filename` — a string representing the desired name of the file. + - Creating our dbt model + - Within this model we are creating a stage called `MODELSTAGE` to place our logistic regression `joblib` model file. This is really important since we need a place to keep our model to reuse and want to ensure it's there. When using Snowpark commands, it's common to see the `.collect()` method to ensure the action is performed. Think of the session as our “start” and collect as our “end” when [working with Snowpark](https://docs.snowflake.com/en/developer-guide/snowpark/python/working-with-dataframes.html) (you can use other ending methods other than collect). + - Using `.ref()` to connect into our `train_test_dataset` model. + - Now we see the machine learning part of our analysis: + - Create new dataframes for our prediction features from our target variable `position_label`. + - Split our dataset into 70% training (and 30% testing), train_size=0.7 with a `random_state` specified to have repeatable results. + - Specify our model is a logistic regression. + - Fit our model. In a logistic regression this means finding the coefficients that will give the least classification error. + - Round our predictions to the nearest integer since logistic regression creates a probability between for each class and calculate a balanced accuracy to account for imbalances in the target variable. + - Right now our model is only in memory, so we need to use our nifty function `save_file` to save our model file to our Snowflake stage. We save our model as a joblib file so Snowpark can easily call this model object back to create predictions. We really don’t need to know much else as a data practitioner unless we want to. It’s worth noting that joblib files aren’t able to be queried directly by SQL. To do this, we would need to transform the joblib file to an SQL querable format such as JSON or CSV (out of scope for this workshop). + - Finally we want to return our dataframe, but create a new column indicating what rows were used for training and those for training. +5. Viewing our output of this model: + + +6. Let’s pop back over to Snowflake and check that our logistic regression model has been stored in our `MODELSTAGE` using the command: + + ```sql + list @modelstage + ``` + + + +7. To investigate the commands run as part of `train_test_position` script, navigate to Snowflake query history to view it **Activity > Query History**. We can view the portions of query that we wrote such as `create or replace stage MODELSTAGE`, but we also see additional queries that Snowflake uses to interpret python code. + + +### Predicting on new data + +1. Create a new file called `predict_position` and copy and save the following code: + + ```python + import logging + import joblib + import pandas as pd + import os + from snowflake.snowpark import types as T + + DB_STAGE = 'MODELSTAGE' + version = '1.0' + # The name of the model file + model_file_path = 'driver_position_'+version + model_file_packaged = 'driver_position_'+version+'.joblib' + + # This is a local directory, used for storing the various artifacts locally + LOCAL_TEMP_DIR = f'/tmp/driver_position' + DOWNLOAD_DIR = os.path.join(LOCAL_TEMP_DIR, 'download') + TARGET_MODEL_DIR_PATH = os.path.join(LOCAL_TEMP_DIR, 'ml_model') + TARGET_LIB_PATH = os.path.join(LOCAL_TEMP_DIR, 'lib') + + # The feature columns that were used during model training + # and that will be used during prediction + FEATURE_COLS = [ + "RACE_YEAR" + ,"CIRCUIT_NAME" + ,"GRID" + ,"CONSTRUCTOR_NAME" + ,"DRIVER" + ,"DRIVERS_AGE_YEARS" + ,"DRIVER_CONFIDENCE" + ,"CONSTRUCTOR_RELAIBLITY" + ,"TOTAL_PIT_STOPS_PER_RACE"] + + def register_udf_for_prediction(p_predictor ,p_session ,p_dbt): + + # The prediction udf + + def predict_position(p_df: T.PandasDataFrame[int, int, int, int, + int, int, int, int, int]) -> T.PandasSeries[int]: + # Snowpark currently does not set the column name in the input dataframe + # The default col names are like 0,1,2,... Hence we need to reset the column + # names to the features that we initially used for training. + p_df.columns = [*FEATURE_COLS] + + # Perform prediction. this returns an array object + pred_array = p_predictor.predict(p_df) + # Convert to series + df_predicted = pd.Series(pred_array) + return df_predicted + + # The list of packages that will be used by UDF + udf_packages = p_dbt.config.get('packages') + + predict_position_udf = p_session.udf.register( + predict_position + ,name=f'predict_position' + ,packages = udf_packages + ) + return predict_position_udf + + def download_models_and_libs_from_stage(p_session): + p_session.file.get(f'@{DB_STAGE}/{model_file_path}/{model_file_packaged}', DOWNLOAD_DIR) + + def load_model(p_session): + # Load the model and initialize the predictor + model_fl_path = os.path.join(DOWNLOAD_DIR, model_file_packaged) + predictor = joblib.load(model_fl_path) + return predictor + + # ------------------------------- + def model(dbt, session): + dbt.config( + packages = ['snowflake-snowpark-python' ,'scipy','scikit-learn' ,'pandas' ,'numpy'], + materialized = "table", + tags = "predict" + ) + session._use_scoped_temp_objects = False + download_models_and_libs_from_stage(session) + predictor = load_model(session) + predict_position_udf = register_udf_for_prediction(predictor, session ,dbt) + + # Retrieve the data, and perform the prediction + hold_out_df = (dbt.ref("hold_out_dataset_for_prediction") + .select(*FEATURE_COLS) + ) + + # Perform prediction. + new_predictions_df = hold_out_df.withColumn("position_predicted" + ,predict_position_udf(*FEATURE_COLS) + ) + + return new_predictions_df + ``` + +2. Execute the following in the command bar: + + ```bash + dbt run --select predict_position + ``` + +3. **Commit and push** our changes to keep saving our work as we go using the commit message `logistic regression model training and application` before moving on. +4. At a high level in this script, we are: + - Retrieving our staged logistic regression model + - Loading the model in + - Placing the model within a user defined function (UDF) to call in line predictions on our driver’s position +5. At a more detailed level: + - Import our libraries. + - Create variables to reference back to the `MODELSTAGE` we just created and stored our model to. + - The temporary file paths we created might look intimidating, but all we’re doing here is programmatically using an initial file path and adding to it to create the following directories: + - LOCAL_TEMP_DIR ➡️ /tmp/driver_position + - DOWNLOAD_DIR ➡️ /tmp/driver_position/download + - TARGET_MODEL_DIR_PATH ➡️ /tmp/driver_position/ml_model + - TARGET_LIB_PATH ➡️ /tmp/driver_position/lib + - Provide a list of our feature columns that we used for model training and will now be used on new data for prediction. + - Next, we are creating our main function `register_udf_for_prediction(p_predictor ,p_session ,p_dbt):`. This function is used to register a user-defined function (UDF) that performs the machine learning prediction. It takes three parameters: `p_predictor` is an instance of the machine learning model, `p_session` is an instance of the Snowflake session, and `p_dbt` is an instance of the dbt library. The function creates a UDF named `predict_churn` which takes a pandas dataframe with the input features and returns a pandas series with the predictions. + - ⚠️ Pay close attention to the whitespace here. We are using a function within a function for this script. + - We have 2 simple functions that are programmatically retrieving our file paths to first get our stored model out of our `MODELSTAGE` and downloaded into the session `download_models_and_libs_from_stage` and then to load the contents of our model in (parameters) in `load_model` to use for prediction. + - Take the model we loaded in and call it `predictor` and wrap it in a UDF. + - Return our dataframe with both the features used to predict and the new label. + +🧠 Another way to read this script is from the bottom up. This can help us progressively see what is going into our final dbt model and work backwards to see how the other functions are being referenced. + +6. Let’s take a look at our predicted position alongside our feature variables. Open a new scratchpad and use the following query. I chose to order by the prediction of who would obtain a podium position: + + ```sql + select * from {{ ref('predict_position') }} order by position_predicted + ``` + +7. We can see that we created predictions in our final dataset, we are ready to move on to testing! + +## Test your data models + +We have now completed building all the models for today’s lab, but how do we know if they meet our assertions? Put another way, how do we know the quality of our data models are any good? This brings us to testing! + +We test data models for mainly two reasons: + +- Ensure that our source data is clean on ingestion before we start data modeling/transformation (aka avoid garbage in, garbage out problem). +- Make sure we don’t introduce bugs in the transformation code we wrote (stop ourselves from creating bad joins/fanouts). + +Testing in dbt comes in two flavors: [generic](/docs/build/tests#generic-tests) and [singular](/docs/build/tests#singular-tests). + +You define them in a test block (similar to a macro) and once defined, you can reference them by name in your `.yml` files (applying them to models, columns, sources, snapshots, and seeds). + +You might be wondering: *what about testing Python models?* + +Since the output of our Python models are tables, we can test SQL and Python models the same way! We don’t have to worry about any syntax differences when testing SQL versus Python data models. This means we use `.yml` and `.sql` files to test our entities (tables, views, etc.). Under the hood, dbt is running an SQL query on our tables to see if they meet assertions. If no rows are returned, dbt will surface a passed test. Conversely, if a test results in returned rows, it will fail or warn depending on the configuration (more on that later). + +### Generic tests + +1. To implement generic out-of-the-box tests dbt comes with, we can use YAML files to specify information about our models. To add generic tests to our aggregates model, create a file called `aggregates.yml`, copy the code block below into the file, and save. + + + ```yaml + version: 2 + + models: + - name: fastest_pit_stops_by_constructor + description: Use the python .describe() method to retrieve summary statistics table about pit stops by constructor. Sort by average stop time ascending so the first row returns the fastest constructor. + columns: + - name: constructor_name + description: team that makes the car + tests: + - unique + + - name: lap_times_moving_avg + description: Use the python .rolling() method to calculate the 5 year rolling average of pit stop times alongside the average for each year. + columns: + - name: race_year + description: year of the race + tests: + - relationships: + to: ref('int_lap_times_years') + field: race_year + ``` + +2. Let’s unpack the code we have here. We have both our aggregates models with the model name to know the object we are referencing and the description of the model that we’ll populate in our documentation. At the column level (a level below our model), we are providing the column name followed by our tests. We want to ensure our `constructor_name` is unique since we used a pandas `groupby` on `constructor_name` in the model `fastest_pit_stops_by_constructor`. Next, we want to ensure our `race_year` has referential integrity from the model we selected from `int_lap_times_years` into our subsequent `lap_times_moving_avg` model. +3. Finally, if we want to see how tests were deployed on sources and SQL models, we can look at other files in our project such as the `f1_sources.yml` we created in our Sources and staging section. + +### Using macros for testing + +1. Under your `macros` folder, create a new file and name it `test_all_values_gte_zero.sql`. Copy the code block below and save the file. For clarity, “gte” is an abbreviation for greater than or equal to. + + + ```sql + {% macro test_all_values_gte_zero(table, column) %} + + select * from {{ ref(table) }} where {{ column }} < 0 + + {% endmacro %} + ``` + +2. Macros in Jinja are pieces of code that can be reused multiple times in our SQL models — they are analogous to "functions" in other programming languages, and are extremely useful if you find yourself repeating code across multiple models. +3. We use the `{% macro %}` to indicate the start of the macro and `{% endmacro %}` for the end. The text after the beginning of the macro block is the name we are giving the macro to later call it. In this case, our macro is called `test_all_values_gte_zero`. Macros take in *arguments* to pass through, in this case the `table` and the `column`. In the body of the macro, we see an SQL statement that is using the `ref` function to dynamically select the table and then the column. You can always view macros without having to run them by using `dbt run-operation`. You can learn more [here](https://docs.getdbt.com/reference/commands/run-operation). +4. Great, now we want to reference this macro as a test! Let’s create a new test file called `macro_pit_stops_mean_is_positive.sql` in our `tests` folder. + + + +5. Copy the following code into the file and save: + + ```sql + {{ + config( + enabled=true, + severity='warn', + tags = ['bi'] + ) + }} + + {{ test_all_values_gte_zero('fastest_pit_stops_by_constructor', 'mean') }} + ``` + +6. In our testing file, we are applying some configurations to the test including `enabled`, which is an optional configuration for disabling models, seeds, snapshots, and tests. Our severity is set to `warn` instead of `error`, which means our pipeline will still continue to run. We have tagged our test with `bi` since we are applying this test to one of our bi models. + +Then, in our final line, we are calling the `test_all_values_gte_zero` macro that takes in our table and column arguments and inputting our table `'fastest_pit_stops_by_constructor'` and the column `'mean'`. + +### Custom singular tests to validate Python models + +The simplest way to define a test is by writing the exact SQL that will return failing records. We call these "singular" tests, because they're one-off assertions usable for a single purpose. + +These tests are defined in `.sql` files, typically in your `tests` directory (as defined by your test-paths config). You can use Jinja in SQL models (including ref and source) in the test definition, just like you can when creating models. Each `.sql` file contains one select statement, and it defines one test. + +Let’s add a custom test that asserts that the moving average of the lap time over the last 5 years is greater than zero (it’s impossible to have time less than 0!). It is easy to assume if this is not the case the data has been corrupted. + +1. Create a file `lap_times_moving_avg_assert_positive_or_null.sql` under the `tests` folder. + + +2. Copy the following code and save the file: + + ```sql + {{ + config( + enabled=true, + severity='error', + tags = ['bi'] + ) + }} + + with lap_times_moving_avg as ( select * from {{ ref('lap_times_moving_avg') }} ) + + select * + from lap_times_moving_avg + where lap_moving_avg_5_years < 0 and lap_moving_avg_5_years is not null + ``` + +### Putting all our tests together + +1. Time to run our tests! Altogether, we have created 4 tests for our 2 Python models: + - `fastest_pit_stops_by_constructor` + - Unique `constructor_name` + - Lap times are greater than 0 or null (to allow for the first leading values in a rolling calculation) + - `lap_times_moving_avg` + - Referential test on `race_year` + - Mean pit stop times are greater than or equal to 0 (no negative time values) +2. To run the tests on both our models, we can use this syntax in the command line to run them both at once, similar to how we did our data splits earlier. + Execute the following in the command bar: + + ```bash + dbt test --select fastest_pit_stops_by_constructor lap_times_moving_avg + ``` + + + +3. All 4 of our tests passed (yay for clean data)! To understand the SQL being run against each of our tables, we can click into the details of the test. +4. Navigating into the **Details** of the `unique_fastest_pit_stops_by_constructor_name`, we can see that each line `constructor_name` should only have one row. + + +## Document your dbt project + +When it comes to documentation, dbt brings together both column and model level descriptions that you can provide as well as details from your Snowflake information schema in a static site for consumption by other data team members and stakeholders. + +We are going to revisit 2 areas of our project to understand our documentation: + +- `intermediate.md` file +- `dbt_project.yml` file + +To start, let’s look back at our `intermediate.md` file. We can see that we provided multi-line descriptions for the models in our intermediate models using [docs blocks](/docs/collaborate/documentation#using-docs-blocks). Then we reference these docs blocks in our `.yml` file. Building descriptions with doc blocks in Markdown files gives you the ability to format your descriptions with Markdown and are particularly helpful when building long descriptions, either at the column or model level. In our `dbt_project.yml`, we added `node_colors` at folder levels. + +1. To see all these pieces come together, execute this in the command bar: + + ```bash + dbt docs generate + ``` + + This will generate the documentation for your project. Click the book button, as shown in the screenshot below to access the docs. + + +2. Go to our project area and view `int_results`. View the description that we created in our doc block. + + +3. View the mini-lineage that looks at the model we are currently selected on (`int_results` in this case). + + +4. In our `dbt_project.yml`, we configured `node_colors` depending on the file directory. Starting in dbt v1.3, we can see how our lineage in our docs looks. By color coding your project, it can help you cluster together similar models or steps and more easily troubleshoot. + + + +## Deploy your code + +Before we jump into deploying our code, let's have a quick primer on environments. Up to this point, all of the work we've done in the dbt Cloud IDE has been in our development environment, with code committed to a feature branch and the models we've built created in our development schema in Snowflake as defined in our Development environment connection. Doing this work on a feature branch, allows us to separate our code from what other coworkers are building and code that is already deemed production ready. Building models in a development schema in Snowflake allows us to separate the database objects we might still be modifying and testing from the database objects running production dashboards or other downstream dependencies. Together, the combination of a Git branch and Snowflake database objects form our environment. + +Now that we've completed testing and documenting our work, we're ready to deploy our code from our development environment to our production environment and this involves two steps: + +- Promoting code from our feature branch to the production branch in our repository. + - Generally, the production branch is going to be named your main branch and there's a review process to go through before merging code to the main branch of a repository. Here we are going to merge without review for ease of this workshop. +- Deploying code to our production environment. + - Once our code is merged to the main branch, we'll need to run dbt in our production environment to build all of our models and run all of our tests. This will allow us to build production-ready objects into our production environment in Snowflake. Luckily for us, the Partner Connect flow has already created our deployment environment and job to facilitate this step. + +1. Before getting started, let's make sure that we've committed all of our work to our feature branch. If you still have work to commit, you'll be able to select the **Commit and push**, provide a message, and then select **Commit** again. +2. Once all of your work is committed, the git workflow button will now appear as **Merge to main**. Select **Merge to main** and the merge process will automatically run in the background. + + +3. When it's completed, you should see the git button read **Create branch** and the branch you're currently looking at will become **main**. +4. Now that all of our development work has been merged to the main branch, we can build our deployment job. Given that our production environment and production job were created automatically for us through Partner Connect, all we need to do here is update some default configurations to meet our needs. +5. In the menu, select **Deploy** **> Environments** + + +6. You should see two environments listed and you'll want to select the **Deployment** environment then **Settings** to modify it. +7. Before making any changes, let's touch on what is defined within this environment. The Snowflake connection shows the credentials that dbt Cloud is using for this environment and in our case they are the same as what was created for us through Partner Connect. Our deployment job will build in our `PC_DBT_DB` database and use the default Partner Connect role and warehouse to do so. The deployment credentials section also uses the info that was created in our Partner Connect job to create the credential connection. However, it is using the same default schema that we've been using as the schema for our development environment. +8. Let's update the schema to create a new schema specifically for our production environment. Click **Edit** to allow you to modify the existing field values. Navigate to **Deployment Credentials >** **schema.** +9. Update the schema name to **production**. Remember to select **Save** after you've made the change. + +10. By updating the schema for our production environment to **production**, it ensures that our deployment job for this environment will build our dbt models in the **production** schema within the `PC_DBT_DB` database as defined in the Snowflake Connection section. +11. Now let's switch over to our production job. Click on the deploy tab again and then select **Jobs**. You should see an existing and preconfigured **Partner Connect Trial Job**. Similar to the environment, click on the job, then select **Settings** to modify it. Let's take a look at the job to understand it before making changes. + + - The Environment section is what connects this job with the environment we want it to run in. This job is already defaulted to use the Deployment environment that we just updated and the rest of the settings we can keep as is. + - The Execution settings section gives us the option to generate docs, run source freshness, and defer to a previous run state. For the purposes of our lab, we're going to keep these settings as is as well and stick with just generating docs. + - The Commands section is where we specify exactly which commands we want to run during this job, and we also want to keep this as is. We want our seed to be uploaded first, then run our models, and finally test them. The order of this is important as well, considering that we need our seed to be created before we can run our incremental model, and we need our models to be created before we can test them. + - Finally, we have the Triggers section, where we have a number of different options for scheduling our job. Given that our data isn't updating regularly here and we're running this job manually for now, we're also going to leave this section alone. + + So, what are we changing then? Just the name! Click **Edit** to allow you to make changes. Then update the name of the job to **Production Job** to denote this as our production deployment job. After that's done, click **Save**. +12. Now let's go to run our job. Clicking on the job name in the path at the top of the screen will take you back to the job run history page where you'll be able to click **Run run** to kick off the job. If you encounter any job failures, try running the job again before further troubleshooting. + + + +13. Let's go over to Snowflake to confirm that everything built as expected in our production schema. Refresh the database objects in your Snowflake account and you should see the production schema now within our default Partner Connect database. If you click into the schema and everything ran successfully, you should be able to see all of the models we developed. + + +### Conclusion + +Fantastic! You’ve finished the workshop! We hope you feel empowered in using both SQL and Python in your dbt Cloud workflows with Snowflake. Having a reliable pipeline to surface both analytics and machine learning is crucial to creating tangible business value from your data. + +For more help and information join our [dbt community Slack](https://www.getdbt.com/community/) which contains more than 50,000 data practitioners today. We have a dedicated slack channel #db-snowflake to Snowflake related content. Happy dbt'ing! diff --git a/website/docs/guides/dbt-ecosystem/sl-partner-integration-guide.md b/website/docs/guides/sl-partner-integration-guide.md similarity index 97% rename from website/docs/guides/dbt-ecosystem/sl-partner-integration-guide.md rename to website/docs/guides/sl-partner-integration-guide.md index 936a54465e8..41a5135bb7d 100644 --- a/website/docs/guides/dbt-ecosystem/sl-partner-integration-guide.md +++ b/website/docs/guides/sl-partner-integration-guide.md @@ -2,8 +2,17 @@ title: "dbt Semantic Layer integration best practices" id: "sl-partner-integration-guide" description: Learn about partner integration guidelines, roadmap, and connectivity. +hoverSnippet: Learn how to +# time_to_complete: '30 minutes' commenting out until we test +icon: 'guides' +hide_table_of_contents: true +tags: ['Semantic layer'] +level: 'Advanced' +recently_updated: true --- +## Introduction + To fit your tool within the world of the Semantic Layer, dbt Labs offers some best practice recommendations for how to expose metrics and allow users to interact with them seamlessly. :::note @@ -11,7 +20,7 @@ This is an evolving guide that is meant to provide recommendations based on our ::: -## Requirements +### Prerequisites To build a dbt Semantic Layer integration: @@ -37,7 +46,7 @@ When building an integration, we recommend you expose certain metadata in the re - The version of dbt they are on. -## Best practices on exposing metrics +## Use best practices when exposing metrics Best practices for exposing metrics are summarized into five themes: @@ -121,7 +130,7 @@ For transparency and additional context, we recommend you have an easy way for t In the cases where our APIs support either a string or a filter list for the `where` clause, we always recommend that your application utilizes the filter list in order to gain maximum pushdown benefits. The `where` string may be more intuitive for users writing queries during testing, but it will not have the performance benefits of the filter list in a production environment. -## Example stages of an integration +## Understand stages of an integration These are recommendations on how to evolve a Semantic Layer integration and not a strict runbook. @@ -149,7 +158,7 @@ These are recommendations on how to evolve a Semantic Layer integration and not * Suggest metrics to users based on teams/identity, and so on. -## Related docs +### Related docs - [Use the dbt Semantic Layer](/docs/use-dbt-semantic-layer/dbt-sl) to learn about the product. - [Build your metrics](/docs/build/build-metrics-intro) for more info about MetricFlow and its components. From cef4f21f834a4d78054bd862d2664bbd9bdb3747 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:33:40 -0800 Subject: [PATCH 061/152] moving and deleting adapter stuff --- .../create-adapters.md | 291 +++++++++++++++++- .../2-prerequisites-for-a-new-adapter.md | 6 - .../3-building-a-new-adapter.md | 5 - .../4-testing-a-new-adapter.md | 5 - .../5-documenting-a-new-adapter.md | 60 ---- .../6-promoting-a-new-adapter.md | 120 -------- .../7-verifying-a-new-adapter.md | 41 --- .../8-building-a-trusted-adapter.md | 79 ----- 8 files changed, 289 insertions(+), 318 deletions(-) rename website/docs/guides/{dbt-ecosystem/adapter-development => }/create-adapters.md (72%) delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter.md delete mode 100644 website/docs/guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter.md diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md b/website/docs/guides/create-adapters.md similarity index 72% rename from website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md rename to website/docs/guides/create-adapters.md index 269a9e1ff6a..deb2764edc6 100644 --- a/website/docs/guides/dbt-ecosystem/adapter-development/create-adapters.md +++ b/website/docs/guides/create-adapters.md @@ -1,6 +1,7 @@ --- title: "Build, test, document, and promote adapters" id: "adapter-creation" +description: hoverSnippet: Learn how to # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' @@ -159,7 +160,7 @@ We strongly encourage you to adopt the following approach when versioning and re - While your plugin is new, and you're iterating on features, aim to offer backwards compatibility and deprecation notices for at least one minor version. As your plugin matures, aim to leave backwards compatibility and deprecation notices in place until the next major version (dbt Core v2). - Release patch versions of your plugins whenever needed. These patch releases should contain fixes _only_. -## Building a new adapter +## Build a new adapter This step will walk you through the first creating the necessary adapter classes and macros, and provide some resources to help you validate that your new adapter is working correctly. Make sure you've familiarized yourself with the previous steps in this guide. @@ -564,7 +565,7 @@ To assure that `dbt --version` provides the latest dbt core version the adapter It should be noted that both of these files are included in the bootstrapped output of the `dbt-database-adapter-scaffold` so when using the scaffolding, these files will be included. -## Testing your adapter +## Test your adapter :::info @@ -1064,3 +1065,289 @@ Finally: python3 -m pytest tests/functional --profile apache_spark python3 -m pytest tests/functional --profile databricks_sql_endpoint ``` + +## Document a new adapter + +If you've already [built](3-building-a-new-adapter), and [tested](4-testing-a-new-adapter) your adapter, it's time to document it so the dbt community will know that it exists and how to use it. + +### Making your adapter available + +Many community members maintain their adapter plugins under open source licenses. If you're interested in doing this, we recommend: + +- Hosting on a public git provider (for example, GitHub or Gitlab) +- Publishing to [PyPI](https://pypi.org/) +- Adding to the list of ["Supported Data Platforms"](/docs/supported-data-platforms#community-supported) (more info below) + +### General Guidelines + +To best inform the dbt community of the new adapter, you should contribute to the dbt's open-source documentation site, which uses the [Docusaurus project](https://docusaurus.io/). This is the site you're currently on! + +### Conventions + +Each `.md` file you create needs a header as shown below. The document id will also need to be added to the config file: `website/sidebars.js`. + +```md +--- +title: "Documenting a new adapter" +id: "documenting-a-new-adapter" +--- +``` + +### Single Source of Truth + +We ask our adapter maintainers to use the [docs.getdbt.com repo](https://github.com/dbt-labs/docs.getdbt.com) (i.e. this site) as the single-source-of-truth for documentation rather than having to maintain the same set of information in three different places. The adapter repo's `README.md` and the data platform's documentation pages should simply link to the corresponding page on this docs site. Keep reading for more information on what should and shouldn't be included on the dbt docs site. + +### Assumed Knowledge + +To simplify things, assume the reader of this documentation already knows how both dbt and your data platform works. There's already great material for how to learn dbt and the data platform out there. The documentation we're asking you to add should be what a user who is already profiecient in both dbt and your data platform would need to know in order to use both. Effectively that boils down to two things: how to connect, and how to configure. + +### Topics and Pages to Cover + +The following subjects need to be addressed across three pages of this docs site to have your data platform be listed on our documentation. After the corresponding pull request is merged, we ask that you link to these pages from your adapter repo's `REAMDE` as well as from your product documentation. + + To contribute, all you will have to do make the changes listed in the table below. + +| How To... | File to change within `/website/docs/` | Action | Info to Include | +|----------------------|--------------------------------------------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Connect | `/docs/core/connect-data-platform/{MY-DATA-PLATFORM}-setup.md` | Create | Give all information needed to define a target in `~/.dbt/profiles.yml` and get `dbt debug` to connect to the database successfully. All possible configurations should be mentioned. | +| Configure | `reference/resource-configs/{MY-DATA-PLATFORM}-configs.md` | Create | What options and configuration specific to your data platform do users need to know? e.g. table distribution and indexing options, column_quoting policy, which incremental strategies are supported | +| Discover and Install | `docs/supported-data-platforms.md` | Modify | Is it a vendor- or community- supported adapter? How to install Python adapter package? Ideally with pip and PyPI hosted package, but can also use `git+` link to GitHub Repo | +| Add link to sidebar | `website/sidebars.js` | Modify | Add the document id to the correct location in the sidebar menu | + +For example say I want to document my new adapter: `dbt-ders`. For the "Connect" page, I will make a new Markdown file, `ders-setup.md` and add it to the `/website/docs/core/connect-data-platform/` directory. + +### Example PRs to add new adapter documentation + +Below are some recent pull requests made by partners to document their data platform's adapter: + +- [TiDB](https://github.com/dbt-labs/docs.getdbt.com/pull/1309) +- [SingleStore](https://github.com/dbt-labs/docs.getdbt.com/pull/1044) +- [Firebolt](https://github.com/dbt-labs/docs.getdbt.com/pull/941) + +## Promote a new adapter + +The most important thing here is recognizing that people are successful in the community when they join, first and foremost, to engage authentically. + +What does authentic engagement look like? It’s challenging to define explicit rules. One good rule of thumb is to treat people with dignity and respect. + +Contributors to the community should think of contribution _as the end itself,_ not a means toward other business KPIs (leads, community members, etc.). [We are a mission-driven company.](https://www.getdbt.com/dbt-labs/values/) Some ways to know if you’re authentically engaging: + +- Is an engagement’s _primary_ purpose of sharing knowledge and resources or building brand engagement? +- Imagine you didn’t work at the org you do — can you imagine yourself still writing this? +- Is it written in formal / marketing language, or does it sound like you, the human? + +### Who should join the dbt community slack? + +- People who have insight into what it means to do hands-on [analytics engineering](https://www.getdbt.com/analytics-engineering/) work + The dbt Community Slack workspace is fundamentally a place for analytics practitioners to interact with each other — the closer the users are in the community to actual data/analytics engineering work, the more natural their engagement will be (leading to better outcomes for partners and the community). + +- DevRel practitioners with strong focus + DevRel practitioners often have a strong analytics background and a good understanding of the community. It’s essential to be sure they are focused on _contributing,_ not on driving community metrics for partner org (such as signing people up for their slack or events). The metrics will rise naturally through authentic engagement. + +- Founder and executives who are interested in directly engaging with the community + This is either incredibly successful or not at all depending on the profile of the founder. Typically, this works best when the founder has a practitioner-level of technical understanding and is interested in joining not to promote, but to learn and hear from users. + +- Software Engineers at partner products that are building and supporting integrations with either dbt Core or dbt Cloud + This is successful when the engineers are familiar with dbt as a product or at least have taken our training course. The Slack is often a place where end-user questions and feedback is initially shared, so it is recommended that someone technical from the team be present. There are also a handful of channels aimed at those building integrations, which tend to be a font of knowledge. + +### Who might struggle in the dbt community + +- People in marketing roles + dbt Slack is not a marketing channel. Attempts to use it as such invariably fall flat and can even lead to people having a negative view of a product. This doesn’t mean that dbt can’t serve marketing objectives, but a long-term commitment to engagement is the only proven method to do this sustainably. + +- People in product roles + The dbt Community can be an invaluable source of feedback on a product. There are two primary ways this can happen — organically (community members proactively suggesting a new feature) and via direct calls for feedback and user research. Immediate calls for engagement must be done in your dedicated #tools channel. Direct calls should be used sparingly, as they can overwhelm more organic discussions and feedback. + +### Who is the audience for an adapter release? + + A new adapter is likely to drive huge community interest from several groups of people: + - People who are currently using the database that the adapter is supporting + - People who may be adopting the database in the near future. + - People who are interested in dbt development in general. + +The database users will be your primary audience and the most helpful in achieving success. Engage them directly in the adapter’s dedicated Slack channel. If one does not exist already, reach out in #channel-requests, and we will get one made for you and include it in an announcement about new channels. + +The final group is where non-slack community engagement becomes important. Twitter and LinkedIn are both great places to interact with a broad audience. A well-orchestrated adapter release can generate impactful and authentic engagement. + +### How to message the initial rollout and follow-up content + +Tell a story that engages dbt users and the community. Highlight new use cases and functionality unlocked by the adapter in a way that will resonate with each segment. + +- Existing users of your technology who are new to dbt + - Provide a general overview of the value dbt will deliver to your users. This can lean on dbt's messaging and talking points which are laid out in the [dbt viewpoint.](/community/resources/viewpoint) + - Give examples of a rollout that speaks to the overall value of dbt and your product. + +- Users who are already familiar with dbt and the community + - Consider unique use cases or advantages your adapter provide over existing adapters. Who will be excited for this? + - Contribute to the dbt Community and ensure that dbt users on your adapter are well supported (tutorial content, packages, documentation, etc). + - Example of a rollout that is compelling for those familiar with dbt: [Firebolt](https://www.linkedin.com/feed/update/urn:li:activity:6879090752459182080/) + +### Tactically manage distribution of content about new or existing adapters + +There are tactical pieces on how and where to share that help ensure success. + +- On slack: + - #i-made-this channel — this channel has a policy against “marketing” and “content marketing” posts, but it should be successful if you write your content with the above guidelines in mind. Even with that, it’s important to post here sparingly. + - Your own database / tool channel — this is where the people who have opted in to receive communications from you and always a great place to share things that are relevant to them. + +- On social media: + - Twitter + - LinkedIn + - Social media posts _from the author_ or an individual connected to the project tend to have better engagement than posts from a company or organization account. + - Ask your partner representative about: + - Retweets and shares from the official dbt Labs accounts. + - Flagging posts internally at dbt Labs to get individual employees to share. + +#### Measuring engagement + +You don’t need 1000 people in a channel to succeed, but you need at least a few active participants who can make it feel lived in. If you’re comfortable working in public, this could be members of your team, or it can be a few people who you know that are highly engaged and would be interested in participating. Having even 2 or 3 regulars hanging out in a channel is all that’s needed for a successful start and is, in fact, much more impactful than 250 people that never post. + +### How to announce a new adapter + +We’d recommend _against_ boilerplate announcements and encourage finding a unique voice. That being said, there are a couple of things that we’d want to include: + +- A summary of the value prop of your database / technology for users who aren’t familiar. +- The personas that might be interested in this news. +- A description of what the adapter _is_. For example: + > With the release of our new dbt adapter, you’ll be able to to use dbt to model and transform your data in [name-of-your-org] +- Particular or unique use cases or functionality unlocked by the adapter. +- Plans for future / ongoing support / development. +- The link to the documentation for using the adapter on the dbt Labs docs site. +- An announcement blog. + +#### Announcing new release versions of existing adapters + +This can vary substantially depending on the nature of the release but a good baseline is the types of release messages that [we put out in the #dbt-releases](https://getdbt.slack.com/archives/C37J8BQEL/p1651242161526509) channel. + +![Full Release Post](/img/adapter-guide/0-full-release-notes.png) + +Breaking this down: + +- Visually distinctive announcement - make it clear this is a release + +- Short written description of what is in the release + +- Links to additional resources + +- Implementation instructions: + +- Future plans + +- Contributor recognition (if applicable) + + + +## Verify a new adapter + +The very first data platform dbt supported was Redshift followed quickly by Postgres (([dbt-core#174](https://github.com/dbt-labs/dbt-core/pull/174)). In 2017, back when dbt Labs (née Fishtown Analytics) was still a data consultancy, we added support for Snowflake and BigQuery. We also turned dbt's database support into an adapter framework ([dbt-core#259](https://github.com/dbt-labs/dbt-core/pull/259/)), and a plugin system a few years later. For years, dbt Labs specialized in those four data platforms and became experts in them. However, the surface area of all possible databases, their respective nuances, and keeping them up-to-date and bug-free is a Herculean and/or Sisyphean task that couldn't be done by a single person or even a single team! Enter the dbt community which enables dbt Core to work on more than 30 different databases (32 as of Sep '22)! + +Free and open-source tools for the data professional are increasingly abundant. This is by-and-large a _good thing_, however it requires due dilligence that wasn't required in a paid-license, closed-source software world. Before taking a dependency on an open-source projet is is important to determine the answer to the following questions: + +1. Does it work? +2. Does it meet my team's specific use case? +3. Does anyone "own" the code, or is anyone liable for ensuring it works? +4. Do bugs get fixed quickly? +5. Does it stay up-to-date with new Core features? +6. Is the usage substantial enough to self-sustain? +7. What risks do I take on by taking a dependency on this library? + +These are valid, important questions to answer—especially given that `dbt-core` itself only put out its first stable release (major version v1.0) in December 2021! Indeed, up until now, the majority of new user questions in database-specific channels are some form of: + +- "How mature is `dbt-`? Any gotchas I should be aware of before I start exploring?" +- "has anyone here used `dbt-` for production models?" +- "I've been playing with `dbt-` -- I was able to install and run my initial experiments. I noticed that there are certain features mentioned on the documentation that are marked as 'not ok' or 'not tested'. What are the risks? +I'd love to make a statement on my team to adopt DBT [sic], but I'm pretty sure questions will be asked around the possible limitations of the adapter or if there are other companies out there using dbt [sic] with Oracle DB in production, etc." + +There has been a tendency to trust the dbt Labs-maintained adapters over community- and vendor-supported adapters, but repo ownership is only one among many indicators of software quality. We aim to help our users feel well-informed as to the caliber of an adapter with a new program. + +### Verified by dbt Labs + +The adapter verification program aims to quickly indicate to users which adapters can be trusted to use in production. Previously, doing so was uncharted territory for new users and complicated making the business case to their leadership team. We plan to give quality assurances by: + +1. appointing a key stakeholder for the adapter repository, +2. ensuring that the chosen stakeholder fixes bugs and cuts new releases in a timely manner see maintainer your adapter (["Maintaining your new adapter"](2-prerequisites-for-a-new-adapter#maintaining-your-new-adapter)), +3. demonstrating that it passes our adapter pytest suite tests, +4. assuring that it works for us internally and ideally an existing team using the adapter in production . + +Every major & minor version of a adapter will be verified internally and given an official :white_check_mark: (custom emoji coming soon), on the ["Supported Data Platforms"](/docs/supported-data-platforms) page. + +### How to get an adapter verified? + +We envision that data platform vendors will be most interested in having their adapter versions verified, however we are open to community adapter verification. If interested, please reach out either to the `partnerships` at `dbtlabs.com` or post in the [#adapter-ecosystem Slack channel](https://getdbt.slack.com/archives/C030A0UF5LM). + +## Build a trusted adapter + +The Trusted adapter program exists to allow adapter maintainers to demonstrate to the dbt community that your adapter is trusted to be used in production. + +### What it means to be trusted + +By opting into the below, you agree to this, and we take you at your word. dbt Labs reserves the right to remove an adapter from the trusted adapter list at any time, should any of the below guidelines not be met. + +### Feature Completeness + +To be considered for the Trusted Adapter program, the adapter must cover the essential functionality of dbt Core given below, with best effort given to support the entire feature set. + +Essential functionality includes (but is not limited to the following features): + +- table, view, and seed materializations +- dbt tests + +The adapter should have the required documentation for connecting and configuring the adapter. The dbt docs site should be the single source of truth for this information. These docs should be kept up-to-date. + +See [Documenting a new adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) for more information. + +### Release Cadence + +Keeping an adapter up-to-date with dbt Core is an integral part of being a trusted adapter. Therefore, we ask that adapter maintainers: + +- Release of new minor versions of the adapter with all tests passing within four weeks of dbt Core's release cut. +- Release of new major versions of the adapter with all tests passing within eight weeks of dbt Core's release cut. + +### Community Responsiveness + +On a best effort basis, active participation and engagement with the dbt Community across the following forums: + +- Being responsive to feedback and supporting user enablement in dbt Community’s Slack workspace +- Responding with comments to issues raised in public dbt adapter code repository +- Merging in code contributions from community members as deemed appropriate + +### Security Practices + +Trusted adapters will not do any of the following: + +- Output to logs or file either access credentials information to or data from the underlying data platform itself. +- Make API calls other than those expressly required for using dbt features (adapters may not add additional logging) +- Obfuscate code and/or functionality so as to avoid detection + +Additionally, to avoid supply-chain attacks: + +- Use an automated service to keep Python dependencies up-to-date (such as Dependabot or similar), +- Publish directly to PyPI from the dbt adapter code repository by using trusted CI/CD process (such as GitHub actions) +- Restrict admin access to both the respective code (GitHub) and package (PyPI) repositories +- Identify and mitigate security vulnerabilities by use of a static code analyzing tool (such as Snyk) as part of a CI/CD process + +### Other considerations + +The adapter repository is: + +- open-souce licensed, +- published to PyPI, and +- automatically tests the codebase against dbt Lab's provided adapter test suite + +### How to get an adapter verified + +Open an issue on the [docs.getdbt.com GitHub repository](https://github.com/dbt-labs/docs.getdbt.com) using the "Add adapter to Trusted list" template. In addition to contact information, it will ask confirm that you agree to the following. + +1. my adapter meet the guidelines given above +2. I will make best reasonable effort that this continues to be so +3. checkbox: I acknowledge that dbt Labs reserves the right to remove an adapter from the trusted adapter list at any time, should any of the above guidelines not be met. + +The approval workflow is as follows: + +1. create and populate the template-created issue +2. dbt Labs will respond as quickly as possible (maximally four weeks, though likely faster) +3. If approved, dbt Labs will create and merge a Pull request to formally add the adapter to the list. + +### Getting help for my trusted adapter + +Ask your question in #adapter-ecosystem channel of the dbt community Slack. diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md deleted file mode 100644 index ca531d04692..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Prerequisites for a new adapter" -id: "2-prerequisites-for-a-new-adapter" ---- - - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md deleted file mode 100644 index e882906da75..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Building a new adapter" -id: "3-building-a-new-adapter" ---- - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md deleted file mode 100644 index 9b1ec2c7e0e..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Testing a new adapter" -id: "4-testing-a-new-adapter" ---- - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter.md deleted file mode 100644 index 80b994aefb0..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Documenting a new adapter" -id: "5-documenting-a-new-adapter" ---- - -If you've already [built](3-building-a-new-adapter), and [tested](4-testing-a-new-adapter) your adapter, it's time to document it so the dbt community will know that it exists and how to use it. - -## Making your adapter available - -Many community members maintain their adapter plugins under open source licenses. If you're interested in doing this, we recommend: - -- Hosting on a public git provider (for example, GitHub or Gitlab) -- Publishing to [PyPI](https://pypi.org/) -- Adding to the list of ["Supported Data Platforms"](/docs/supported-data-platforms#community-supported) (more info below) - -## General Guidelines - -To best inform the dbt community of the new adapter, you should contribute to the dbt's open-source documentation site, which uses the [Docusaurus project](https://docusaurus.io/). This is the site you're currently on! - -### Conventions - -Each `.md` file you create needs a header as shown below. The document id will also need to be added to the config file: `website/sidebars.js`. - -```md ---- -title: "Documenting a new adapter" -id: "documenting-a-new-adapter" ---- -``` - -### Single Source of Truth - -We ask our adapter maintainers to use the [docs.getdbt.com repo](https://github.com/dbt-labs/docs.getdbt.com) (i.e. this site) as the single-source-of-truth for documentation rather than having to maintain the same set of information in three different places. The adapter repo's `README.md` and the data platform's documentation pages should simply link to the corresponding page on this docs site. Keep reading for more information on what should and shouldn't be included on the dbt docs site. - -### Assumed Knowledge - -To simplify things, assume the reader of this documentation already knows how both dbt and your data platform works. There's already great material for how to learn dbt and the data platform out there. The documentation we're asking you to add should be what a user who is already profiecient in both dbt and your data platform would need to know in order to use both. Effectively that boils down to two things: how to connect, and how to configure. - -## Topics and Pages to Cover - -The following subjects need to be addressed across three pages of this docs site to have your data platform be listed on our documentation. After the corresponding pull request is merged, we ask that you link to these pages from your adapter repo's `REAMDE` as well as from your product documentation. - - To contribute, all you will have to do make the changes listed in the table below. - -| How To... | File to change within `/website/docs/` | Action | Info to Include | -|----------------------|--------------------------------------------------------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Connect | `/docs/core/connect-data-platform/{MY-DATA-PLATFORM}-setup.md` | Create | Give all information needed to define a target in `~/.dbt/profiles.yml` and get `dbt debug` to connect to the database successfully. All possible configurations should be mentioned. | -| Configure | `reference/resource-configs/{MY-DATA-PLATFORM}-configs.md` | Create | What options and configuration specific to your data platform do users need to know? e.g. table distribution and indexing options, column_quoting policy, which incremental strategies are supported | -| Discover and Install | `docs/supported-data-platforms.md` | Modify | Is it a vendor- or community- supported adapter? How to install Python adapter package? Ideally with pip and PyPI hosted package, but can also use `git+` link to GitHub Repo | -| Add link to sidebar | `website/sidebars.js` | Modify | Add the document id to the correct location in the sidebar menu | - -For example say I want to document my new adapter: `dbt-ders`. For the "Connect" page, I will make a new Markdown file, `ders-setup.md` and add it to the `/website/docs/core/connect-data-platform/` directory. - -## Example PRs to add new adapter documentation - -Below are some recent pull requests made by partners to document their data platform's adapter: - -- [TiDB](https://github.com/dbt-labs/docs.getdbt.com/pull/1309) -- [SingleStore](https://github.com/dbt-labs/docs.getdbt.com/pull/1044) -- [Firebolt](https://github.com/dbt-labs/docs.getdbt.com/pull/941) diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter.md deleted file mode 100644 index 9bf2f949bef..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: "Promoting a new adapter" -id: "6-promoting-a-new-adapter" ---- - -## Model for engagement in the dbt community - -The most important thing here is recognizing that people are successful in the community when they join, first and foremost, to engage authentically. - -What does authentic engagement look like? It’s challenging to define explicit rules. One good rule of thumb is to treat people with dignity and respect. - -Contributors to the community should think of contribution *as the end itself,* not a means toward other business KPIs (leads, community members, etc.). [We are a mission-driven company.](https://www.getdbt.com/dbt-labs/values/) Some ways to know if you’re authentically engaging: - -- Is an engagement’s *primary* purpose of sharing knowledge and resources or building brand engagement? -- Imagine you didn’t work at the org you do — can you imagine yourself still writing this? -- Is it written in formal / marketing language, or does it sound like you, the human? - -## Who should join the dbt community slack - -### People who have insight into what it means to do hands-on [analytics engineering](https://www.getdbt.com/analytics-engineering/) work - -The dbt Community Slack workspace is fundamentally a place for analytics practitioners to interact with each other — the closer the users are in the community to actual data/analytics engineering work, the more natural their engagement will be (leading to better outcomes for partners and the community). - -### DevRel practitioners with strong focus - -DevRel practitioners often have a strong analytics background and a good understanding of the community. It’s essential to be sure they are focused on *contributing,* not on driving community metrics for partner org (such as signing people up for their slack or events). The metrics will rise naturally through authentic engagement. - -### Founder and executives who are interested in directly engaging with the community - -This is either incredibly successful or not at all depending on the profile of the founder. Typically, this works best when the founder has a practitioner-level of technical understanding and is interested in joining not to promote, but to learn and hear from users. - -### Software Engineers at partner products that are building and supporting integrations with either dbt Core or dbt Cloud - -This is successful when the engineers are familiar with dbt as a product or at least have taken our training course. The Slack is often a place where end-user questions and feedback is initially shared, so it is recommended that someone technical from the team be present. There are also a handful of channels aimed at those building integrations, which tend to be a font of knowledge. - -### Who might struggle in the dbt community -#### People in marketing roles -dbt Slack is not a marketing channel. Attempts to use it as such invariably fall flat and can even lead to people having a negative view of a product. This doesn’t mean that dbt can’t serve marketing objectives, but a long-term commitment to engagement is the only proven method to do this sustainably. - -#### People in product roles -The dbt Community can be an invaluable source of feedback on a product. There are two primary ways this can happen — organically (community members proactively suggesting a new feature) and via direct calls for feedback and user research. Immediate calls for engagement must be done in your dedicated #tools channel. Direct calls should be used sparingly, as they can overwhelm more organic discussions and feedback. - -## Who is the audience for an adapter release - -A new adapter is likely to drive huge community interest from several groups of people: -- People who are currently using the database that the adapter is supporting -- People who may be adopting the database in the near future. -- People who are interested in dbt development in general. - -The database users will be your primary audience and the most helpful in achieving success. Engage them directly in the adapter’s dedicated Slack channel. If one does not exist already, reach out in #channel-requests, and we will get one made for you and include it in an announcement about new channels. - -The final group is where non-slack community engagement becomes important. Twitter and LinkedIn are both great places to interact with a broad audience. A well-orchestrated adapter release can generate impactful and authentic engagement. - -## How to message the initial rollout and follow-up content - -Tell a story that engages dbt users and the community. Highlight new use cases and functionality unlocked by the adapter in a way that will resonate with each segment. - -### Existing users of your technology who are new to dbt - -- Provide a general overview of the value dbt will deliver to your users. This can lean on dbt's messaging and talking points which are laid out in the [dbt viewpoint.](/community/resources/viewpoint) - - Give examples of a rollout that speaks to the overall value of dbt and your product. - -### Users who are already familiar with dbt and the community -- Consider unique use cases or advantages your adapter provide over existing adapters. Who will be excited for this? -- Contribute to the dbt Community and ensure that dbt users on your adapter are well supported (tutorial content, packages, documentation, etc). -- Example of a rollout that is compelling for those familiar with dbt: [Firebolt](https://www.linkedin.com/feed/update/urn:li:activity:6879090752459182080/) - -## Tactically manage distribution of content about new or existing adapters - -There are tactical pieces on how and where to share that help ensure success. - -### On slack: -- #i-made-this channel — this channel has a policy against “marketing” and “content marketing” posts, but it should be successful if you write your content with the above guidelines in mind. Even with that, it’s important to post here sparingly. -- Your own database / tool channel — this is where the people who have opted in to receive communications from you and always a great place to share things that are relevant to them. - -### On social media: -- Twitter -- LinkedIn -- Social media posts *from the author* or an individual connected to the project tend to have better engagement than posts from a company or organization account. -- Ask your partner representative about: - - Retweets and shares from the official dbt Labs accounts. - - Flagging posts internally at dbt Labs to get individual employees to share. - -## Measuring engagement - -You don’t need 1000 people in a channel to succeed, but you need at least a few active participants who can make it feel lived in. If you’re comfortable working in public, this could be members of your team, or it can be a few people who you know that are highly engaged and would be interested in participating. Having even 2 or 3 regulars hanging out in a channel is all that’s needed for a successful start and is, in fact, much more impactful than 250 people that never post. - -## How to announce a new adapter - -We’d recommend *against* boilerplate announcements and encourage finding a unique voice. That being said, there are a couple of things that we’d want to include: - -- A summary of the value prop of your database / technology for users who aren’t familiar. -- The personas that might be interested in this news. -- A description of what the adapter *is*. For example: - > With the release of our new dbt adapter, you’ll be able to to use dbt to model and transform your data in [name-of-your-org] -- Particular or unique use cases or functionality unlocked by the adapter. -- Plans for future / ongoing support / development. -- The link to the documentation for using the adapter on the dbt Labs docs site. -- An announcement blog. - -## Announcing new release versions of existing adapters - -This can vary substantially depending on the nature of the release but a good baseline is the types of release messages that [we put out in the #dbt-releases](https://getdbt.slack.com/archives/C37J8BQEL/p1651242161526509) channel. - -![Full Release Post](/img/adapter-guide/0-full-release-notes.png) - -Breaking this down: - -- Visually distinctive announcement - make it clear this is a release - -- Short written description of what is in the release - -- Links to additional resources - -- Implementation instructions: - -- Future plans - -- Contributor recognition (if applicable) - diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter.md deleted file mode 100644 index 6310569dfad..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Verifying a new adapter" -id: "7-verifying-a-new-adapter" ---- - -## Why verify an adapter? - -The very first data platform dbt supported was Redshift followed quickly by Postgres (([dbt-core#174](https://github.com/dbt-labs/dbt-core/pull/174)). In 2017, back when dbt Labs (née Fishtown Analytics) was still a data consultancy, we added support for Snowflake and BigQuery. We also turned dbt's database support into an adapter framework ([dbt-core#259](https://github.com/dbt-labs/dbt-core/pull/259/)), and a plugin system a few years later. For years, dbt Labs specialized in those four data platforms and became experts in them. However, the surface area of all possible databases, their respective nuances, and keeping them up-to-date and bug-free is a Herculean and/or Sisyphean task that couldn't be done by a single person or even a single team! Enter the dbt community which enables dbt Core to work on more than 30 different databases (32 as of Sep '22)! - -Free and open-source tools for the data professional are increasingly abundant. This is by-and-large a *good thing*, however it requires due dilligence that wasn't required in a paid-license, closed-source software world. Before taking a dependency on an open-source projet is is important to determine the answer to the following questions: - -1. Does it work? -2. Does it meet my team's specific use case? -3. Does anyone "own" the code, or is anyone liable for ensuring it works? -4. Do bugs get fixed quickly? -5. Does it stay up-to-date with new Core features? -6. Is the usage substantial enough to self-sustain? -7. What risks do I take on by taking a dependency on this library? - -These are valid, important questions to answer—especially given that `dbt-core` itself only put out its first stable release (major version v1.0) in December 2021! Indeed, up until now, the majority of new user questions in database-specific channels are some form of: -- "How mature is `dbt-`? Any gotchas I should be aware of before I start exploring?" -- "has anyone here used `dbt-` for production models?" -- "I've been playing with `dbt-` -- I was able to install and run my initial experiments. I noticed that there are certain features mentioned on the documentation that are marked as 'not ok' or 'not tested'. What are the risks? -I'd love to make a statement on my team to adopt DBT [sic], but I'm pretty sure questions will be asked around the possible limitations of the adapter or if there are other companies out there using dbt [sic] with Oracle DB in production, etc." - -There has been a tendency to trust the dbt Labs-maintained adapters over community- and vendor-supported adapters, but repo ownership is only one among many indicators of software quality. We aim to help our users feel well-informed as to the caliber of an adapter with a new program. - -## Verified by dbt Labs - -The adapter verification program aims to quickly indicate to users which adapters can be trusted to use in production. Previously, doing so was uncharted territory for new users and complicated making the business case to their leadership team. We plan to give quality assurances by: -1. appointing a key stakeholder for the adapter repository, -2. ensuring that the chosen stakeholder fixes bugs and cuts new releases in a timely manner see maintainer your adapter (["Maintaining your new adapter"](2-prerequisites-for-a-new-adapter#maintaining-your-new-adapter)), -3. demonstrating that it passes our adapter pytest suite tests, -4. assuring that it works for us internally and ideally an existing team using the adapter in production . - - -Every major & minor version of a adapter will be verified internally and given an official :white_check_mark: (custom emoji coming soon), on the ["Supported Data Platforms"](/docs/supported-data-platforms) page. - -## How to get an adapter verified? - -We envision that data platform vendors will be most interested in having their adapter versions verified, however we are open to community adapter verification. If interested, please reach out either to the `partnerships` at `dbtlabs.com` or post in the [#adapter-ecosystem Slack channel](https://getdbt.slack.com/archives/C030A0UF5LM). diff --git a/website/docs/guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter.md b/website/docs/guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter.md deleted file mode 100644 index 9783ec66460..00000000000 --- a/website/docs/guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: "Building a Trusted Adapter" -id: "8-building-a-trusted-adapter" ---- - -The Trusted adapter program exists to allow adapter maintainers to demonstrate to the dbt community that your adapter is trusted to be used in production. - -## What does it mean to be trusted - -By opting into the below, you agree to this, and we take you at your word. dbt Labs reserves the right to remove an adapter from the trusted adapter list at any time, should any of the below guidelines not be met. - -### Feature Completeness - -To be considered for the Trusted Adapter program, the adapter must cover the essential functionality of dbt Core given below, with best effort given to support the entire feature set. - -Essential functionality includes (but is not limited to the following features): - -- table, view, and seed materializations -- dbt tests - -The adapter should have the required documentation for connecting and configuring the adapter. The dbt docs site should be the single source of truth for this information. These docs should be kept up-to-date. - -See [Documenting a new adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) for more information. - -### Release Cadence - -Keeping an adapter up-to-date with dbt Core is an integral part of being a trusted adapter. Therefore, we ask that adapter maintainers: - -- Release of new minor versions of the adapter with all tests passing within four weeks of dbt Core's release cut. -- Release of new major versions of the adapter with all tests passing within eight weeks of dbt Core's release cut. - -### Community Responsiveness - -On a best effort basis, active participation and engagement with the dbt Community across the following forums: - -- Being responsive to feedback and supporting user enablement in dbt Community’s Slack workspace -- Responding with comments to issues raised in public dbt adapter code repository -- Merging in code contributions from community members as deemed appropriate - -### Security Practices - -Trusted adapters will not do any of the following: - -- Output to logs or file either access credentials information to or data from the underlying data platform itself. -- Make API calls other than those expressly required for using dbt features (adapters may not add additional logging) -- Obfuscate code and/or functionality so as to avoid detection - -Additionally, to avoid supply-chain attacks: - -- Use an automated service to keep Python dependencies up-to-date (such as Dependabot or similar), -- Publish directly to PyPI from the dbt adapter code repository by using trusted CI/CD process (such as GitHub actions) -- Restrict admin access to both the respective code (GitHub) and package (PyPI) repositories -- Identify and mitigate security vulnerabilities by use of a static code analyzing tool (such as Snyk) as part of a CI/CD process - -### Other considerations - -The adapter repository is: - -- open-souce licensed, -- published to PyPI, and -- automatically tests the codebase against dbt Lab's provided adapter test suite - -## How to get an adapter verified? - -Open an issue on the [docs.getdbt.com GitHub repository](https://github.com/dbt-labs/docs.getdbt.com) using the "Add adapter to Trusted list" template. In addition to contact information, it will ask confirm that you agree to the following. - -1. my adapter meet the guidelines given above -2. I will make best reasonable effort that this continues to be so -3. checkbox: I acknowledge that dbt Labs reserves the right to remove an adapter from the trusted adapter list at any time, should any of the above guidelines not be met. - -The approval workflow is as follows: - -1. create and populate the template-created issue -2. dbt Labs will respond as quickly as possible (maximally four weeks, though likely faster) -3. If approved, dbt Labs will create and merge a Pull request to formally add the adapter to the list. - -## How to get help with my trusted adapter? - -Ask your question in #adapter-ecosystem channel of the community Slack. From 6be2182a6fe069598537f327f1147726e05a507c Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:18:40 -0800 Subject: [PATCH 062/152] adding tags --- website/docs/guides/create-adapters.md | 6 +++--- website/docs/guides/debug-schema-names.md | 2 +- website/docs/guides/sl-partner-integration-guide.md | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/docs/guides/create-adapters.md b/website/docs/guides/create-adapters.md index deb2764edc6..ad3f16ef831 100644 --- a/website/docs/guides/create-adapters.md +++ b/website/docs/guides/create-adapters.md @@ -1,8 +1,8 @@ --- title: "Build, test, document, and promote adapters" id: "adapter-creation" -description: -hoverSnippet: Learn how to +description: Create an adapter that connects dbt to you platform, and learn how to maintain and version that adapter. +hoverSnippet: Learn how to build, test, document, and promote adapters as well as maintaining and versioning an adapter. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true @@ -13,7 +13,7 @@ recently_updated: true ## Introduction -Adapters are an essential component of dbt. At their most basic level, they are how dbt Core connects with the various supported data platforms. At a higher-level, dbt Core adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt Core. +Adapters are an essential component of dbt. At their most basic level, they are how dbt connects with the various supported data platforms. At a higher-level, dbt Core adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt Core. Navigating and developing around the nuances of different databases can be daunting, but you are not alone. Visit [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel for additional help beyond the documentation. diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index 7f40576d7a9..ee5156c578b 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -8,7 +8,7 @@ hoverSnippet: Learn how to debug schema names in dbt. platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true -tags: ['dbt Core'] +tags: ['dbt Core','Troubleshooting'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/sl-partner-integration-guide.md b/website/docs/guides/sl-partner-integration-guide.md index 41a5135bb7d..e547b206aeb 100644 --- a/website/docs/guides/sl-partner-integration-guide.md +++ b/website/docs/guides/sl-partner-integration-guide.md @@ -1,19 +1,19 @@ --- -title: "dbt Semantic Layer integration best practices" +title: "Integrate with dbt Semantic Layer using best practices" id: "sl-partner-integration-guide" description: Learn about partner integration guidelines, roadmap, and connectivity. -hoverSnippet: Learn how to +hoverSnippet: Learn how to integrate with the Semantic Layer using best practices # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true -tags: ['Semantic layer'] +tags: ['Semantic layer','Best practices'] level: 'Advanced' recently_updated: true --- ## Introduction -To fit your tool within the world of the Semantic Layer, dbt Labs offers some best practice recommendations for how to expose metrics and allow users to interact with them seamlessly. +To fit your tool within the world of the Semantic Layer, dbt Labs offers some best practice recommendations for how to expose metrics and allow users to interact with them seamlessly. :::note This is an evolving guide that is meant to provide recommendations based on our experience. If you have any feedback, we'd love to hear it! From 4808d3e338f417c06fd54cda0e578ad9f4b4132a Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:47:42 -0800 Subject: [PATCH 063/152] fixing details --- .../dbt-unity-catalog-best-practices.md | 11 +++++++++-- .../{create-adapters.md => adapter-creation.md} | 8 ++++---- website/docs/guides/bigquery-qs.md | 2 +- website/docs/guides/building-packages.md | 6 +++--- website/docs/guides/codespace-qs.md | 2 +- website/docs/guides/custom-cicd-pipelines.md | 2 +- website/docs/guides/databricks-qs.md | 2 +- .../debugging-errors.md => guides/debug-errors.md} | 11 ++++++++--- website/docs/guides/debug-schema-names.md | 1 - .../productionize-your-dbt-databricks-project.md | 2 +- website/docs/guides/redshift-qs.md | 4 ++-- website/docs/guides/set-up-ci.md | 8 ++++---- website/docs/guides/sl-migration.md | 2 +- website/docs/guides/sl-partner-integration-guide.md | 2 +- website/docs/guides/snowflake-qs.md | 2 +- website/docs/guides/zapier-ms-teams.md | 3 +-- website/sidebars.js | 2 +- website/src/components/quickstartGuideList/index.js | 2 +- 18 files changed, 41 insertions(+), 31 deletions(-) rename website/docs/guides/{create-adapters.md => adapter-creation.md} (99%) rename website/docs/{best-practices/debugging-errors.md => guides/debug-errors.md} (98%) diff --git a/website/docs/best-practices/dbt-unity-catalog-best-practices.md b/website/docs/best-practices/dbt-unity-catalog-best-practices.md index b60e1b912c7..0d24cc320ec 100644 --- a/website/docs/best-practices/dbt-unity-catalog-best-practices.md +++ b/website/docs/best-practices/dbt-unity-catalog-best-practices.md @@ -1,6 +1,13 @@ -# Best practices for dbt and Unity Catalog +--- +title: "Best practices for dbt and Unity Catalog" +id: "dbt-unity-catalog-best-practices" +description: Learn how to configure your. +displayText: Writing custom generic tests +hoverSnippet: Learn how to define your own custom generic tests. +--- -Your Databricks dbt project should be configured after following the ["How to set up your databricks dbt project guide"](how-to-set-up-your-databricks-dbt-project). Now we’re ready to start building a dbt project using Unity Catalog. However, we should first consider how we want to allow dbt users to interact with our different catalogs. We recommend the following best practices to ensure the integrity of your production data: + +Your Databricks dbt project should be configured after following the ["How to set up your databricks dbt project guide"](/guides/set-up-your-databricks-dbt-project). Now we’re ready to start building a dbt project using Unity Catalog. However, we should first consider how we want to allow dbt users to interact with our different catalogs. We recommend the following best practices to ensure the integrity of your production data: ## Isolate your Bronze (aka source) data diff --git a/website/docs/guides/create-adapters.md b/website/docs/guides/adapter-creation.md similarity index 99% rename from website/docs/guides/create-adapters.md rename to website/docs/guides/adapter-creation.md index ad3f16ef831..cd18a413b10 100644 --- a/website/docs/guides/create-adapters.md +++ b/website/docs/guides/adapter-creation.md @@ -1,8 +1,8 @@ --- -title: "Build, test, document, and promote adapters" -id: "adapter-creation" -description: Create an adapter that connects dbt to you platform, and learn how to maintain and version that adapter. -hoverSnippet: Learn how to build, test, document, and promote adapters as well as maintaining and versioning an adapter. +title: Build, test, document, and promote adapters +id: adapter-creation +description: "Create an adapter that connects dbt to you platform, and learn how to maintain and version that adapter." +hoverSnippet: "Learn how to build, test, document, and promote adapters as well as maintaining and versioning an adapter." # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true diff --git a/website/docs/guides/bigquery-qs.md b/website/docs/guides/bigquery-qs.md index f13749333f5..c1f632f0621 100644 --- a/website/docs/guides/bigquery-qs.md +++ b/website/docs/guides/bigquery-qs.md @@ -5,7 +5,7 @@ id: "bigquery" level: 'Beginner' icon: 'bigquery' hide_table_of_contents: true -tags: ['BigQuery', 'dbt Cloud'] +tags: ['BigQuery', 'dbt Cloud','Quickstart'] recently_updated: true --- diff --git a/website/docs/guides/building-packages.md b/website/docs/guides/building-packages.md index ed22ae2fa12..641a1c6af6d 100644 --- a/website/docs/guides/building-packages.md +++ b/website/docs/guides/building-packages.md @@ -1,7 +1,7 @@ --- -title: "Building dbt packages" -id: "building-packages" -description: When you have dbt code that might help others, you can create a package for dbt using a GitHub repository. +title: Building dbt packages +id: building-packages +description: "When you have dbt code that might help others, you can create a package for dbt using a GitHub repository." displayText: Building dbt packages hoverSnippet: Learn how to create packages for dbt. # time_to_complete: '30 minutes' commenting out until we test diff --git a/website/docs/guides/codespace-qs.md b/website/docs/guides/codespace-qs.md index 2f5f57677af..7712ed8f8e8 100644 --- a/website/docs/guides/codespace-qs.md +++ b/website/docs/guides/codespace-qs.md @@ -1,5 +1,5 @@ --- -title: "Quickstart for dbt Core using GitHub Codespaces" +title: Quickstart for dbt Core using GitHub Codespaces id: codespace platform: 'dbt-core' icon: 'fa-github' diff --git a/website/docs/guides/custom-cicd-pipelines.md b/website/docs/guides/custom-cicd-pipelines.md index df3d30e7c27..bf781204fc5 100644 --- a/website/docs/guides/custom-cicd-pipelines.md +++ b/website/docs/guides/custom-cicd-pipelines.md @@ -7,7 +7,7 @@ hoverSnippet: Learn version-controlled code, custom pipelines, and enhanced code # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true -tags: ['dbt Cloud', 'Orchestration'] +tags: ['dbt Cloud', 'Orchestration', 'CI'] level: 'Intermediate' recently_updated: true --- diff --git a/website/docs/guides/databricks-qs.md b/website/docs/guides/databricks-qs.md index 0aadf79c18e..5a0c5536e7f 100644 --- a/website/docs/guides/databricks-qs.md +++ b/website/docs/guides/databricks-qs.md @@ -5,7 +5,7 @@ level: 'Beginner' icon: 'databricks' hide_table_of_contents: true recently_updated: true -tags: ['dbt Cloud', 'Quickstart'] +tags: ['dbt Cloud', 'Quickstart','Databricks'] --- ## Introduction diff --git a/website/docs/best-practices/debugging-errors.md b/website/docs/guides/debug-errors.md similarity index 98% rename from website/docs/best-practices/debugging-errors.md rename to website/docs/guides/debug-errors.md index fe600ec4f67..febfb6ac422 100644 --- a/website/docs/best-practices/debugging-errors.md +++ b/website/docs/guides/debug-errors.md @@ -1,13 +1,18 @@ --- -title: "Debugging errors" -id: "debugging-errors" +title: "Debug errors" +id: "debug-errors" description: Learn about errors and the art of debugging them. displayText: Debugging errors hoverSnippet: Learn about errors and the art of debugging those errors. +icon: 'guides' +hide_table_of_contents: true +tags: ['Troubleshooting', 'dbt Core', 'dbt Cloud'] +level: 'Beginner' +recently_updated: true --- - ## General process of debugging + Learning how to debug is a skill, and one that will make you great at your role! 1. Read the error message — when writing the code behind dbt, we try our best to make error messages as useful as we can. The error message dbt produces will normally contain the type of error (more on these error types below), and the file where the error occurred. 2. Inspect the file that was known to cause the issue, and see if there's an immediate fix. diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index ee5156c578b..de713e07df7 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -5,7 +5,6 @@ description: Learn how to debug schema names when models build under unexpected displayText: Debug schema names hoverSnippet: Learn how to debug schema names in dbt. # time_to_complete: '30 minutes' commenting out until we test -platform: 'dbt-core' icon: 'guides' hide_table_of_contents: true tags: ['dbt Core','Troubleshooting'] diff --git a/website/docs/guides/productionize-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md index ce4e73b567b..12060da999d 100644 --- a/website/docs/guides/productionize-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -1,6 +1,6 @@ --- title: Productionize your dbt Databricks project -id: "productionize-your-dbt-databricks-project" +id: productionize-your-dbt-databricks-project description: "Learn how to deliver models to end users and use best practices to maintain production data" displayText: Productionize your dbt Databricks project hoverSnippet: Learn how to Productionize your dbt Databricks project. diff --git a/website/docs/guides/redshift-qs.md b/website/docs/guides/redshift-qs.md index d9f41be939c..9296e6c6568 100644 --- a/website/docs/guides/redshift-qs.md +++ b/website/docs/guides/redshift-qs.md @@ -1,10 +1,10 @@ --- title: "Quickstart for dbt Cloud and Redshift" -id: "redshift" +id: redshift level: 'Beginner' icon: 'redshift' hide_table_of_contents: true -tags: ['Redshift', 'dbt Cloud'] +tags: ['Redshift', 'dbt Cloud','Quickstart'] --- ## Introduction diff --git a/website/docs/guides/set-up-ci.md b/website/docs/guides/set-up-ci.md index 1dfe7270708..c6bcf316952 100644 --- a/website/docs/guides/set-up-ci.md +++ b/website/docs/guides/set-up-ci.md @@ -1,16 +1,16 @@ --- title: "Get started with Continuous Integration tests" -description: How to implement a CI environment for safe project validation. -displayText: -hoverSnippet: Learn how to +description: Implement a CI environment for safe project validation. +hoverSnippet: Learn how to implement a CI environment for safe project validation. id: set-up-ci # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true -tags: ['dbt Cloud', 'Orchestration'] +tags: ['dbt Cloud', 'Orchestration', 'CI'] level: 'Intermediate' recently_updated: true --- + ## Introduction By validating your code _before_ it goes into production, you don't need to spend your afternoon fielding messages from people whose reports are suddenly broken. diff --git a/website/docs/guides/sl-migration.md b/website/docs/guides/sl-migration.md index b0605ece333..0cfde742af2 100644 --- a/website/docs/guides/sl-migration.md +++ b/website/docs/guides/sl-migration.md @@ -6,7 +6,7 @@ hoverSnippet: Migrate from the legacy dbt Semantic Layer to the latest one. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true -tags: ['Semantic Layer'] +tags: ['Semantic Layer','Migration'] level: 'Intermediate' recently_updated: true --- diff --git a/website/docs/guides/sl-partner-integration-guide.md b/website/docs/guides/sl-partner-integration-guide.md index e547b206aeb..04f58f525bd 100644 --- a/website/docs/guides/sl-partner-integration-guide.md +++ b/website/docs/guides/sl-partner-integration-guide.md @@ -6,7 +6,7 @@ hoverSnippet: Learn how to integrate with the Semantic Layer using best practice # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' hide_table_of_contents: true -tags: ['Semantic layer','Best practices'] +tags: ['Semantic Layer','Best practices'] level: 'Advanced' recently_updated: true --- diff --git a/website/docs/guides/snowflake-qs.md b/website/docs/guides/snowflake-qs.md index 4488d6b3097..abb18276b97 100644 --- a/website/docs/guides/snowflake-qs.md +++ b/website/docs/guides/snowflake-qs.md @@ -3,7 +3,7 @@ title: "Quickstart for dbt Cloud and Snowflake" id: "snowflake" level: 'Beginner' icon: 'snowflake' -tags: ['dbt Cloud','Quickstart'] +tags: ['dbt Cloud','Quickstart','Snowflake'] hide_table_of_contents: true --- ## Introduction diff --git a/website/docs/guides/zapier-ms-teams.md b/website/docs/guides/zapier-ms-teams.md index cb62a0edc33..bd8bdd4aca2 100644 --- a/website/docs/guides/zapier-ms-teams.md +++ b/website/docs/guides/zapier-ms-teams.md @@ -1,7 +1,6 @@ --- title: "Post to Microsoft Teams when a job finishes" -id: webhooks-guide-zapier-ms-teams -slug: zapier-ms-teams +id: zapier-ms-teams description: Use Zapier and dbt Cloud webhooks to post to Microsoft Teams when a job finishes running hoverSnippet: Learn how to use Zapier with dbt Cloud webhooks to post in Microsoft Teams when a job finishes running. # time_to_complete: '30 minutes' commenting out until we test diff --git a/website/sidebars.js b/website/sidebars.js index 504be383845..66ba731fb1b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -1049,9 +1049,9 @@ const sidebarSettings = { "best-practices/materializations/materializations-guide-7-conclusion", ], }, - "best-practices/debugging-errors", "best-practices/writing-custom-generic-tests", "best-practices/best-practice-workflows", + "best-practices/dbt-unity-catalog-best-practices", ], }, ], diff --git a/website/src/components/quickstartGuideList/index.js b/website/src/components/quickstartGuideList/index.js index 5d7c8f4c498..05c8c041a0e 100644 --- a/website/src/components/quickstartGuideList/index.js +++ b/website/src/components/quickstartGuideList/index.js @@ -86,7 +86,7 @@ function QuickstartList({ quickstartData }) { {levelOptions && levelOptions.length > 0 && ( )} - setSearchInput(value)} placeholder='Search Quickstarts' /> + setSearchInput(value)} placeholder='Search Guides' />
    {filteredData && filteredData.length > 0 ? ( From a6c73591918086fe1fa3607c321b53a098ace688 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:30:13 -0800 Subject: [PATCH 064/152] fixing links --- ...5-how-to-build-a-mature-dbt-project-from-scratch.md | 2 +- .../blog/2023-04-24-framework-refactor-alteryx-dbt.md | 4 ++-- .../materializations-guide-1-guide-overview.md | 2 +- .../release-notes/09-April-2023/product-docs.md | 6 +++--- website/docs/docs/deploy/webhooks.md | 4 ++-- .../productionize-your-dbt-databricks-project.md | 2 +- website/src/pages/index.js | 6 +++--- website/vercel.json | 10 +++++----- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/website/blog/2021-12-05-how-to-build-a-mature-dbt-project-from-scratch.md b/website/blog/2021-12-05-how-to-build-a-mature-dbt-project-from-scratch.md index c4de04a48c3..8ea387cf00c 100644 --- a/website/blog/2021-12-05-how-to-build-a-mature-dbt-project-from-scratch.md +++ b/website/blog/2021-12-05-how-to-build-a-mature-dbt-project-from-scratch.md @@ -69,7 +69,7 @@ In addition to learning the basic pieces of dbt, we're familiarizing ourselves w If we decide not to do this, we end up missing out on what the dbt workflow has to offer. If you want to learn more about why we think analytics engineering with dbt is the way to go, I encourage you to read the [dbt Viewpoint](/community/resources/viewpoint#analytics-is-collaborative)! -In order to learn the basics, we’re going to [port over the SQL file](/guides/migration/tools/refactoring-legacy-sql) that powers our existing "patient_claim_summary" report that we use in our KPI dashboard in parallel to our old transformation process. We’re not ripping out the old plumbing just yet. In doing so, we're going to try dbt on for size and get used to interfacing with a dbt project. +In order to learn the basics, we’re going to [port over the SQL file](/guides/refactoring-legacy-sql) that powers our existing "patient_claim_summary" report that we use in our KPI dashboard in parallel to our old transformation process. We’re not ripping out the old plumbing just yet. In doing so, we're going to try dbt on for size and get used to interfacing with a dbt project. **Project Appearance** diff --git a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md index c5b677f7f3e..9b6135b0984 100644 --- a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md +++ b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md @@ -94,7 +94,7 @@ It is essential to click on each data source (the green book icons on the leftmo For this step, we identified which operators were used in the data source (for example, joining data, order columns, group by, etc). Usually the Alteryx operators are pretty self-explanatory and all the information needed for understanding appears on the left side of the menu. We also checked the documentation to understand how each Alteryx operator works behind the scenes. -We followed dbt Labs' guide on how to refactor legacy SQL queries in dbt and some [best practices](https://docs.getdbt.com/guides/migration/tools/refactoring-legacy-sql). After we finished refactoring all the Alteryx workflows, we checked if the Alteryx output matched the output of the refactored model built on dbt. +We followed dbt Labs' guide on how to refactor legacy SQL queries in dbt and some [best practices](https://docs.getdbt.com/guides/refactoring-legacy-sql). After we finished refactoring all the Alteryx workflows, we checked if the Alteryx output matched the output of the refactored model built on dbt. #### Step 3: Use the `audit_helper` package to audit refactored data models @@ -131,4 +131,4 @@ As we can see, refactoring Alteryx to dbt was an important step in the direction > > [Audit_helper in dbt: Bringing data auditing to a higher level](https://docs.getdbt.com/blog/audit-helper-for-migration) > -> [Refactoring legacy SQL to dbt](https://docs.getdbt.com/guides/migration/tools/refactoring-legacy-sql) +> [Refactoring legacy SQL to dbt](https://docs.getdbt.com/guides/refactoring-legacy-sql) diff --git a/website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md b/website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md index 467d58ce4a9..248b4c4749b 100644 --- a/website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md +++ b/website/docs/best-practices/materializations/materializations-guide-1-guide-overview.md @@ -28,7 +28,7 @@ By the end of this guide you should have a solid understanding of: - 📒 You’ll want to have worked through the [quickstart guide](/guides) and have a project setup to work through these concepts. - 🏃🏻‍♀️ Concepts like dbt runs, `ref()` statements, and models should be familiar to you. -- 🔧 [**Optional**] Reading through the [How we structure our dbt projects](guides/best-practices/how-we-structure/1-guide-overview) Guide will be beneficial for the last section of this guide, when we review best practices for materializations using the dbt project approach of staging models and marts. +- 🔧 [**Optional**] Reading through the [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) Guide will be beneficial for the last section of this guide, when we review best practices for materializations using the dbt project approach of staging models and marts. ### Guiding principle diff --git a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md index 84b962c56d2..5082699619b 100644 --- a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md +++ b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md @@ -32,9 +32,9 @@ Hello from the dbt Docs team: @mirnawong1, @matthewshaver, @nghi-ly, and @runleo ## New 📚 Guides and ✏️ blog posts - [Use Databricks workflows to run dbt Cloud jobs](/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs) -- [Refresh Tableau workbook with extracts after a job finishes](/guides/orchestration/webhooks/zapier-refresh-tableau-workbook) -- [dbt Python Snowpark workshop/tutorial](/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark) -- [How to optimize and troubleshoot dbt Models on Databricks](/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks) +- [Refresh Tableau workbook with extracts after a job finishes](/guides/zapier-refresh-tableau-workbook) +- [dbt Python Snowpark workshop/tutorial](/guides/dbt-python-snowpark) +- [How to optimize and troubleshoot dbt Models on Databricks](/guides/optimize-dbt-models-on-databricks) - [The missing guide to debug() in dbt](https://docs.getdbt.com/blog/guide-to-jinja-debug) - [dbt Squared: Leveraging dbt Core and dbt Cloud together at scale](https://docs.getdbt.com/blog/dbt-squared) - [Audit_helper in dbt: Bringing data auditing to a higher level](https://docs.getdbt.com/blog/audit-helper-for-migration) diff --git a/website/docs/docs/deploy/webhooks.md b/website/docs/docs/deploy/webhooks.md index 069e7a3e283..25e16e201c1 100644 --- a/website/docs/docs/deploy/webhooks.md +++ b/website/docs/docs/deploy/webhooks.md @@ -8,7 +8,7 @@ With dbt Cloud, you can create outbound webhooks to send events (notifications) A webhook is an HTTP-based callback function that allows event-driven communication between two different web applications. This allows you to get the latest information on your dbt jobs in real time. Without it, you would need to make API calls repeatedly to check if there are any updates that you need to account for (polling). Because of this, webhooks are also called _push APIs_ or _reverse APIs_ and are often used for infrastructure development. -dbt Cloud sends a JSON payload to your application's endpoint URL when your webhook is triggered. You can send a [Slack](/guides/orchestration/webhooks/zapier-slack) notification, a [Microsoft Teams](/guides/orchestration/webhooks/zapier-ms-teams) notification, [open a PagerDuty incident](/guides/orchestration/webhooks/serverless-pagerduty) when a dbt job fails, [and more](/guides/orchestration/webhooks). +dbt Cloud sends a JSON payload to your application's endpoint URL when your webhook is triggered. You can send a [Slack](/guides/zapier-slack) notification, a [Microsoft Teams](/guides/zapier-ms-teams) notification, [open a PagerDuty incident](/guides/serverless-pagerduty) when a dbt job fails. You can create webhooks for these events from the [dbt Cloud web-based UI](#create-a-webhook-subscription) and by using the [dbt Cloud API](#api-for-webhooks): @@ -549,5 +549,5 @@ DELETE https://{your access URL}/api/v3/accounts/{account_id}/webhooks/subscript ## Related docs - [dbt Cloud CI](/docs/deploy/continuous-integration) -- [Use dbt Cloud's webhooks with other SaaS apps](/guides/orchestration/webhooks) +- [Use dbt Cloud's webhooks with other SaaS apps](/guides) diff --git a/website/docs/guides/productionize-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md index 12060da999d..fdadcef6d34 100644 --- a/website/docs/guides/productionize-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -117,7 +117,7 @@ Setting up [notifications](/docs/deploy/job-notifications) in dbt Cloud allows y 2. Select the **Notifications** tab. 3. Choose the desired notification type (Email or Slack) and configure the relevant settings. -If you require notifications through other means than email or Slack, you can use dbt Cloud's outbound [webhooks](/docs/deploy/webhooks) feature to relay job events to other tools. Webhooks enable you to [integrate dbt Cloud with a wide range of SaaS applications](/guides/orchestration/webhooks), extending your pipeline’s automation into other systems. +If you require notifications through other means than email or Slack, you can use dbt Cloud's outbound [webhooks](/docs/deploy/webhooks) feature to relay job events to other tools. Webhooks enable you to integrate dbt Cloud with a wide range of SaaS applications, extending your pipeline’s automation into other systems. ## Troubleshooting diff --git a/website/src/pages/index.js b/website/src/pages/index.js index cee3796133f..7285bad7182 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -34,7 +34,7 @@ function Home() { const featuredResource = { title: "How we structure our dbt projects", description: "Our hands-on learnings for how to structure your dbt project for success and gain insights into the principles of analytics engineering.", - link: "/guides/best-practices/how-we-structure/1-guide-overview", + link: "/best-practices/how-we-structure/1-guide-overview", image: "/img/structure-dbt-projects.png", sectionTitle: 'Featured resource' } @@ -146,9 +146,9 @@ function Home() {
    diff --git a/website/vercel.json b/website/vercel.json index c73534da7c2..3c2c0c6e3ce 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -2,6 +2,11 @@ "cleanUrls": true, "trailingSlash": false, "redirects": [ + { + "source": "/faqs/Project/docs-for-multiple-projects", + "destination": "/docs/collaborate/explore-projects#about-project-level-lineage", + "permanent": true + }, { "source": "/faqs/Project/docs-for-multiple-projects", "destination": "/docs/collaborate/explore-projects#about-project-level-lineage", @@ -4181,11 +4186,6 @@ "source": "/quickstarts/manual-install", "destination": "/guides/manual-install", "permanent": true - }, - { - "source": "TODO", - "destination": "TODO", - "permanent": true } ] } From bb20a8361db93f8e28d6083e3b7b9854743e6a81 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:51:04 +0000 Subject: [PATCH 065/152] Update metricflow-commands.md clarify mf command examples avail in cloud --- website/docs/docs/build/metricflow-commands.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/website/docs/docs/build/metricflow-commands.md b/website/docs/docs/build/metricflow-commands.md index 2386dab4ba2..fd120591900 100644 --- a/website/docs/docs/build/metricflow-commands.md +++ b/website/docs/docs/build/metricflow-commands.md @@ -180,9 +180,9 @@ Options: ### Validate-configs This command performs validations against the defined semantic model configurations: + ```bash -dbt sl validate-configs # In dbt Cloud mf validate-configs # In dbt Core @@ -206,20 +206,18 @@ Options: ### Health checks This command performs a health check against the data platform you provided in the configs: + ```bash -dbt sl health-checks #in dbt Cloud - mf health-checks #in dbt Core ``` ### Tutorial Follow the dedicated MetricFlow tutorial to help you get started: + ```bash -dbt sl tutorial # In dbt Cloud - mf tutorial # In dbt Core ``` @@ -522,7 +520,7 @@ mf query --metrics revenue --group-by metric_time__month # In dbt Core To add a dimension filter to a where filter, you have to indicate that the filter item is part of your model and use a template wrapper: {{Dimension('primary_entity__dimension_name')}}. -Here's an example query: dbt sl query --metrics order_total --group-by metric_time --where "{{Dimension('order_id__is_food_order')}} = True".

    Before using the template wrapper, however, you will need to set up your terminal to escape curly braces for the filter template to work. +Here's an example query: dbt sl query --metrics order_total --group-by metric_time --where "{{Dimension('order_id__is_food_order')}} = True".

    Before using the template wrapper, however, set up your terminal to escape curly braces for the filter template to work.
    How to set up your terminal to escape curly braces? From 0dd1d2863907a7e847d11a14e9c28d72608a4beb Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 12:43:08 +0000 Subject: [PATCH 066/152] add callout for deprecation --- website/docs/docs/build/metrics.md | 14 +++----------- .../avail-sl-integrations.md | 4 ++-- website/docs/docs/use-dbt-semantic-layer/dbt-sl.md | 4 ++-- .../docs/use-dbt-semantic-layer/quickstart-sl.md | 4 ++-- .../docs/docs/use-dbt-semantic-layer/setup-sl.md | 4 ++-- .../docs/use-dbt-semantic-layer/sl-architecture.md | 4 ++++ website/snippets/sl-deprecation-notice.md | 8 ++++++++ 7 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 website/snippets/sl-deprecation-notice.md diff --git a/website/docs/docs/build/metrics.md b/website/docs/docs/build/metrics.md index 7a505fdad14..6d4e0fec893 100644 --- a/website/docs/docs/build/metrics.md +++ b/website/docs/docs/build/metrics.md @@ -7,13 +7,9 @@ keywords: tags: [Metrics] --- -:::caution Upgrade to access MetricFlow and the new dbt Semantic Layer +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; -The dbt_metrics package has been deprecated and replaced with [MetricFlow](/docs/build/about-metricflow?version=1.6). If you're using the dbt_metrics package or the legacy Semantic Layer (available on v1.5 or lower), we **highly** recommend [upgrading your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to access MetricFlow and the new [dbt Semantic Layer](/docs/use-dbt-semantic-layer/dbt-sl?version=1.6). - -To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. - -::: + @@ -38,11 +34,7 @@ A metric is an aggregation over a that supports zero or more - active users - monthly recurring revenue (mrr) -In v1.0, dbt supports metric definitions as a new node type. Like [exposures](exposures), metrics appear as nodes in the directed acyclic graph (DAG) and can be expressed in YAML files. Defining metrics in dbt projects encodes crucial business logic in tested, version-controlled code. Further, you can expose these metrics definitions to downstream tooling, which drives consistency and precision in metric reporting. - -Review the video below to learn more about metrics, why they're important, and how to get started: - - +In v1.0, dbt supports metric definitions as a new node type. Like [exposures](exposures), metrics appear as nodes in the directed acyclic graph (DAG) and can be expressed in YAML files. Defining metrics in dbt projects encodes crucial business logic in tested, version-controlled code. Further, you can expose these metrics definitions to downstream tooling, which drives consistency and precision in metric reporting. ### Benefits of defining metrics diff --git a/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md b/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md index 22178a14b5b..4f009db1819 100644 --- a/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md +++ b/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md @@ -38,9 +38,9 @@ import AvailIntegrations from '/snippets/_sl-partner-links.md'; -import LegacyInfo from '/snippets/_legacy-sl-callout.md'; +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; - + A wide variety of data applications across the modern data stack natively integrate with the dbt Semantic Layer and dbt metrics — from Business Intelligence tools to notebooks, data catalogs, and more. diff --git a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md index 8c78d556a67..b1a2c2ecaa9 100644 --- a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md @@ -67,9 +67,9 @@ plan="dbt Cloud Team or Enterprise" -import LegacyInfo from '/snippets/_legacy-sl-callout.md'; +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; - + The dbt Semantic Layer allows your data teams to centrally define essential business metrics like `revenue`, `customer`, and `churn` in the modeling layer (your dbt project) for consistent self-service within downstream data tools like BI and metadata management solutions. The dbt Semantic Layer provides the flexibility to define metrics on top of your existing models and then query those metrics and models in your analysis tools of choice. diff --git a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md index 42f08a90401..f68b0e144d1 100644 --- a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md @@ -114,9 +114,9 @@ User data passes through the Semantic Layer on its way back from the warehouse. -import LegacyInfo from '/snippets/_legacy-sl-callout.md'; +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; - + To try out the features of the dbt Semantic Layer, you first need to have a dbt project set up. This quickstart guide will lay out the following steps, and recommends a workflow that demonstrates some of its essential features: diff --git a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md index 4c88ee50b25..6fc7a018b8b 100644 --- a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md @@ -39,9 +39,9 @@ import SlSetUp from '/snippets/_new-sl-setup.md'; -import LegacyInfo from '/snippets/_legacy-sl-callout.md'; +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; - + With the dbt Semantic Layer, you can define business metrics, reduce code duplication and inconsistency, create self-service in downstream tools, and more. Configure the dbt Semantic Layer in dbt Cloud to connect with your integrated partner tool. diff --git a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md index 9e8737c68d3..a1a19ead040 100644 --- a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md +++ b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md @@ -56,6 +56,10 @@ The dbt Semantic Layer is proprietary; however, some components of the dbt Seman +import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; + + + ## Product architecture The dbt Semantic Layer product architecture includes four primary components: diff --git a/website/snippets/sl-deprecation-notice.md b/website/snippets/sl-deprecation-notice.md new file mode 100644 index 00000000000..ec49fd28fbf --- /dev/null +++ b/website/snippets/sl-deprecation-notice.md @@ -0,0 +1,8 @@ +:::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer +If you're reading this message, you're either using an older version of our documentation or currently use the legacy Semantic Layer. + +As of December 15th, 2023, dbt Metrics and the legacy dbt Semantic Layer (v1.5 and lower) will no longer be supported. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. + +After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. + +::: From 6c5ab74eca9b6c13401b5b0328f11aa9f7ef62e9 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 12:50:41 +0000 Subject: [PATCH 067/152] adding bullets for consistency --- website/docs/docs/use-dbt-semantic-layer/gsheets.md | 8 ++++---- website/docs/docs/use-dbt-semantic-layer/tableau.md | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/website/docs/docs/use-dbt-semantic-layer/gsheets.md b/website/docs/docs/use-dbt-semantic-layer/gsheets.md index ee391c91b70..2bbba70f98e 100644 --- a/website/docs/docs/use-dbt-semantic-layer/gsheets.md +++ b/website/docs/docs/use-dbt-semantic-layer/gsheets.md @@ -13,10 +13,10 @@ The dbt Semantic Layer offers a seamless integration with Google Sheets through ## Prerequisites -1. You have a Google account with access to Google Sheets. -2. You can install Google add-ons. -3. You have [set up the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl). -4. You have a dbt Cloud Environment ID and a [service token](/docs/dbt-cloud-apis/service-tokens) to authenticate with from a dbt Cloud account. +- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using the latest version. +- You have a Google account with access to Google Sheets. +- You can install Google add-ons. +- You have a dbt Cloud Environment ID and a [service token](/docs/dbt-cloud-apis/service-tokens) to authenticate with from a dbt Cloud account. ## Installing the add-on diff --git a/website/docs/docs/use-dbt-semantic-layer/tableau.md b/website/docs/docs/use-dbt-semantic-layer/tableau.md index c93643354aa..fe02b33d89d 100644 --- a/website/docs/docs/use-dbt-semantic-layer/tableau.md +++ b/website/docs/docs/use-dbt-semantic-layer/tableau.md @@ -16,11 +16,12 @@ This integration provides a live connection to the dbt Semantic Layer through Ta ## Prerequisites -1. You must have [Tableau Desktop](https://www.tableau.com/en-gb/products/desktop) installed with version 2021.1 or greater +- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using the latest version. +- You must have [Tableau Desktop](https://www.tableau.com/en-gb/products/desktop) installed with version 2021.1 or greater - Note that Tableau Online does not currently support custom connectors natively. -2. Log in to Tableau Desktop using either your license or the login details you use for Tableau Server or Tableau Online. -3. You need your dbt Cloud host, [Environment ID](/docs/use-dbt-semantic-layer/setup-sl#set-up-dbt-semantic-layer) and [service token](/docs/dbt-cloud-apis/service-tokens) to log in. This account should be set up with the dbt Semantic Layer. -4. You must have a dbt Cloud Team or Enterprise [account](https://www.getdbt.com/pricing) and multi-tenant [deployment](/docs/cloud/about-cloud/regions-ip-addresses). (Single-Tenant coming soon) +- Log in to Tableau Desktop using either your license or the login details you use for Tableau Server or Tableau Online. +- You need your dbt Cloud host, [Environment ID](/docs/use-dbt-semantic-layer/setup-sl#set-up-dbt-semantic-layer) and [service token](/docs/dbt-cloud-apis/service-tokens) to log in. This account should be set up with the dbt Semantic Layer. +- You must have a dbt Cloud Team or Enterprise [account](https://www.getdbt.com/pricing) and multi-tenant [deployment](/docs/cloud/about-cloud/regions-ip-addresses). (Single-Tenant coming soon) ## Installing From d106cb5aaac4c5320c7df6afda44f8cf7dd37bb1 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 12:59:22 +0000 Subject: [PATCH 068/152] update api callout --- website/snippets/_legacy-sl-callout.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/snippets/_legacy-sl-callout.md b/website/snippets/_legacy-sl-callout.md index f45c6b68af3..1f1de95ce17 100644 --- a/website/snippets/_legacy-sl-callout.md +++ b/website/snippets/_legacy-sl-callout.md @@ -1,11 +1,11 @@ -:::important Upgrade to access the new dbt Semantic Layer +:::important Upgrade to access the latest dbt Semantic Layer -The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), improving governance, introducing a new API, and making it more efficient to define and query metrics. The legacy Semantic Layer, available in dbt v1.5 or lower, is no longer supported and won't receive any code fixes. +The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), introducing new features such as dbt Semantic Layer APIs. The APIs integrate with data applications, such as Tableau and Google Sheets, to query metrics and unlock insights. -**Who does this affect?** Anyone who uses the legacy Semantic Layer. The new Semantic Layer is available to [Team or Enterprise](https://www.getdbt.com/pricing/) multi-tenant dbt Cloud plans [hosted in North America](/docs/cloud/about-cloud/regions-ip-addresses) (more regions coming soon). You must be on dbt v1.6 or higher to access it. Users on dbt Cloud Developer plans or dbt Core users can use MetricFlow to only define and test metrics locally. +If you're reading this message, you're either using an older version of our documentation or currently use the legacy Semantic Layer. -**What’s changed?** The dbt_metrics package has been [deprecated](https://docs.getdbt.com/blog/deprecating-dbt-metrics) and replaced with [MetricFlow](/docs/build/about-metricflow?version=1.6), a new framework for defining metrics in dbt. This means dbt_metrics is no longer supported after dbt v1.5 and won't receive any code fixes. +As of December 15th, 2023, dbt Metrics and the legacy dbt Semantic Layer (v1.5 and lower) will no longer be supported. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. -**What should you do?** If you're using the legacy Semantic Layer, we **highly** recommend you [upgrade your dbt version](/docs/dbt-versions/upgrade-core-in-cloud) to dbt v1.6 or higher to use the new dbt Semantic Layer. To migrate to the new Semantic Layer, refer to the dedicated [migration guide](/guides/migration/sl-migration) for more info. +After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. ::: From e48c8214ba0b2d613470a956eb5118dbf06d08d7 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 16:14:44 +0000 Subject: [PATCH 069/152] update --- .../docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md | 2 +- website/docs/docs/use-dbt-semantic-layer/dbt-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/gsheets.md | 2 +- website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/setup-sl.md | 2 +- website/docs/docs/use-dbt-semantic-layer/sl-architecture.md | 2 +- website/docs/docs/use-dbt-semantic-layer/tableau.md | 2 +- .../{sl-deprecation-notice.md => _sl-deprecation-notice.md} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename website/snippets/{sl-deprecation-notice.md => _sl-deprecation-notice.md} (100%) diff --git a/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md b/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md index 4f009db1819..4f4621fa860 100644 --- a/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md +++ b/website/docs/docs/use-dbt-semantic-layer/avail-sl-integrations.md @@ -38,7 +38,7 @@ import AvailIntegrations from '/snippets/_sl-partner-links.md'; -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; diff --git a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md index b1a2c2ecaa9..9c9933340bd 100644 --- a/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/dbt-sl.md @@ -67,7 +67,7 @@ plan="dbt Cloud Team or Enterprise" -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; diff --git a/website/docs/docs/use-dbt-semantic-layer/gsheets.md b/website/docs/docs/use-dbt-semantic-layer/gsheets.md index 2bbba70f98e..cb9f4014803 100644 --- a/website/docs/docs/use-dbt-semantic-layer/gsheets.md +++ b/website/docs/docs/use-dbt-semantic-layer/gsheets.md @@ -13,7 +13,7 @@ The dbt Semantic Layer offers a seamless integration with Google Sheets through ## Prerequisites -- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using the latest version. +- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using dbt v1.6 or higher. - You have a Google account with access to Google Sheets. - You can install Google add-ons. - You have a dbt Cloud Environment ID and a [service token](/docs/dbt-cloud-apis/service-tokens) to authenticate with from a dbt Cloud account. diff --git a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md index f68b0e144d1..7e77c075ce6 100644 --- a/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/quickstart-sl.md @@ -114,7 +114,7 @@ User data passes through the Semantic Layer on its way back from the warehouse. -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; diff --git a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md index 6fc7a018b8b..9997e2d3647 100644 --- a/website/docs/docs/use-dbt-semantic-layer/setup-sl.md +++ b/website/docs/docs/use-dbt-semantic-layer/setup-sl.md @@ -39,7 +39,7 @@ import SlSetUp from '/snippets/_new-sl-setup.md'; -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; diff --git a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md index a1a19ead040..a44144f61d6 100644 --- a/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md +++ b/website/docs/docs/use-dbt-semantic-layer/sl-architecture.md @@ -56,7 +56,7 @@ The dbt Semantic Layer is proprietary; however, some components of the dbt Seman -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; diff --git a/website/docs/docs/use-dbt-semantic-layer/tableau.md b/website/docs/docs/use-dbt-semantic-layer/tableau.md index fe02b33d89d..8e6d7d8ed27 100644 --- a/website/docs/docs/use-dbt-semantic-layer/tableau.md +++ b/website/docs/docs/use-dbt-semantic-layer/tableau.md @@ -16,7 +16,7 @@ This integration provides a live connection to the dbt Semantic Layer through Ta ## Prerequisites -- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using the latest version. +- You have [configured the dbt Semantic Layer](/docs/use-dbt-semantic-layer/setup-sl) and are using dbt v1.6 or higher. - You must have [Tableau Desktop](https://www.tableau.com/en-gb/products/desktop) installed with version 2021.1 or greater - Note that Tableau Online does not currently support custom connectors natively. - Log in to Tableau Desktop using either your license or the login details you use for Tableau Server or Tableau Online. diff --git a/website/snippets/sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md similarity index 100% rename from website/snippets/sl-deprecation-notice.md rename to website/snippets/_sl-deprecation-notice.md From 9148b87836832f821434724bca77eb570153c3fa Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 16:21:34 +0000 Subject: [PATCH 070/152] fix snippet --- website/docs/docs/build/metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/build/metrics.md b/website/docs/docs/build/metrics.md index 6d4e0fec893..6686dadc356 100644 --- a/website/docs/docs/build/metrics.md +++ b/website/docs/docs/build/metrics.md @@ -7,7 +7,7 @@ keywords: tags: [Metrics] --- -import DeprecationNotice from '/snippets/sl-deprecation-notice.md'; +import DeprecationNotice from '/snippets/_sl-deprecation-notice.md'; From cfab9a4eb5e766ad672c3c5620d10da771da2a32 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Thu, 9 Nov 2023 16:55:55 +0000 Subject: [PATCH 071/152] will's feedback --- website/snippets/_legacy-sl-callout.md | 7 +++---- website/snippets/_sl-deprecation-notice.md | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/website/snippets/_legacy-sl-callout.md b/website/snippets/_legacy-sl-callout.md index 1f1de95ce17..61d3a1c910f 100644 --- a/website/snippets/_legacy-sl-callout.md +++ b/website/snippets/_legacy-sl-callout.md @@ -1,11 +1,10 @@ :::important Upgrade to access the latest dbt Semantic Layer -The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), introducing new features such as dbt Semantic Layer APIs. The APIs integrate with data applications, such as Tableau and Google Sheets, to query metrics and unlock insights. +The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), introducing new features such as dbt Semantic Layer APIs. The APIs integrate with data applications, such as Tableau and Google Sheets, to query metrics and unlock insights. -If you're reading this message, you're either using an older version of our documentation or currently use the legacy Semantic Layer. - -As of December 15th, 2023, dbt Metrics and the legacy dbt Semantic Layer (v1.5 and lower) will no longer be supported. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. +For users on dbt version 1.5 or earlier, starting on December 15th, 2023, support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. ::: +support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index ec49fd28fbf..8d107f39fff 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,7 +1,5 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer -If you're reading this message, you're either using an older version of our documentation or currently use the legacy Semantic Layer. - -As of December 15th, 2023, dbt Metrics and the legacy dbt Semantic Layer (v1.5 and lower) will no longer be supported. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. +For users on dbt version 1.5 or earlier — starting on December 15th, 2023, support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. From 59b506117cc6330fb06d42adc0056b72b46f4fba Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 11:48:24 -0800 Subject: [PATCH 072/152] fixing links --- .../dbt-unity-catalog-best-practices.md | 8 ++++---- website/docs/community/resources/getting-help.md | 2 +- website/docs/docs/cloud/billing.md | 2 +- website/docs/docs/connect-adapters.md | 2 +- website/docs/docs/contribute-core-adapters.md | 4 ++-- .../core-upgrade/07-upgrading-to-v1.1.md | 2 +- website/docs/docs/dbt-versions/core-versions.md | 2 +- .../04-Sept-2023/ci-updates-phase2-rn.md | 2 +- .../04-Sept-2023/product-docs-summer-rn.md | 2 +- .../dbt-databricks-unity-catalog-support.md | 2 +- website/docs/docs/deploy/ci-jobs.md | 2 +- website/docs/docs/supported-data-platforms.md | 2 +- website/docs/docs/trusted-adapters.md | 2 +- website/docs/docs/verified-adapters.md | 2 +- website/docs/guides/adapter-creation.md | 5 ++--- website/docs/guides/dbt-models-on-databricks.md | 4 ++-- .../productionize-your-dbt-databricks-project.md | 16 ++++++++-------- website/docs/guides/set-up-ci.md | 6 +++--- .../guides/set-up-your-databricks-dbt-project.md | 2 +- website/docs/reference/commands/init.md | 2 +- website/docs/reference/events-logging.md | 2 +- .../reference/resource-configs/no-configs.md | 2 +- 22 files changed, 37 insertions(+), 38 deletions(-) diff --git a/website/docs/best-practices/dbt-unity-catalog-best-practices.md b/website/docs/best-practices/dbt-unity-catalog-best-practices.md index 0d24cc320ec..89153fe1b86 100644 --- a/website/docs/best-practices/dbt-unity-catalog-best-practices.md +++ b/website/docs/best-practices/dbt-unity-catalog-best-practices.md @@ -60,9 +60,9 @@ Ready to start transforming your Unity Catalog datasets with dbt? Check out the resources below for guides, tips, and best practices: -- [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) +- [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) - [Self-paced dbt fundamentals training videos](https://courses.getdbt.com/courses/fundamentals) -- [Customizing CI/CD](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/1-cicd-background) & [SQL linting](https://docs.getdbt.com/guides/orchestration/custom-cicd-pipelines/2-lint-on-push) -- [Debugging errors](https://docs.getdbt.com/best-practices/debugging-errors) -- [Writing custom generic tests](https://docs.getdbt.com/best-practices/writing-custom-generic-tests) +- [Customizing CI/CD](/guides/custom-cicd-pipelines) +- [Debugging errors](/guides/debug-errors) +- [Writing custom generic tests](/best-practices/writing-custom-generic-tests) - [dbt packages hub](https://hub.getdbt.com/) diff --git a/website/docs/community/resources/getting-help.md b/website/docs/community/resources/getting-help.md index 658f7d154db..2f30644186e 100644 --- a/website/docs/community/resources/getting-help.md +++ b/website/docs/community/resources/getting-help.md @@ -9,7 +9,7 @@ dbt is open source, and has a generous community behind it. Asking questions wel #### Search the existing documentation The docs site you're on is highly searchable, make sure to explore for the answer here as a first step. If you're new to dbt, try working through the [quickstart guide](/guides) first to get a firm foundation on the essential concepts. #### Try to debug the issue yourself -We have a handy guide on [debugging errors](/best-practices/debugging-errors) to help out! This guide also helps explain why errors occur, and which docs you might need to search for help. +We have a handy guide on [debugging errors](/guides/debug-errors) to help out! This guide also helps explain why errors occur, and which docs you might need to search for help. #### Search for answers using your favorite search engine We're committed to making more errors searchable, so it's worth checking if there's a solution already out there! Further, some errors related to installing dbt, the SQL in your models, or getting YAML right, are errors that are not-specific to dbt, so there may be other resources to check. diff --git a/website/docs/docs/cloud/billing.md b/website/docs/docs/cloud/billing.md index ef3eb00a3c6..6853cc0004b 100644 --- a/website/docs/docs/cloud/billing.md +++ b/website/docs/docs/cloud/billing.md @@ -237,7 +237,7 @@ To understand better how long each model takes to run within the context of a sp Once you've identified which models could be optimized, check out these other resources that walk through how to optimize your work: * [Build scalable and trustworthy data pipelines with dbt and BigQuery](https://services.google.com/fh/files/misc/dbt_bigquery_whitepaper.pdf) * [Best Practices for Optimizing Your dbt and Snowflake Deployment](https://www.snowflake.com/wp-content/uploads/2021/10/Best-Practices-for-Optimizing-Your-dbt-and-Snowflake-Deployment.pdf) -* [How to optimize and troubleshoot dbt models on Databricks](/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks) +* [How to optimize and troubleshoot dbt models on Databricks](/guides/optimize-dbt-models-on-databricks) ## FAQs diff --git a/website/docs/docs/connect-adapters.md b/website/docs/docs/connect-adapters.md index 77ead34e51d..e301cfc237e 100644 --- a/website/docs/docs/connect-adapters.md +++ b/website/docs/docs/connect-adapters.md @@ -3,7 +3,7 @@ title: "How to connect to adapters" id: "connect-adapters" --- -Adapters are an essential component of dbt. At their most basic level, they are how dbt connects with the various supported data platforms. At a higher-level, adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt — for more detail, read the [What are adapters](/guides/dbt-ecosystem/adapter-development/1-what-are-adapters) guide. +Adapters are an essential component of dbt. At their most basic level, they are how dbt connects with the various supported data platforms. At a higher-level, adapters strive to give analytics engineers more transferrable skills as well as standardize how analytics projects are structured. Gone are the days where you have to learn a new language or flavor of SQL when you move to a new job that has a different data platform. That is the power of adapters in dbt — for more detail, refer to the [Build, test, document, and promote adapters](/guides/adapter-creation) guide. This section provides more details on different ways you can connect dbt to an adapter, and explains what a maintainer is. diff --git a/website/docs/docs/contribute-core-adapters.md b/website/docs/docs/contribute-core-adapters.md index 553361ee1a2..d3b1edf2a38 100644 --- a/website/docs/docs/contribute-core-adapters.md +++ b/website/docs/docs/contribute-core-adapters.md @@ -17,6 +17,6 @@ Community-supported plugins are works in progress, and anyone is welcome to cont ### Create a new adapter -If you see something missing from the lists above, and you're interested in developing an integration, read more about adapters and how they're developed in the [Adapter Development](/guides/dbt-ecosystem/adapter-development/1-what-are-adapters) section. +If you see something missing from the lists above, and you're interested in developing an integration, read more about adapters and how they're developed in the [Build, test, document, and promote adapters](/guides/adapter-creation). -If you have a new adapter, please add it to this list using a pull request! See [Documenting your adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) for more information. +If you have a new adapter, please add it to this list using a pull request! You can refer to [Build, test, document, and promote adapters](/guides/adapter-creation) for more information on documenting your adapter. diff --git a/website/docs/docs/dbt-versions/core-upgrade/07-upgrading-to-v1.1.md b/website/docs/docs/dbt-versions/core-upgrade/07-upgrading-to-v1.1.md index 7819709558e..403264a46e6 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/07-upgrading-to-v1.1.md +++ b/website/docs/docs/dbt-versions/core-upgrade/07-upgrading-to-v1.1.md @@ -21,7 +21,7 @@ There are no breaking changes for code in dbt projects and packages. We are comm ### For maintainers of adapter plugins -We have reworked the testing suite for adapter plugin functionality. For details on the new testing suite, see: [Testing a new adapter](/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter). +We have reworked the testing suite for adapter plugin functionality. For details on the new testing suite, refer to the "Test your adapter" step in the [Build, test, document, and promote adapters](/guides/adapter-creation) guide. The abstract methods `get_response` and `execute` now only return `connection.AdapterReponse` in type hints. Previously, they could return a string. We encourage you to update your methods to return an object of class `AdapterResponse`, or implement a subclass specific to your adapter. This also gives you the opportunity to add fields specific to your adapter's query execution, such as `rows_affected` or `bytes_processed`. diff --git a/website/docs/docs/dbt-versions/core-versions.md b/website/docs/docs/dbt-versions/core-versions.md index 5e8e437f0b1..2467f3c946b 100644 --- a/website/docs/docs/dbt-versions/core-versions.md +++ b/website/docs/docs/dbt-versions/core-versions.md @@ -84,7 +84,7 @@ Like many software projects, dbt Core releases follow [semantic versioning](http We are committed to avoiding breaking changes in minor versions for end users of dbt. There are two types of breaking changes that may be included in minor versions: -- Changes to the [Python interface for adapter plugins](/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter). These changes are relevant _only_ to adapter maintainers, and they will be clearly communicated in documentation and release notes. +- Changes to the Python interface for adapter plugins. These changes are relevant _only_ to adapter maintainers, and they will be clearly communicated in documentation and release notes. For more information, refer to [Build, test, document, and promote adapters](/guides/adapter-creation) guide. - Changes to metadata interfaces, including [artifacts](/docs/deploy/artifacts) and [logging](/reference/events-logging), signalled by a version bump. Those version upgrades may require you to update external code that depends on these interfaces, or to coordinate upgrades between dbt orchestrations that share metadata, such as [state-powered selection](/reference/node-selection/syntax#about-node-selection). ### How we version adapter plugins diff --git a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/ci-updates-phase2-rn.md b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/ci-updates-phase2-rn.md index fd2d163b748..a8ae1ade65b 100644 --- a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/ci-updates-phase2-rn.md +++ b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/ci-updates-phase2-rn.md @@ -29,7 +29,7 @@ Below is a comparison table that describes how deploy jobs and CI jobs behave di ## What you need to update -- If you want to set up a CI environment for your jobs, dbt Labs recommends that you create your CI job in a dedicated [deployment environment](/docs/deploy/deploy-environments#create-a-deployment-environment) that's connected to a staging database. To learn more about these environment best practices, refer to the guide [Get started with continuous integration tests](/guides/orchestration/set-up-ci/overview). +- If you want to set up a CI environment for your jobs, dbt Labs recommends that you create your CI job in a dedicated [deployment environment](/docs/deploy/deploy-environments#create-a-deployment-environment) that's connected to a staging database. To learn more about these environment best practices, refer to the guide [Get started with continuous integration tests](/guides/set-up-ci). - If you had set up a CI job before October 2, 2023, the job might've been misclassified as a deploy job with this update. Below describes how to fix the job type: diff --git a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md index 2739ef2b7aa..e8fb9539c50 100644 --- a/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md +++ b/website/docs/docs/dbt-versions/release-notes/04-Sept-2023/product-docs-summer-rn.md @@ -40,4 +40,4 @@ You can provide feedback by opening a pull request or issue in [our repo](https: ## New 📚 Guides, ✏️ blog posts, and FAQs * Check out how these community members use the dbt community in the [Community spotlight](/community/spotlight). * Blog posts published this summer include [Optimizing Materialized Views with dbt](/blog/announcing-materialized-views), [Data Vault 2.0 with dbt Cloud](/blog/data-vault-with-dbt-cloud), and [Create dbt Documentation and Tests 10x faster with ChatGPT](/blog/create-dbt-documentation-10x-faster-with-chatgpt) -* We now have two new best practice guides: [How we build our metrics](/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) and [Set up Continuous Integration](/guides/orchestration/set-up-ci/overview). +- We now have two new best practice guides: [How we build our metrics](/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) and [Set up Continuous Integration](/guides/set-up-ci). diff --git a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md index 25d5ca5205f..ee46cb5f558 100644 --- a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md +++ b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md @@ -8,6 +8,6 @@ tags: [Nov-2022, v1.1.66.15] dbt Cloud is the easiest and most reliable way to develop and deploy a dbt project. It helps remove complexity while also giving you more features and better performance. A simpler Databricks connection experience with support for Databricks’ Unity Catalog and better modeling defaults is now available for your use. -For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](https://docs.getdbt.com/guides/migration/tools/migrating-from-spark-to-databricks#migration) your connection to the [dbt-databricks adapter](https://docs.getdbt.com/reference/warehouse-setups/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. +For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](/guides/migrate-from-spark-to-databricks) your connection to the [dbt-databricks adapter](https://docs.getdbt.com/reference/warehouse-setups/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. Check out our [live blog post](https://www.getdbt.com/blog/dbt-cloud-databricks-experience/) to learn more. diff --git a/website/docs/docs/deploy/ci-jobs.md b/website/docs/docs/deploy/ci-jobs.md index d10bc780fc2..6114ed1ca14 100644 --- a/website/docs/docs/deploy/ci-jobs.md +++ b/website/docs/docs/deploy/ci-jobs.md @@ -9,7 +9,7 @@ You can set up [continuous integration](/docs/deploy/continuous-integration) (CI ## Set up CI jobs {#set-up-ci-jobs} -dbt Labs recommends that you create your CI job in a dedicated dbt Cloud [deployment environment](/docs/deploy/deploy-environments#create-a-deployment-environment) that's connected to a staging database. Having a separate environment dedicated for CI will provide better isolation between your temporary CI schema builds and your production data builds. Additionally, sometimes teams need their CI jobs to be triggered when a PR is made to a branch other than main. If your team maintains a staging branch as part of your release process, having a separate environment will allow you to set a [custom branch](/faqs/environments/custom-branch-settings) and, accordingly, the CI job in that dedicated environment will be triggered only when PRs are made to the specified custom branch. To learn more, refer to [Get started with CI tests](/guides/orchestration/set-up-ci/overview). +dbt Labs recommends that you create your CI job in a dedicated dbt Cloud [deployment environment](/docs/deploy/deploy-environments#create-a-deployment-environment) that's connected to a staging database. Having a separate environment dedicated for CI will provide better isolation between your temporary CI schema builds and your production data builds. Additionally, sometimes teams need their CI jobs to be triggered when a PR is made to a branch other than main. If your team maintains a staging branch as part of your release process, having a separate environment will allow you to set a [custom branch](/faqs/environments/custom-branch-settings) and, accordingly, the CI job in that dedicated environment will be triggered only when PRs are made to the specified custom branch. To learn more, refer to [Get started with CI tests](/guides/set-up-ci). ### Prerequisites - You have a dbt Cloud account. diff --git a/website/docs/docs/supported-data-platforms.md b/website/docs/docs/supported-data-platforms.md index a8e146f49d0..c0c9a30db36 100644 --- a/website/docs/docs/supported-data-platforms.md +++ b/website/docs/docs/supported-data-platforms.md @@ -8,7 +8,7 @@ pagination_next: "docs/connect-adapters" pagination_prev: null --- -dbt connects to and runs SQL against your database, warehouse, lake, or query engine. These SQL-speaking platforms are collectively referred to as _data platforms_. dbt connects with data platforms by using a dedicated adapter plugin for each. Plugins are built as Python modules that dbt Core discovers if they are installed on your system. Read [What are Adapters](/guides/dbt-ecosystem/adapter-development/1-what-are-adapters) for more info. +dbt connects to and runs SQL against your database, warehouse, lake, or query engine. These SQL-speaking platforms are collectively referred to as _data platforms_. dbt connects with data platforms by using a dedicated adapter plugin for each. Plugins are built as Python modules that dbt Core discovers if they are installed on your system. Refer to the [Build, test, document, and promote adapters](/guides/adapter-creation) guide. for more info. You can [connect](/docs/connect-adapters) to adapters and data platforms natively in dbt Cloud or install them manually using dbt Core. diff --git a/website/docs/docs/trusted-adapters.md b/website/docs/docs/trusted-adapters.md index 08191e8ea42..20d61f69575 100644 --- a/website/docs/docs/trusted-adapters.md +++ b/website/docs/docs/trusted-adapters.md @@ -21,7 +21,7 @@ pendency on this library? ### Trusted adapter specifications -See [Building a Trusted Adapter](/guides/dbt-ecosystem/adapter-development/8-building-a-trusted-adapter) for more information, particularly if you are an adapter maintainer considering having your adapter be added to the trusted list. +Refer to the [Build, test, document, and promote adapters](/guides/adapter-creation) guide for more information, particularly if you are an adapter maintainer considering having your adapter be added to the trusted list. ### Trusted vs Verified diff --git a/website/docs/docs/verified-adapters.md b/website/docs/docs/verified-adapters.md index 170bc8f885b..75c7529c247 100644 --- a/website/docs/docs/verified-adapters.md +++ b/website/docs/docs/verified-adapters.md @@ -11,7 +11,7 @@ These adapters then earn a "Verified" status so that users can have a certain le The verification process serves as the on-ramp to integration with dbt Cloud. As such, we restrict applicants to data platform vendors with whom we are already engaged. -To learn more, see [Verifying a new adapter](/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter). +To learn more, refer to the [Build, test, document, and promote adapters](/guides/adapter-creation) guide. import MSCallout from '/snippets/_microsoft-adapters-soon.md'; diff --git a/website/docs/guides/adapter-creation.md b/website/docs/guides/adapter-creation.md index cd18a413b10..6c9d575bae2 100644 --- a/website/docs/guides/adapter-creation.md +++ b/website/docs/guides/adapter-creation.md @@ -164,8 +164,7 @@ We strongly encourage you to adopt the following approach when versioning and re This step will walk you through the first creating the necessary adapter classes and macros, and provide some resources to help you validate that your new adapter is working correctly. Make sure you've familiarized yourself with the previous steps in this guide. -Once the adapter is passing most of the functional tests (see ["Testing a new adapter"](4-testing-a-new-adapter) -), please let the community know that is available to use by adding the adapter to the ["Supported Data Platforms"](/docs/supported-data-platforms) page by following the steps given in [Documenting your adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter). +Once the adapter is passing most of the functional tests in the previous "Testing a new adapter" step, please let the community know that is available to use by adding the adapter to the ["Supported Data Platforms"](/docs/supported-data-platforms) page by following the steps given in "Documenting your adapter. For any questions you may have, don't hesitate to ask in the [#adapter-ecosystem](https://getdbt.slack.com/archives/C030A0UF5LM) Slack channel. The community is very helpful and likely has experienced a similar issue as you. @@ -1294,7 +1293,7 @@ Essential functionality includes (but is not limited to the following features): The adapter should have the required documentation for connecting and configuring the adapter. The dbt docs site should be the single source of truth for this information. These docs should be kept up-to-date. -See [Documenting a new adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) for more information. +Proceed to the "Document a new adapter" step for more information. ### Release Cadence diff --git a/website/docs/guides/dbt-models-on-databricks.md b/website/docs/guides/dbt-models-on-databricks.md index d1a55915777..f26b7253be9 100644 --- a/website/docs/guides/dbt-models-on-databricks.md +++ b/website/docs/guides/dbt-models-on-databricks.md @@ -14,7 +14,7 @@ recently_updated: true ## Introduction -Continuing our Databricks and dbt guide series from the last [guide](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project), it’s time to talk about performance optimization. In this follow-up post,  we outline simple strategies to optimize for cost, performance, and simplicity when architecting your data pipelines. We’ve encapsulated these strategies in this acronym-framework: +Building on the [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project) guide, we'd like to discuss performance optimization. In this follow-up post,  we outline simple strategies to optimize for cost, performance, and simplicity when you architect data pipelines. We’ve encapsulated these strategies in this acronym-framework: - Platform Components - Patterns & Best Practices @@ -177,6 +177,6 @@ With the [dbt Cloud Admin API](/docs/dbt-cloud-apis/admin-cloud-api), you can  ### Conclusion -This concludes the second guide in our series on “Working with Databricks and dbt”, following [How to set up your Databricks and dbt Project](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project). +This builds on the content in [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project). We welcome you to try these strategies on our example open source TPC-H implementation and to provide us with thoughts/feedback as you start to incorporate these features into production. Looking forward to your feedback on [#db-databricks-and-spark](https://getdbt.slack.com/archives/CNGCW8HKL) Slack channel! diff --git a/website/docs/guides/productionize-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md index fdadcef6d34..f26a132919b 100644 --- a/website/docs/guides/productionize-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -18,10 +18,10 @@ Welcome to the third installment of our comprehensive series on optimizing and d ### Prerequisites -If you don't have any of the following requirements, refer to the instructions in the [setup guide](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project) to catch up: +If you don't have any of the following requirements, refer to the instructions in the [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project) for help meeting these requirements: -- You have [set up your Databricks and dbt Cloud environments](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project). -- You have [optimized your dbt models for peak performance](/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks). +- You have [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project). +- You have [optimized your dbt models for peak performance](/guides/optimize-dbt-models-on-databricks). - You have created two catalogs in Databricks: *dev* and *prod*. - You have created Databricks Service Principal to run your production jobs. - You have at least one [deployment environment](/docs/deploy/deploy-environments) in dbt Cloud. @@ -52,7 +52,7 @@ Let’s [create a job](/docs/deploy/deploy-jobs#create-and-schedule-jobs) in dbt 1. Create a new job by clicking **Deploy** in the header, click **Jobs** and then **Create job**. 2. **Name** the job “Daily refresh”. 3. Set the **Environment** to your *production* environment. - - This will allow the job to inherit the catalog, schema, credentials, and environment variables defined in the [setup guide](https://docs.getdbt.com/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project#defining-your-dbt-deployment-environment). + - This will allow the job to inherit the catalog, schema, credentials, and environment variables defined in [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project). 4. Under **Execution Settings** - Check the **Generate docs on run** checkbox to configure the job to automatically generate project docs each time this job runs. This will ensure your documentation stays evergreen as models are added and modified. - Select the **Run on source freshness** checkbox to configure dbt [source freshness](/docs/deploy/source-freshness) as the first step of this job. Your sources will need to be configured to [snapshot freshness information](/docs/build/sources#snapshotting-source-data-freshness) for this to drive meaningful insights. @@ -87,7 +87,7 @@ dbt allows you to write [tests](/docs/build/tests) for your data pipeline, which 2. **Development**: Running tests during development ensures that your code changes do not break existing assumptions, enabling developers to iterate faster by catching problems immediately after writing code. 3. **CI checks**: Automated CI jobs run and test your pipeline end-to end when a pull request is created, providing confidence to developers, code reviewers, and end users that the proposed changes are reliable and will not cause disruptions or data quality issues -Your CI job will ensure that the models build properly and pass any tests applied to them. We recommend creating a separate *test* environment and having a dedicated service principal. This will ensure the temporary schemas created during CI tests are in their own catalog and cannot unintentionally expose data to other users. Repeat the [steps](/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project) used to create your *prod* environment to create a *test* environment. After setup, you should have: +Your CI job will ensure that the models build properly and pass any tests applied to them. We recommend creating a separate *test* environment and having a dedicated service principal. This will ensure the temporary schemas created during CI tests are in their own catalog and cannot unintentionally expose data to other users. Repeat the steps in [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project) to create your *prod* environment to create a *test* environment. After setup, you should have: - A catalog called *test* - A service principal called *dbt_test_sp* @@ -130,13 +130,13 @@ The five key steps for troubleshooting dbt Cloud issues are: 3. Isolate the problem by running one model at a time in the IDE or undoing the code that caused the issue. 4. Check for problems in compiled files and logs. -Consult the [Debugging errors documentation](/best-practices/debugging-errors) for a comprehensive list of error types and diagnostic methods. +Consult the [Debugging errors documentation](/guides/debug-errors) for a comprehensive list of error types and diagnostic methods. To troubleshoot issues with a dbt Cloud job, navigate to the "Deploy > Run History" tab in your dbt Cloud project and select the failed run. Then, expand the run steps to view [console and debug logs](/docs/deploy/run-visibility#access-logs) to review the detailed log messages. To obtain additional information, open the Artifacts tab and download the compiled files associated with the run. If your jobs are taking longer than expected, use the [model timing](/docs/deploy/run-visibility#model-timing) dashboard to identify bottlenecks in your pipeline. Analyzing the time taken for each model execution helps you pinpoint the slowest components and optimize them for better performance. The Databricks [Query History](https://docs.databricks.com/sql/admin/query-history.html) lets you inspect granular details such as time spent in each task, rows returned, I/O performance, and execution plan. -For more on performance tuning, see our guide on [How to Optimize and Troubleshoot dbt Models on Databricks](/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks). +For more on performance tuning, see our guide on [How to Optimize and Troubleshoot dbt Models on Databricks](/guides/optimize-dbt-models-on-databricks). ## Advanced considerations @@ -160,7 +160,7 @@ To trigger your dbt Cloud job from Databricks, follow the instructions in our [D ## Data masking -Our [Best Practices for dbt and Unity Catalog](/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices) guide recommends using separate catalogs *dev* and *prod* for development and deployment environments, with Unity Catalog and dbt Cloud handling configurations and permissions for environment isolation. Ensuring security while maintaining efficiency in your development and deployment environments is crucial. Additional security measures may be necessary to protect sensitive data, such as personally identifiable information (PII). +Our [Best Practices for dbt and Unity Catalog](/best-practices/dbt-unity-catalog-best-practices) guide recommends using separate catalogs *dev* and *prod* for development and deployment environments, with Unity Catalog and dbt Cloud handling configurations and permissions for environment isolation. Ensuring security while maintaining efficiency in your development and deployment environments is crucial. Additional security measures may be necessary to protect sensitive data, such as personally identifiable information (PII). Databricks leverages [Dynamic Views](https://docs.databricks.com/data-governance/unity-catalog/create-views.html#create-a-dynamic-view) to enable data masking based on group membership. Because views in Unity Catalog use Spark SQL, you can implement advanced data masking by using more complex SQL expressions and regular expressions. You can now also apply fine grained access controls like row filters in preview and column masks in preview on tables in Databricks Unity Catalog, which will be the recommended approach to protect sensitive data once this goes GA. Additionally, in the near term, Databricks Unity Catalog will also enable Attribute Based Access Control natively, which will make protecting sensitive data at scale simpler. diff --git a/website/docs/guides/set-up-ci.md b/website/docs/guides/set-up-ci.md index c6bcf316952..83362094ec6 100644 --- a/website/docs/guides/set-up-ci.md +++ b/website/docs/guides/set-up-ci.md @@ -54,7 +54,7 @@ To be able to find modified nodes, dbt needs to have something to compare agains ### 3. Test your process -That's it! There are other steps you can take to be even more confident in your work, such as [validating your structure follows best practices](/guides/orchestration/set-up-ci/run-dbt-project-evaluator) and [linting your code](/guides/orchestration/set-up-ci/lint-on-push), but this covers the most critical checks. +That's it! There are other steps you can take to be even more confident in your work, such as validating your structure follows best practices and linting your code. For more information, refer to [Get started with Continuous Integration tests](/guides/set-up-ci). To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft). Within a few seconds, you’ll see a new check appear in your git provider. @@ -313,7 +313,7 @@ The git flow will look like this: ### Advanced prerequisites -- You have the **Development**, **CI**, and **Production** environments, as described in [the Baseline setup](/guides/orchestration/set-up-ci/in-15-minutes). +- You have the **Development**, **CI**, and **Production** environments, as described in [the Baseline setup](/guides/set-up-ci). ### 1. Create a `release` branch in your git repo @@ -350,6 +350,6 @@ Adding a regularly-scheduled job inside of the QA environment whose only command ### 5. Test your process -When the Release Manager is ready to cut a new release, they will manually open a PR from `qa` into `main` from their git provider (e.g. GitHub, GitLab, Azure DevOps). dbt Cloud will detect the new PR, at which point the existing check in the CI environment will trigger and run. When using the [baseline configuration](/guides/orchestration/set-up-ci/in-15-minutes), it's possible to kick off the PR creation from inside of the dbt Cloud IDE. Under this paradigm, that button will create PRs targeting your QA branch instead. +When the Release Manager is ready to cut a new release, they will manually open a PR from `qa` into `main` from their git provider (e.g. GitHub, GitLab, Azure DevOps). dbt Cloud will detect the new PR, at which point the existing check in the CI environment will trigger and run. When using the [baseline configuration](/guides/set-up-ci), it's possible to kick off the PR creation from inside of the dbt Cloud IDE. Under this paradigm, that button will create PRs targeting your QA branch instead. To test your new flow, create a new branch in the dbt Cloud IDE then add a new file or modify an existing one. Commit it, then create a new Pull Request (not a draft) against your `qa` branch. You'll see the integration tests begin to run. Once they complete, manually create a PR against `main`, and within a few seconds you’ll see the tests run again but this time incorporating all changes from all code that hasn't been merged to main yet. diff --git a/website/docs/guides/set-up-your-databricks-dbt-project.md b/website/docs/guides/set-up-your-databricks-dbt-project.md index e40a4182423..d378b57cacc 100644 --- a/website/docs/guides/set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/set-up-your-databricks-dbt-project.md @@ -68,7 +68,7 @@ We are not covering python in this post but if you want to learn more, check out Now that the Databricks components are in place, we can configure our dbt project. This involves connecting dbt to our Databricks SQL warehouse to run SQL queries and using a version control system like GitHub to store our transformation code. -If you are migrating an existing dbt project from the dbt-spark adapter to dbt-databricks, follow this [migration guide](https://docs.getdbt.com/guides/migration/tools/migrating-from-spark-to-databricks#migration) to switch adapters without needing to update developer credentials and other existing configs. +If you are migrating an existing dbt project from the dbt-spark adapter to dbt-databricks, follow this [migration guide](/guides/migrate-from-spark-to-databricks) to switch adapters without needing to update developer credentials and other existing configs. If you’re starting a new dbt project, follow the steps below. For a more detailed setup flow, check out our [quickstart guide.](/guides/databricks) diff --git a/website/docs/reference/commands/init.md b/website/docs/reference/commands/init.md index ac55717c0ec..e9cc2ccba4e 100644 --- a/website/docs/reference/commands/init.md +++ b/website/docs/reference/commands/init.md @@ -36,7 +36,7 @@ If you've just cloned or downloaded an existing dbt project, `dbt init` can stil `dbt init` knows how to prompt for connection information by looking for a file named `profile_template.yml`. It will look for this file in two places: -- **Adapter plugin:** What's the bare minumum Postgres profile? What's the type of each field, what are its defaults? This information is stored in a file called [`dbt/include/postgres/profile_template.yml`](https://github.com/dbt-labs/dbt-core/blob/main/plugins/postgres/dbt/include/postgres/profile_template.yml). If you're the maintainer of an adapter plugin, we highly recommend that you add a `profile_template.yml` to your plugin, too. See more details in [building-a-new-adapter](/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter). +- **Adapter plugin:** What's the bare minumum Postgres profile? What's the type of each field, what are its defaults? This information is stored in a file called [`dbt/include/postgres/profile_template.yml`](https://github.com/dbt-labs/dbt-core/blob/main/plugins/postgres/dbt/include/postgres/profile_template.yml). If you're the maintainer of an adapter plugin, we highly recommend that you add a `profile_template.yml` to your plugin, too. Refer to the [Build, test, document, and promote adapters](/guides/adapter-creation) guide for more information. - **Existing project:** If you're the maintainer of an existing project, and you want to help new users get connected to your database quickly and easily, you can include your own custom `profile_template.yml` in the root of your project, alongside `dbt_project.yml`. For common connection attributes, set the values in `fixed`; leave user-specific attributes in `prompts`, but with custom hints and defaults as you'd like. diff --git a/website/docs/reference/events-logging.md b/website/docs/reference/events-logging.md index 94b865fad0d..ffdeb7bb752 100644 --- a/website/docs/reference/events-logging.md +++ b/website/docs/reference/events-logging.md @@ -4,7 +4,7 @@ title: "Events and logs" As dbt runs, it generates events. The most common way to see those events is as log messages, written in real time to two places: - The command line terminal (`stdout`), to provide interactive feedback while running dbt. -- The debug log file (`logs/dbt.log`), to enable detailed [debugging of errors](/best-practices/debugging-errors) when they occur. The text-formatted log messages in this file include all `DEBUG`-level events, as well as contextual information, such as log level and thread name. The location of this file can be configured via [the `log_path` config](/reference/project-configs/log-path). +- The debug log file (`logs/dbt.log`), to enable detailed [debugging of errors](/guides/debug-errors) when they occur. The text-formatted log messages in this file include all `DEBUG`-level events, as well as contextual information, such as log level and thread name. The location of this file can be configured via [the `log_path` config](/reference/project-configs/log-path). diff --git a/website/docs/reference/resource-configs/no-configs.md b/website/docs/reference/resource-configs/no-configs.md index 5a4ba4eaaa2..5eec26917c8 100644 --- a/website/docs/reference/resource-configs/no-configs.md +++ b/website/docs/reference/resource-configs/no-configs.md @@ -8,4 +8,4 @@ If you were guided to this page from a data platform setup article, it most like - Setting up the profile is the only action the end-user needs to take on the data platform, or - The subsequent actions the end-user needs to take are not currently documented -If you'd like to contribute to data platform-specifc configuration information, refer to [Documenting a new adapter](/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter) \ No newline at end of file +If you'd like to contribute to data platform-specific configuration information, refer to [Documenting a new adapter](/guides/adapter-creation) From 26338c0f55b04465b346544e5d8cdf2e92155a31 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:19:23 -0800 Subject: [PATCH 073/152] fixing more links --- .../materializations-guide-7-conclusion.md | 2 +- website/docs/docs/cloud/billing.md | 2 +- website/docs/docs/dbt-cloud-apis/sl-jdbc.md | 2 +- .../dbt-versions/core-upgrade/08-upgrading-to-v1.0.md | 2 +- .../release-notes/07-June-2023/product-docs-jun.md | 2 +- .../release-notes/09-April-2023/product-docs.md | 8 ++++---- website/docs/docs/deploy/deployment-tools.md | 2 +- website/docs/faqs/Models/available-materializations.md | 2 +- website/docs/faqs/Project/why-not-write-dml.md | 2 +- website/docs/faqs/Warehouse/db-connection-dbt-compile.md | 2 +- website/docs/guides/dbt-python-snowpark.md | 4 ++-- website/docs/guides/migrate-from-spark-to-databricks.md | 2 +- .../guides/productionize-your-dbt-databricks-project.md | 2 +- website/docs/guides/refactoring-legacy-sql.md | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md b/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md index c0c4e023a55..cd561716fe4 100644 --- a/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md +++ b/website/docs/best-practices/materializations/materializations-guide-7-conclusion.md @@ -9,6 +9,6 @@ hoverSnippet: Read this conclusion to our guide on using materializations in dbt You're now following best practices in your project, and have optimized the materializations of your DAG. You’re equipped with the 3 main materializations that cover almost any analytics engineering situation! -There are more configs and materializations available, as well as specific materializations for certain platforms and adapters — and like everything with dbt, materializations are extensible, meaning you can create your own [custom materializations](/guides/creating-new-materializations) for your needs. So this is just the beginning of what you can do with these powerful configurations. +There are more configs and materializations available, as well as specific materializations for certain platforms and adapters — and like everything with dbt, materializations are extensible, meaning you can create your own [custom materializations](/guides/create-new-materializations) for your needs. So this is just the beginning of what you can do with these powerful configurations. For the vast majority of users and companies though, tables, views, and incremental models will handle everything you can throw at them. Develop your intuition and expertise for these materializations, and you’ll be well on your way to tackling advanced analytics engineering problems. diff --git a/website/docs/docs/cloud/billing.md b/website/docs/docs/cloud/billing.md index 6853cc0004b..f66e2aad363 100644 --- a/website/docs/docs/cloud/billing.md +++ b/website/docs/docs/cloud/billing.md @@ -215,7 +215,7 @@ If you want to ensure that you're building views whenever the logic is changed, Executing `dbt build` in this context is unnecessary because the CI job was used to both run and test the code that just got merged into main. 5. Under the **Execution Settings**, select the default production job to compare changes against: - **Defer to a previous run state** — Select the “Merge Job” you created so the job compares and identifies what has changed since the last merge. -6. In your dbt project, follow the steps in [Run a dbt Cloud job on merge](/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge) to create a script to trigger the dbt Cloud API to run your job after a merge happens within your git repository or watch this [video](https://www.loom.com/share/e7035c61dbed47d2b9b36b5effd5ee78?sid=bcf4dd2e-b249-4e5d-b173-8ca204d9becb). +6. In your dbt project, follow the steps in Run a dbt Cloud job on merge in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide to create a script to trigger the dbt Cloud API to run your job after a merge happens within your git repository or watch this [video](https://www.loom.com/share/e7035c61dbed47d2b9b36b5effd5ee78?sid=bcf4dd2e-b249-4e5d-b173-8ca204d9becb). The purpose of the merge job is to: diff --git a/website/docs/docs/dbt-cloud-apis/sl-jdbc.md b/website/docs/docs/dbt-cloud-apis/sl-jdbc.md index e10d057dc75..931666dd10c 100644 --- a/website/docs/docs/dbt-cloud-apis/sl-jdbc.md +++ b/website/docs/docs/dbt-cloud-apis/sl-jdbc.md @@ -363,5 +363,5 @@ semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'], ## Related docs -- [dbt Semantic Layer integration best practices](/guides/dbt-ecosystem/sl-partner-integration-guide) +- [dbt Semantic Layer integration best practices](/guides/sl-partner-integration-guide) diff --git a/website/docs/docs/dbt-versions/core-upgrade/08-upgrading-to-v1.0.md b/website/docs/docs/dbt-versions/core-upgrade/08-upgrading-to-v1.0.md index 543368b873a..3f45e44076c 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/08-upgrading-to-v1.0.md +++ b/website/docs/docs/dbt-versions/core-upgrade/08-upgrading-to-v1.0.md @@ -51,7 +51,7 @@ Global project macros have been reorganized, and some old unused macros have bee ### For users of adapter plugins -- **BigQuery:** Support for [ingestion-time-partitioned tables](/guides/legacy/creating-date-partitioned-tables) has been officially deprecated in favor of modern approaches. Use `partition_by` and incremental modeling strategies instead. +- **BigQuery:** Support for ingestion-time-partitioned tables has been officially deprecated in favor of modern approaches. Use `partition_by` and incremental modeling strategies instead. For more information, refer to [Incremental models](/docs/build/incremental-models). ### For maintainers of plugins + other integrations diff --git a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md index 469d2ac362b..7a474cc091f 100644 --- a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md +++ b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md @@ -32,4 +32,4 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/) in June: ## New 📚 Guides, ✏️ blog posts, and FAQs -- Add an Azure DevOps example to the [Customizing CI/CD guide](/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge). +- Add an Azure DevOps example to the in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide. diff --git a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md index 5082699619b..3de29b605ce 100644 --- a/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md +++ b/website/docs/docs/dbt-versions/release-notes/09-April-2023/product-docs.md @@ -31,10 +31,10 @@ Hello from the dbt Docs team: @mirnawong1, @matthewshaver, @nghi-ly, and @runleo ## New 📚 Guides and ✏️ blog posts -- [Use Databricks workflows to run dbt Cloud jobs](/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs) +- [Use Databricks workflows to run dbt Cloud jobs](/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs) - [Refresh Tableau workbook with extracts after a job finishes](/guides/zapier-refresh-tableau-workbook) - [dbt Python Snowpark workshop/tutorial](/guides/dbt-python-snowpark) - [How to optimize and troubleshoot dbt Models on Databricks](/guides/optimize-dbt-models-on-databricks) -- [The missing guide to debug() in dbt](https://docs.getdbt.com/blog/guide-to-jinja-debug) -- [dbt Squared: Leveraging dbt Core and dbt Cloud together at scale](https://docs.getdbt.com/blog/dbt-squared) -- [Audit_helper in dbt: Bringing data auditing to a higher level](https://docs.getdbt.com/blog/audit-helper-for-migration) +- [The missing guide to debug() in dbt](/blog/guide-to-jinja-debug) +- [dbt Squared: Leveraging dbt Core and dbt Cloud together at scale](/blog/dbt-squared) +- [Audit_helper in dbt: Bringing data auditing to a higher level](/blog/audit-helper-for-migration) diff --git a/website/docs/docs/deploy/deployment-tools.md b/website/docs/docs/deploy/deployment-tools.md index 3b2da778a53..cca2368f38a 100644 --- a/website/docs/docs/deploy/deployment-tools.md +++ b/website/docs/docs/deploy/deployment-tools.md @@ -126,7 +126,7 @@ Cron is a decent way to schedule bash commands. However, while it may seem like Use Databricks workflows to call the dbt Cloud job API, which has several benefits such as integration with other ETL processes, utilizing dbt Cloud job features, separation of concerns, and custom job triggering based on custom conditions or logic. These advantages lead to more modularity, efficient debugging, and flexibility in scheduling dbt Cloud jobs. -For more info, refer to the guide on [Databricks workflows and dbt Cloud jobs](/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs). +For more info, refer to the guide on [Databricks workflows and dbt Cloud jobs](/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs). diff --git a/website/docs/faqs/Models/available-materializations.md b/website/docs/faqs/Models/available-materializations.md index fcb3e3a9d26..bf11c92b595 100644 --- a/website/docs/faqs/Models/available-materializations.md +++ b/website/docs/faqs/Models/available-materializations.md @@ -8,4 +8,4 @@ id: available-materializations dbt ships with five materializations: `view`, `table`, `incremental`, `ephemeral` and `materialized_view`. Check out the documentation on [materializations](/docs/build/materializations) for more information on each of these options. -You can also create your own [custom materializations](/guides/creating-new-materializations), if required however this is an advanced feature of dbt. +You can also create your own [custom materializations](/guides/create-new-materializations), if required however this is an advanced feature of dbt. diff --git a/website/docs/faqs/Project/why-not-write-dml.md b/website/docs/faqs/Project/why-not-write-dml.md index 349fc2c5c74..210ef4a916d 100644 --- a/website/docs/faqs/Project/why-not-write-dml.md +++ b/website/docs/faqs/Project/why-not-write-dml.md @@ -30,4 +30,4 @@ You can test your models, generate documentation, create snapshots, and more! SQL dialects tend to diverge the most in DML and DDL (rather than in `select` statements) — check out the example [here](/faqs/models/sql-dialect). By writing less SQL, it can make a migration to a new database technology easier. -If you do need to write custom DML, there are ways to do this in dbt using [custom materializations](/guides/creating-new-materializations). +If you do need to write custom DML, there are ways to do this in dbt using [custom materializations](/guides/create-new-materializations). diff --git a/website/docs/faqs/Warehouse/db-connection-dbt-compile.md b/website/docs/faqs/Warehouse/db-connection-dbt-compile.md index be46f1a1d8c..8017da4545b 100644 --- a/website/docs/faqs/Warehouse/db-connection-dbt-compile.md +++ b/website/docs/faqs/Warehouse/db-connection-dbt-compile.md @@ -22,7 +22,7 @@ To generate the compiled SQL for many models, dbt needs to run introspective que These introspective queries include: -- Populating the [relation cache](/guides/creating-new-materializations#update-the-relation-cache). Caching speeds up the metadata checks, including whether an [incremental model](/docs/build/incremental-models) already exists in the data platform. +- Populating the relation cache. For more information, refer to the [Create new materializations](/guides/create-new-materializations) guide. Caching speeds up the metadata checks, including whether an [incremental model](/docs/build/incremental-models) already exists in the data platform. - Resolving [macros](/docs/build/jinja-macros#macros), such as `run_query` or `dbt_utils.get_column_values` that you're using to template out your SQL. This is because dbt needs to run those queries during model SQL compilation. Without a data platform connection, dbt can't perform these introspective queries and won't be able to generate the compiled SQL needed for the next steps in the dbt workflow. You can [`parse`](/reference/commands/parse) a project and use the [`list`](/reference/commands/list) resources in the project, without an internet or data platform connection. Parsing a project is enough to produce a [manifest](/reference/artifacts/manifest-json), however, keep in mind that the written-out manifest won't include compiled SQL. diff --git a/website/docs/guides/dbt-python-snowpark.md b/website/docs/guides/dbt-python-snowpark.md index 8417ec9177b..35842eb8d91 100644 --- a/website/docs/guides/dbt-python-snowpark.md +++ b/website/docs/guides/dbt-python-snowpark.md @@ -932,7 +932,7 @@ By now, we are pretty good at creating new files in the correct directories so w select * from int_results ``` -1. Create a *Markdown* file `intermediate.md` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. +1. Create a *Markdown* file `intermediate.md` that we will go over in depth in the Test and Documentation sections of the [Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake](/guides/dbt-python-snowpark) guide. ```markdown # the intent of this .md is to allow for multi-line long form explanations for our intermediate transformations @@ -947,7 +947,7 @@ By now, we are pretty good at creating new files in the correct directories so w {% docs int_lap_times_years %} Lap times are done per lap. We need to join them out to the race year to understand yearly lap time trends. {% enddocs %} ``` -1. Create a *YAML* file `intermediate.yml` that we will go over in depth during the [Testing](/guides/dbt-ecosystem/dbt-python-snowpark/13-testing) and [Documentation](/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation) sections. +1. Create a *YAML* file `intermediate.yml` that we will go over in depth during the Test and Document sections of the [Leverage dbt Cloud to generate analytics and ML-ready pipelines with SQL and Python with Snowflake](/guides/dbt-python-snowpark) guide. ```yaml version: 2 diff --git a/website/docs/guides/migrate-from-spark-to-databricks.md b/website/docs/guides/migrate-from-spark-to-databricks.md index 5be1c08d787..b249021ed50 100644 --- a/website/docs/guides/migrate-from-spark-to-databricks.md +++ b/website/docs/guides/migrate-from-spark-to-databricks.md @@ -14,7 +14,7 @@ recently_updated: true ## Introduction -You can [migrate your projects](#migrate-your-dbt-projects) from using the `dbt-spark` adapter to using the [dbt-databricks adapter](https://github.com/databricks/dbt-databricks). In collaboration with dbt Labs, Databricks built this adapter using dbt-spark as the foundation and added some critical improvements. With it, you get an easier set up — requiring only three inputs for authentication — and more features such as support for [Unity Catalog](https://www.databricks.com/product/unity-catalog). +You can migrate your projects from using the `dbt-spark` adapter to using the [dbt-databricks adapter](https://github.com/databricks/dbt-databricks). In collaboration with dbt Labs, Databricks built this adapter using dbt-spark as the foundation and added some critical improvements. With it, you get an easier set up — requiring only three inputs for authentication — and more features such as support for [Unity Catalog](https://www.databricks.com/product/unity-catalog). ### Prerequisites diff --git a/website/docs/guides/productionize-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md index f26a132919b..2c6a436a15b 100644 --- a/website/docs/guides/productionize-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -156,7 +156,7 @@ Inserting dbt Cloud jobs into a Databricks Workflows allows you to chain togethe - Logs and Run History: Accessing logs and run history becomes more convenient when using dbt Cloud. - Monitoring and Notification Features: dbt Cloud comes equipped with monitoring and notification features like the ones described above that can help you stay informed about the status and performance of your jobs. -To trigger your dbt Cloud job from Databricks, follow the instructions in our [Databricks Workflows to run dbt Cloud jobs guide](/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs). +To trigger your dbt Cloud job from Databricks, follow the instructions in our [Databricks Workflows to run dbt Cloud jobs guide](/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs). ## Data masking diff --git a/website/docs/guides/refactoring-legacy-sql.md b/website/docs/guides/refactoring-legacy-sql.md index 09fcb9aaf82..a339e523020 100644 --- a/website/docs/guides/refactoring-legacy-sql.md +++ b/website/docs/guides/refactoring-legacy-sql.md @@ -31,7 +31,7 @@ When migrating and refactoring code, it’s of course important to stay organize Let's get into it! :::info More resources -This guide is excerpted from the new dbt Learn On-demand Course, "Refactoring SQL for Modularity" - if you're curious, pick up the [free refactoring course here](https://courses.getdbt.com/courses/refactoring-sql-for-modularity), which includes example and practice refactoring projects. Or for a more in-depth look at migrating DDL and DML from stored procedures check out [this guide](/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures). +This guide is excerpted from the new dbt Learn On-demand Course, "Refactoring SQL for Modularity" - if you're curious, pick up the [free refactoring course here](https://courses.getdbt.com/courses/refactoring-sql-for-modularity), which includes example and practice refactoring projects. Or for a more in-depth look at migrating DDL and DML from stored procedures, refer to the[Migrate from stored procedures](/guides/migrate-from-stored-procedures) guide. ::: ## Migrate your existing SQL code From bab0aa6f5d2df66bfc0a81a90e5fd071ec19d55f Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:32:22 -0800 Subject: [PATCH 074/152] fix a link o'rama --- .../11-Older versions/upgrading-to-0-15-0.md | 2 +- website/docs/guides/adapter-creation.md | 10 +++++----- .../docs/guides/set-up-your-databricks-dbt-project.md | 4 ++-- website/snippets/dbt-databricks-for-databricks.md | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md index 8259e66fa46..98248a1caa5 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md +++ b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md @@ -26,7 +26,7 @@ expect this field will now return errors. See the latest ### Custom materializations -All materializations must now [manage dbt's Relation cache](/guides/creating-new-materializations#update-the-relation-cache). +All materializations must now manage dbt's Relation cache. For more information, refer to [Create new materializations](/guides/creating-new-materializations). ### dbt Server diff --git a/website/docs/guides/adapter-creation.md b/website/docs/guides/adapter-creation.md index 6c9d575bae2..8a9145f0258 100644 --- a/website/docs/guides/adapter-creation.md +++ b/website/docs/guides/adapter-creation.md @@ -260,7 +260,7 @@ class MyAdapterCredentials(Credentials): There are a few things you can do to make it easier for users when connecting to your database: - Be sure to implement the Credentials' `_connection_keys` method shown above. This method will return the keys that should be displayed in the output of the `dbt debug` command. As a general rule, it's good to return all the arguments used in connecting to the actual database except the password (even optional arguments). -- Create a `profile_template.yml` to enable configuration prompts for a brand-new user setting up a connection profile via the [`dbt init` command](/reference/commands/init). See more details [below](#other-files). +- Create a `profile_template.yml` to enable configuration prompts for a brand-new user setting up a connection profile via the [`dbt init` command](/reference/commands/init). You will find more details in the following steps. - You may also want to define an `ALIASES` mapping on your Credentials class to include any config names you want users to be able to use in place of 'database' or 'schema'. For example if everyone using the MyAdapter database calls their databases "collections", you might do: @@ -574,8 +574,8 @@ Previously, we offered a packaged suite of tests for dbt adapter functionality: This document has two sections: -1. "[About the testing framework](#about-the-testing-framework)" describes the standard framework that we maintain for using pytest together with dbt. It includes an example that shows the anatomy of a simple test case. -2. "[Testing your adapter](#testing-your-adapter)" offers a step-by-step guide for using our out-of-the-box suite of "basic" tests, which will validate that your adapter meets a baseline of dbt functionality. +1. Refer to "About the testing framework" for a description of the standard framework that we maintain for using pytest together with dbt. It includes an example that shows the anatomy of a simple test case. +2. Refer to "Testing your adapter" for a step-by-step guide for using our out-of-the-box suite of "basic" tests, which will validate that your adapter meets a baseline of dbt functionality. ### Testing prerequisites @@ -1067,7 +1067,7 @@ python3 -m pytest tests/functional --profile databricks_sql_endpoint ## Document a new adapter -If you've already [built](3-building-a-new-adapter), and [tested](4-testing-a-new-adapter) your adapter, it's time to document it so the dbt community will know that it exists and how to use it. +If you've already built, and tested your adapter, it's time to document it so the dbt community will know that it exists and how to use it. ### Making your adapter available @@ -1264,7 +1264,7 @@ There has been a tendency to trust the dbt Labs-maintained adapters over communi The adapter verification program aims to quickly indicate to users which adapters can be trusted to use in production. Previously, doing so was uncharted territory for new users and complicated making the business case to their leadership team. We plan to give quality assurances by: 1. appointing a key stakeholder for the adapter repository, -2. ensuring that the chosen stakeholder fixes bugs and cuts new releases in a timely manner see maintainer your adapter (["Maintaining your new adapter"](2-prerequisites-for-a-new-adapter#maintaining-your-new-adapter)), +2. ensuring that the chosen stakeholder fixes bugs and cuts new releases in a timely manner. Refer to the "Maintaining your new adapter" step for more information. 3. demonstrating that it passes our adapter pytest suite tests, 4. assuring that it works for us internally and ideally an existing team using the adapter in production . diff --git a/website/docs/guides/set-up-your-databricks-dbt-project.md b/website/docs/guides/set-up-your-databricks-dbt-project.md index d378b57cacc..c47895f7246 100644 --- a/website/docs/guides/set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/set-up-your-databricks-dbt-project.md @@ -46,7 +46,7 @@ Service principals are used to remove humans from deploying to production for co [Let’s create a service principal](https://docs.databricks.com/administration-guide/users-groups/service-principals.html#add-a-service-principal-to-your-databricks-account) in Databricks: 1. Have your Databricks Account admin [add a service principal](https://docs.databricks.com/administration-guide/users-groups/service-principals.html#add-a-service-principal-to-your-databricks-account) to your account. The service principal’s name should differentiate itself from a user ID and make its purpose clear (eg dbt_prod_sp). -2. Add the service principal added to any groups it needs to be a member of at this time. There are more details on permissions in our ["Unity Catalog best practices" guide](dbt-unity-catalog-best-practices). +2. Add the service principal added to any groups it needs to be a member of at this time. There are more details on permissions in our ["Unity Catalog best practices" guide](/best-practices/dbt-unity-catalog-best-practices). 3. [Add the service principal to your workspace](https://docs.databricks.com/administration-guide/users-groups/service-principals.html#add-a-service-principal-to-a-workspace) and apply any [necessary entitlements](https://docs.databricks.com/administration-guide/users-groups/service-principals.html#add-a-service-principal-to-a-workspace-using-the-admin-console), such as Databricks SQL access and Workspace access. ## Setting up Databricks Compute @@ -113,4 +113,4 @@ Next, you’ll need somewhere to store and version control your code that allows ### Next steps -Now that your project is configured, you can start transforming your Databricks data with dbt. To help you scale efficiently, we recommend you follow our best practices, starting with the [Unity Catalog best practices](/best-practices/dbt-unity-catalog-best-practices), then you can [Optimize dbt models on Databricks](/guides/how_to_optimize_dbt_models_on_databricks) . +Now that your project is configured, you can start transforming your Databricks data with dbt. To help you scale efficiently, we recommend you follow our best practices, starting with the [Unity Catalog best practices](/best-practices/dbt-unity-catalog-best-practices), then you can [Optimize dbt models on Databricks](/guides/optimize-dbt-models-on-databricks). diff --git a/website/snippets/dbt-databricks-for-databricks.md b/website/snippets/dbt-databricks-for-databricks.md index 930e7a85a9f..f1c5ec84af1 100644 --- a/website/snippets/dbt-databricks-for-databricks.md +++ b/website/snippets/dbt-databricks-for-databricks.md @@ -1,4 +1,4 @@ :::info If you're using Databricks, use `dbt-databricks` If you're using Databricks, the `dbt-databricks` adapter is recommended over `dbt-spark`. -If you're still using dbt-spark with Databricks consider [migrating from the dbt-spark adapter to the dbt-databricks adapter](/guides/migration/tools/migrating-from-spark-to-databricks#migrate-your-dbt-projects). -::: \ No newline at end of file +If you're still using dbt-spark with Databricks consider [migrating from the dbt-spark adapter to the dbt-databricks adapter](/guides/migrate-from-spark-to-databricks). +::: From 550d33340d59f994c2859595130ba0e05f11ed7a Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:35:31 -0800 Subject: [PATCH 075/152] fix a link o'rama --- .../core-upgrade/11-Older versions/upgrading-to-0-15-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md index 98248a1caa5..7009dc2d088 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md +++ b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md @@ -26,7 +26,7 @@ expect this field will now return errors. See the latest ### Custom materializations -All materializations must now manage dbt's Relation cache. For more information, refer to [Create new materializations](/guides/creating-new-materializations). +All materializations must now manage dbt's Relation cache. For more information, refer to [Create new materializations](/guides/creatie-new-materializations). ### dbt Server From 78f22b5ea4e76a45f163e8be89cf490746404db5 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:45:31 -0800 Subject: [PATCH 076/152] fix a typo --- .../core-upgrade/11-Older versions/upgrading-to-0-15-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md index 7009dc2d088..5eba212590f 100644 --- a/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md +++ b/website/docs/docs/dbt-versions/core-upgrade/11-Older versions/upgrading-to-0-15-0.md @@ -26,7 +26,7 @@ expect this field will now return errors. See the latest ### Custom materializations -All materializations must now manage dbt's Relation cache. For more information, refer to [Create new materializations](/guides/creatie-new-materializations). +All materializations must now manage dbt's Relation cache. For more information, refer to [Create new materializations](/guides/create-new-materializations). ### dbt Server From 07f9a241e06081d848ca20968596bd118bedc7f1 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:12:43 -0800 Subject: [PATCH 077/152] adding the forwarders --- website/vercel.json | 490 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) diff --git a/website/vercel.json b/website/vercel.json index 3c2c0c6e3ce..7c054b0947e 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -2,6 +2,496 @@ "cleanUrls": true, "trailingSlash": false, "redirects": [ + { + "source": "/guides/advanced/creating-new-materializations", + "destination": "/guides/create-new-materializations", + "permanent": true + }, + { + "source": "/guides/advanced/using-jinja", + "destination": "/guides/using-jinja", + "permanent": true + }, + { + "source": "/guides/best-practices", + "destination": "/best-practices", + "permanent": true + }, + { + "source": "/guides/best-practices/debugging-errors", + "destination": "/guides/debug-errors", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-1-intro", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-2-setup", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-2-setup", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-3-build-semantic-models", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-5-refactor-a-mart", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics", + "destination": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-6-advanced-metrics", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion", + "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-7-conclusion", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/1-guide-overview", + "destination": "/best-practices/how-we-structure/1-guide-overview", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/2-staging", + "destination": "/best-practices/how-we-structure/2-staging", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/3-intermediate", + "destination": "/best-practices/how-we-structure/3-intermediate", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/4-marts", + "destination": "/best-practices/how-we-structure/4-marts", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/5-semantic-layer-marts", + "destination": "/best-practices/how-we-structure/5-semantic-layer-marts", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-structure/6-the-rest-of-the-project", + "destination": "/best-practices/how-we-structure/6-the-rest-of-the-project", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/0-how-we-style-our-dbt-projects", + "destination": "/best-practices/how-we-style/0-how-we-style-our-dbt-projects", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/1-how-we-style-our-dbt-models", + "destination": "/best-practices/how-we-style/1-how-we-style-our-dbt-models", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/2-how-we-style-our-sql", + "destination": "/best-practices/how-we-style/2-how-we-style-our-sql", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/3-how-we-style-our-python", + "destination": "/best-practices/how-we-style/3-how-we-style-our-python", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/4-how-we-style-our-jinja", + "destination": "/best-practices/how-we-style/4-how-we-style-our-jinja", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/5-how-we-style-our-yaml", + "destination": "/best-practices/how-we-style/5-how-we-style-our-yaml", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-style/6-how-we-style-conclusion", + "destination": "/best-practices/how-we-style/6-how-we-style-conclusion", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/1-guide-overview", + "destination": "/best-practices/materializations/1-guide-overview", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/2-available-materializations", + "destination": "/best-practices/materializations/2-available-materializations", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/3-configuring-materializations", + "destination": "/best-practices/materializations/3-configuring-materializations", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/4-incremental-models", + "destination": "/best-practices/materializations/4-incremental-models", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/5-best-practices", + "destination": "/best-practices/materializations/5-best-practices", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/6-examining-builds", + "destination": "/best-practices/materializations/6-examining-builds", + "permanent": true + }, + { + "source": "/guides/best-practices/materializations/7-conclusion", + "destination": "/best-practices/materializations/7-conclusion", + "permanent": true + }, + { + "source": "/guides/best-practices/writing-custom-generic-tests", + "destination": "/best-practices/writing-custom-generic-tests", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem", + "destination": "/guides", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/1-what-are-adapters", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/2-prerequisites-for-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/3-building-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/4-testing-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/5-documenting-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/6-promoting-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/adapter-development/7-verifying-a-new-adapter", + "destination": "/guides/adapter-creation", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/databricks-guides/dbt-unity-catalog-best-practices", + "destination": "/best-practices/dbt-unity-catalog-best-practices", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/databricks-guides/how_to_optimize_dbt_models_on_databricks", + "destination": "/guides/optimize-dbt-models-on-databricks", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/databricks-guides/how-to-set-up-your-databricks-dbt-project", + "destination": "/guides/set-up-your-databricks-dbt-project", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/databricks-guides/productionizing-your-dbt-databricks-project", + "destination": "/guides/productionize-your-dbt-databricks-project", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/1-overview-dbt-python-snowpark", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/10-python-transformations", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/11-machine-learning-prep", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/12-machine-learning-training-prediction", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/13-testing", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/14-documentation", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/15-deployment", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/2-snowflake-configuration", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/3-connect-to-data-source", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/4-configure-dbt", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/5-development-schema-name", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/6-foundational-structure", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/7-folder-structure", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/8-sources-and-staging", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/dbt-python-snowpark/9-sql-transformations", + "destination": "/guides/dbt-python-snowpark", + "permanent": true + }, + { + "source": "/guides/dbt-ecosystem/sl-partner-integration-guide", + "destination": "/guides/sl-partner-integration-guide", + "permanent": true + }, + { + "source": "/guides/legacy/best-practices", + "destination": "/best-practices/best-practice-workflows", + "permanent": true + }, + { + "source": "/guides/legacy/building-packages", + "destination": "/guides/building-packages", + "permanent": true + }, + { + "source": "/guides/legacy/creating-date-partitioned-tables", + "destination": "/docs/build/incremental-models", + "permanent": true + }, + { + "source": "/guides/legacy/debugging-schema-names", + "destination": "/guides/debug-schema-names", + "permanent": true + }, + { + "source": "/guides/legacy/videos", + "destination": "/guides", + "permanent": true + }, + { + "source": "/guides/migration/sl-migration", + "destination": "/guides/sl-migration", + "permanent": true + }, + { + "source": "/guides/migration/tools", + "destination": "/guides", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-spark-to-databricks", + "destination": "/guides/migrate-from-spark-to-databricks", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/1-migrating-from-stored-procedures", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/2-inserts", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/3-updates", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/4-deletes", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/5-merges", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/migrating-from-stored-procedures/6-migrating-from-stored-procedures-conclusion", + "destination": "/guides/migrate-from-stored-procedures", + "permanent": true + }, + { + "source": "/guides/migration/tools/refactoring-legacy-sql", + "destination": "/guides/refactoring-legacy-sql", + "permanent": true + }, + { + "source": "/guides/orchestration", + "destination": "/guides", + "permanent": true + }, + { + "source": "/guides/orchestration/airflow-and-dbt-cloud/1-airflow-and-dbt-cloud", + "destination": "/guides/airflow-and-dbt-cloud", + "permanent": true + }, + { + "source": "/guides/orchestration/airflow-and-dbt-cloud/2-setting-up-airflow-and-dbt-cloud", + "destination": "/guides/airflow-and-dbt-cloud", + "permanent": true + }, + { + "source": "/guides/orchestration/airflow-and-dbt-cloud/3-running-airflow-and-dbt-cloud", + "destination": "/guides/airflow-and-dbt-cloud", + "permanent": true + }, + { + "source": "/guides/orchestration/airflow-and-dbt-cloud/4-airflow-and-dbt-cloud-faqs", + "destination": "/guides/airflow-and-dbt-cloud", + "permanent": true + }, + { + "source": "/guides/orchestration/custom-cicd-pipelines/1-cicd-background", + "destination": "/guides/custom-cicd-pipelines", + "permanent": true + }, + { + "source": "/guides/orchestration/custom-cicd-pipelines/3-dbt-cloud-job-on-merge", + "destination": "/guides/custom-cicd-pipelines", + "permanent": true + }, + { + "source": "/guides/orchestration/custom-cicd-pipelines/4-dbt-cloud-job-on-pr", + "destination": "/guides/custom-cicd-pipelines", + "permanent": true + }, + { + "source": "/guides/orchestration/custom-cicd-pipelines/5-something-to-consider", + "destination": "/guides/custom-cicd-pipelines", + "permanent": true + }, + { + "source": "/guides/orchestration/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs", + "destination": "/guides/how-to-use-databricks-workflows-to-run-dbt-cloud-jobs", + "permanent": true + }, + { + "source": "/guides/orchestration/set-up-ci/in-15-minutes", + "destination": "/guides/set-up-ci", + "permanent": true + }, + { + "source": "/guides/orchestration/set-up-ci/lint-on-push", + "destination": " /guides/set-up-ci", + "permanent": true + }, + { + "source": "/guides/orchestration/set-up-ci/multiple-environments", + "destination": "/guides/set-up-ci", + "permanent": true + }, + { + "source": "/guides/orchestration/set-up-ci/overview", + "destination": "/guides/set-up-ci", + "permanent": true + }, + { + "source": "/guides/orchestration/set-up-ci/run-dbt-project-evaluator", + "destination": "/guides/set-up-ci", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks", + "destination": "/guides", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/serverless-datadog", + "destination": "/guides/serverless-datadog", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/serverless-pagerduty", + "destination": "/guides/serverless-pagerduty", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/zapier-ms-teams", + "destination": "/guides/zapier-ms-team", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/zapier-new-cloud-job", + "destination": "/guides/zapier-new-cloud-job", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/zapier-refresh-mode-report", + "destination": "/guides/zapier-refresh-mode-report", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/zapier-refresh-tableau-workbook", + "destination": "/guides/zapier-refresh-tableau-workbook", + "permanent": true + }, + { + "source": "/guides/orchestration/webhooks/zapier-slack", + "destination": "/guides/zapier-slack", + "permanent": true + }, { "source": "/faqs/Project/docs-for-multiple-projects", "destination": "/docs/collaborate/explore-projects#about-project-level-lineage", From ea3f927e8ea037d7fc0a92582b1252c0d454e9a3 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:04:12 -0800 Subject: [PATCH 078/152] Update website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- .../dbt-versions/release-notes/07-June-2023/product-docs-jun.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md index 7a474cc091f..4ead401a759 100644 --- a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md +++ b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md @@ -32,4 +32,4 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/) in June: ## New 📚 Guides, ✏️ blog posts, and FAQs -- Add an Azure DevOps example to the in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide. +- Add an Azure DevOps example in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide. From a68ca2e655ead85d6b0be20faa704c27c31eb253 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:08:45 -0800 Subject: [PATCH 079/152] Update website/docs/guides/dbt-models-on-databricks.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/dbt-models-on-databricks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/dbt-models-on-databricks.md b/website/docs/guides/dbt-models-on-databricks.md index f26b7253be9..489a3c28467 100644 --- a/website/docs/guides/dbt-models-on-databricks.md +++ b/website/docs/guides/dbt-models-on-databricks.md @@ -14,7 +14,7 @@ recently_updated: true ## Introduction -Building on the [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project) guide, we'd like to discuss performance optimization. In this follow-up post,  we outline simple strategies to optimize for cost, performance, and simplicity when you architect data pipelines. We’ve encapsulated these strategies in this acronym-framework: +Building on the [Set up your dbt project with Databricks](/guides/set-up-your-databricks-dbt-project) guide, we'd like to discuss performance optimization. In this follow-up post, we outline simple strategies to optimize for cost, performance, and simplicity when you architect data pipelines. We’ve encapsulated these strategies in this acronym-framework: - Platform Components - Patterns & Best Practices From 51ac90fb4efd553fd550ca7a6529cd618c5b49fb Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:08:53 -0800 Subject: [PATCH 080/152] Update website/docs/guides/debug-schema-names.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index de713e07df7..e600c772284 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -1,7 +1,7 @@ --- title: Debug schema names id: debug-schema-names -description: Learn how to debug schema names when models build under unexpected schemas +description: Learn how to debug schema names when models build under unexpected schemas. displayText: Debug schema names hoverSnippet: Learn how to debug schema names in dbt. # time_to_complete: '30 minutes' commenting out until we test From 5358095e9ba253406a527fccdc310881ed4364e5 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:09:04 -0800 Subject: [PATCH 081/152] Update website/docs/guides/debug-schema-names.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index e600c772284..8f8c6d3580f 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -73,7 +73,7 @@ Your project is switching out the `generate_schema_name` macro for another macro {%- endmacro %} ``` -### I have a `generate_schema_name` macro with custom logic +### You have a `generate_schema_name` macro with custom logic If this is the case — it might be a great idea to reach out to the person who added this macro to your project, as they will have context here — you can use [GitHub's blame feature](https://docs.github.com/en/free-pro-team@latest/github/managing-files-in-a-repository/tracking-changes-in-a-file) to do this. From 2f3699084006913ffd5b9d7bc9e80385580e0913 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:09:16 -0800 Subject: [PATCH 082/152] Update website/docs/guides/debug-schema-names.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index 8f8c6d3580f..01c87a0931c 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -27,7 +27,7 @@ You can also follow along via this video: ## Search for a macro named `generate_schema_name` Do a file search to check if you have a macro named `generate_schema_name` in the `macros` directory of your project. -### I do not have a macro named `generate_schema_name` in my project +### You do not have a macro named `generate_schema_name` in my project This means that you are using dbt's default implementation of the macro, as defined [here](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/include/global_project/macros/get_custom_name/get_custom_schema.sql#L47C1-L60) ```sql From a70225dd7bf9b71a388159d44ade1949a8bf56ac Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:09:22 -0800 Subject: [PATCH 083/152] Update website/docs/guides/serverless-datadog.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/serverless-datadog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/serverless-datadog.md b/website/docs/guides/serverless-datadog.md index 3b1a3bd6db4..2b8d8341e28 100644 --- a/website/docs/guides/serverless-datadog.md +++ b/website/docs/guides/serverless-datadog.md @@ -1,7 +1,7 @@ --- title: "Create Datadog events from dbt Cloud results" id: serverless-datadog -description: Configure a serverless app to add dbt Cloud events to Datadog logs +description: Configure a serverless app to add dbt Cloud events to Datadog logs. hoverSnippet: Learn how to configure a serverless app to add dbt Cloud events to Datadog logs. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' From 0129c68a26ee45a27f796f5aa21e039a308be251 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:09:34 -0800 Subject: [PATCH 084/152] Update website/docs/guides/debug-schema-names.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index 01c87a0931c..171e1544a19 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -49,7 +49,7 @@ This means that you are using dbt's default implementation of the macro, as defi Note that this logic is designed so that two dbt users won't accidentally overwrite each other's work by writing to the same schema. -### I have a `generate_schema_name` macro in my project that calls another macro +### You have a `generate_schema_name` macro in my project that calls another macro If your `generate_schema_name` macro looks like so: ```sql {% macro generate_schema_name(custom_schema_name, node) -%} From 40aabfa144067ccdfffd25dddd85e5b9de424d61 Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:12:36 -0800 Subject: [PATCH 085/152] Apply suggestions from code review Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/docs/guides/migrate-from-spark-to-databricks.md | 2 +- .../docs/guides/productionize-your-dbt-databricks-project.md | 2 +- website/docs/guides/serverless-datadog.md | 2 +- website/docs/guides/serverless-pagerduty.md | 2 +- website/docs/guides/set-up-your-databricks-dbt-project.md | 2 +- website/docs/guides/zapier-ms-teams.md | 2 +- website/docs/guides/zapier-refresh-mode-report.md | 2 +- website/docs/guides/zapier-refresh-tableau-workbook.md | 2 +- website/docs/guides/zapier-slack.md | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/website/docs/guides/migrate-from-spark-to-databricks.md b/website/docs/guides/migrate-from-spark-to-databricks.md index b249021ed50..8fb02ae79d7 100644 --- a/website/docs/guides/migrate-from-spark-to-databricks.md +++ b/website/docs/guides/migrate-from-spark-to-databricks.md @@ -18,7 +18,7 @@ You can migrate your projects from using the `dbt-spark` adapter to using the [d ### Prerequisites -- Your project must be compatible with dbt 1.0 or greater. Refer to [Upgrading to v1.0](/docs/dbt-versions/core-upgrade/upgrading-to-v1.0) for details. For the latest version of dbt, refer to [Upgrading to v1.3](/docs/dbt-versions/core-upgrade/upgrading-to-v1.3). +- Your project must be compatible with dbt 1.0 or greater. Refer to [Upgrading to v1.0](/docs/dbt-versions/core-upgrade/upgrading-to-v1.0) for details. For the latest version of dbt, refer to [Upgrading to v1.7](/docs/dbt-versions/core-upgrade/upgrading-to-v1.7). - For dbt Cloud, you need administrative (admin) privileges to migrate dbt projects. ### Simpler authentication diff --git a/website/docs/guides/productionize-your-dbt-databricks-project.md b/website/docs/guides/productionize-your-dbt-databricks-project.md index 2c6a436a15b..b95d8ffd2dd 100644 --- a/website/docs/guides/productionize-your-dbt-databricks-project.md +++ b/website/docs/guides/productionize-your-dbt-databricks-project.md @@ -1,7 +1,7 @@ --- title: Productionize your dbt Databricks project id: productionize-your-dbt-databricks-project -description: "Learn how to deliver models to end users and use best practices to maintain production data" +description: "Learn how to deliver models to end users and use best practices to maintain production data." displayText: Productionize your dbt Databricks project hoverSnippet: Learn how to Productionize your dbt Databricks project. # time_to_complete: '30 minutes' commenting out until we test diff --git a/website/docs/guides/serverless-datadog.md b/website/docs/guides/serverless-datadog.md index 2b8d8341e28..931ba9832ab 100644 --- a/website/docs/guides/serverless-datadog.md +++ b/website/docs/guides/serverless-datadog.md @@ -98,7 +98,7 @@ Wrote config file fly.toml
    ## Configure a new webhook in dbt Cloud 1. See [Create a webhook subscription](/docs/deploy/webhooks#create-a-webhook-subscription) for full instructions. Your event should be **Run completed**. -2. Set the webhook URL to the host name you created earlier (`APP_NAME.fly.dev`) +2. Set the webhook URL to the host name you created earlier (`APP_NAME.fly.dev`). 3. Make note of the Webhook Secret Key for later. *Do not test the endpoint*; it won't work until you have stored the auth keys (next step) diff --git a/website/docs/guides/serverless-pagerduty.md b/website/docs/guides/serverless-pagerduty.md index 31436221be5..50cc1b2b36e 100644 --- a/website/docs/guides/serverless-pagerduty.md +++ b/website/docs/guides/serverless-pagerduty.md @@ -1,7 +1,7 @@ --- title: "Trigger PagerDuty alarms when dbt Cloud jobs fail" id: serverless-pagerduty -description: Use webhooks to configure a serverless app to trigger PagerDuty alarms +description: Use webhooks to configure a serverless app to trigger PagerDuty alarms. hoverSnippet: Learn how to configure a serverless app that uses webhooks to trigger PagerDuty alarms. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' diff --git a/website/docs/guides/set-up-your-databricks-dbt-project.md b/website/docs/guides/set-up-your-databricks-dbt-project.md index c47895f7246..c17c6a1f99e 100644 --- a/website/docs/guides/set-up-your-databricks-dbt-project.md +++ b/website/docs/guides/set-up-your-databricks-dbt-project.md @@ -1,7 +1,7 @@ --- title: Set up your dbt project with Databricks id: set-up-your-databricks-dbt-project -description: "Learn more about setting up your dbt project with Databricks" +description: "Learn more about setting up your dbt project with Databricks." displayText: Setting up your dbt project with Databricks hoverSnippet: Learn how to set up your dbt project with Databricks. # time_to_complete: '30 minutes' commenting out until we test diff --git a/website/docs/guides/zapier-ms-teams.md b/website/docs/guides/zapier-ms-teams.md index bd8bdd4aca2..66596d590e0 100644 --- a/website/docs/guides/zapier-ms-teams.md +++ b/website/docs/guides/zapier-ms-teams.md @@ -1,7 +1,7 @@ --- title: "Post to Microsoft Teams when a job finishes" id: zapier-ms-teams -description: Use Zapier and dbt Cloud webhooks to post to Microsoft Teams when a job finishes running +description: Use Zapier and dbt Cloud webhooks to post to Microsoft Teams when a job finishes running. hoverSnippet: Learn how to use Zapier with dbt Cloud webhooks to post in Microsoft Teams when a job finishes running. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' diff --git a/website/docs/guides/zapier-refresh-mode-report.md b/website/docs/guides/zapier-refresh-mode-report.md index 0ffcec9c96d..5bab165b11d 100644 --- a/website/docs/guides/zapier-refresh-mode-report.md +++ b/website/docs/guides/zapier-refresh-mode-report.md @@ -1,7 +1,7 @@ --- title: "Refresh a Mode dashboard when a job completes" id: zapier-refresh-mode-report -description: Use Zapier to trigger a Mode dashboard refresh when a dbt Cloud job completes +description: Use Zapier to trigger a Mode dashboard refresh when a dbt Cloud job completes. hoverSnippet: Learn how to use Zapier to trigger a Mode dashboard refresh when a dbt Cloud job completes. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' diff --git a/website/docs/guides/zapier-refresh-tableau-workbook.md b/website/docs/guides/zapier-refresh-tableau-workbook.md index 6e8621659f0..f614b64eaa2 100644 --- a/website/docs/guides/zapier-refresh-tableau-workbook.md +++ b/website/docs/guides/zapier-refresh-tableau-workbook.md @@ -1,7 +1,7 @@ --- title: "Refresh Tableau workbook with extracts after a job finishes" id: zapier-refresh-tableau-workbook -description: Use Zapier to trigger a Tableau workbook refresh once a dbt Cloud job completes successfully +description: Use Zapier to trigger a Tableau workbook refresh once a dbt Cloud job completes successfully. hoverSnippet: Learn how to use Zapier to trigger a Tableau workbook refresh once a dbt Cloud job completes successfully. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' diff --git a/website/docs/guides/zapier-slack.md b/website/docs/guides/zapier-slack.md index d103e4aa541..61b96658f95 100644 --- a/website/docs/guides/zapier-slack.md +++ b/website/docs/guides/zapier-slack.md @@ -1,7 +1,7 @@ --- title: "Post to Slack with error context when a job fails" id: zapier-slack -description: Use a webhook or Slack message to trigger Zapier and post error context in Slack when a job fails +description: Use a webhook or Slack message to trigger Zapier and post error context in Slack when a job fails. hoverSnippet: Learn how to use a webhook or Slack message to trigger Zapier to post error context in Slack when a job fails. # time_to_complete: '30 minutes' commenting out until we test icon: 'guides' From f17ee74a9f9e5d2ee7ac3ea1c67887084861fd9c Mon Sep 17 00:00:00 2001 From: Matt Sunderland <40239275+gatewaycat@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:37:06 -0500 Subject: [PATCH 086/152] Link to reference/resource-configs/schema#tests Current text reads "you can configure the schema to a different value," alluding to https://docs.getdbt.com/reference/resource-configs/schema#tests, but does not link to it. Adding such a link. I believe this link will help dbt practitioners find the --store-failures schema configuration, in particular because when you google "--store-failures schema," this page https://docs.getdbt.com/reference/resource-configs/store_failures is the 1st result, but the page you actually want https://docs.getdbt.com/reference/resource-configs/schema#tests does not appear at all, as google hides it "In order to show you the most relevant results, we have omitted some entries very similar to the 100 already displayed." This has been the case since at least 2022. --- website/docs/reference/resource-configs/store_failures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/reference/resource-configs/store_failures.md b/website/docs/reference/resource-configs/store_failures.md index 6c71cdb9296..2c596d1cf3e 100644 --- a/website/docs/reference/resource-configs/store_failures.md +++ b/website/docs/reference/resource-configs/store_failures.md @@ -10,7 +10,7 @@ Optionally set a test to always or never store its failures in the database. - If specified as `true` or `false`, the `store_failures` config will take precedence over the presence or absence of the `--store-failures` flag. - If the `store_failures` config is `none` or omitted, the resource will use the value of the `--store-failures` flag. -- When true, `store_failures` save all the record(s) that failed the test only if [limit](/reference/resource-configs/limit) is not set or if there are fewer records than the limit. `store_failures` are saved in a new table with the name of the test. By default, `store_failures` use a schema named `dbt_test__audit`, but, you can configure the schema to a different value. +- When true, `store_failures` save all the record(s) that failed the test only if [limit](/reference/resource-configs/limit) is not set or if there are fewer records than the limit. `store_failures` are saved in a new table with the name of the test. By default, `store_failures` use a schema named `dbt_test__audit`, but, you can [configure](/reference/resource-configs/schema#tests) the schema to a different value. This logic is encoded in the [`should_store_failures()`](https://github.com/dbt-labs/dbt-core/blob/98c015b7754779793e44e056905614296c6e4527/core/dbt/include/global_project/macros/materializations/helpers.sql#L77) macro. From 33c3a5f37454770e7a4625e257d70eba00d47c3c Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:13:57 +0000 Subject: [PATCH 087/152] Update semantic-layer-4-build-metrics.md clarify per [slack convo](https://getdbt.slack.com/archives/C05M77P54FL/p1699556859073509?thread_ts=1699525055.794449&cid=C05M77P54FL) --- .../semantic-layer-4-build-metrics.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md b/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md index cd0efdc9e64..8b112f2f828 100644 --- a/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md +++ b/website/docs/guides/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md @@ -34,8 +34,15 @@ metrics: ## Query your metric -- It's best practice any time we're updating our semantic layer code to run a `dbt parse && mf validate-configs`. +Use [MetricFlow commands](/docs/build/metricflow-commands#metricflow) for metric validation or queries during development, and apply the following conventions based on your environment: + +- For dbt Cloud, use the `dbt sl` prefix before the command (such as, `dbt sl parse` or `dbt sl query`). +- For dbt Core, use the `mf` prefix (such as `mf validate-configs` or `mf query)`. + +Follow these best practices when updating your semantic layer code, using the `mf` command as an example (replace `mf` with `dbt sl` if you're using dbt Cloud): + +- It's best practice any time we're updating our semantic layer code to run `dbt parse` if using dbt Cloud or `dbt parse && mf validate-configs` if using dbt Core, to validate your configs. - If everything passes, we can start querying this metric with `mf query`! - `mf query` is not how you would use the tool in production, that's handled by the dbt Cloud Semantic Layer's features. It's available for testing results of various metric queries in development, exactly as we're using it now. - Try `mf query --metrics revenue --group-by metric_time__day` and see a preview of the data come back. -- Note the structure of the above query. We select the metric(s) we want and the dimensions to group them by — we use dunders (double underscores e.g.`metric_time__[time bucket]`) to designate time dimensions or other non-unique dimensions that need a specified entity path to resolve (e.g. if you have a orders location dimension and a employee location dimension both named 'location' you would need dunders to specify `orders__location` or `employee__location`). +- Note the structure of the above query. We select the metric(s) we want and the dimensions to group them by — we use dunders (double underscores e.g.`metric_time__[time bucket]`) to designate time dimensions or other non-unique dimensions that need a specified entity path to resolve (e.g. if you have an orders location dimension and an employee location dimension both named 'location' you would need dunders to specify `orders__location` or `employee__location`). From 875f6eb1a14854dda9977cac6f152ca70fbfbe51 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Fri, 10 Nov 2023 10:51:10 +0000 Subject: [PATCH 088/152] clarify dbt rules for defer --- .../docs/docs/cloud/about-cloud-develop-defer.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 95e745fe963..48c4388578c 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -7,9 +7,20 @@ pagination_next: "docs/cloud/cloud-cli-installation" --- -[Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). This is powered by using a production manifest for comparison, and dbt will resolve the `{{ ref() }}` function with upstream production artifacts. +[Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). This is powered by using a production manifest for comparison, and dbt will resolve the `{{ ref() }}` function with upstream production artifacts. -Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows, dramatically reducing development time and warehouse spend by preventing unnecessary model builds. +By default, dbt follows these rules: + +- Defers to the production environment when there's no development schema. +- If a development schema exists, dbt will prioritize those changes, which minimizes development time and avoids unnecessary model builds. + +Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. + +For specific scenarios: +- Use [`--favor-state`](/reference/node-selection/defer#favor-state) to always use production artifacts to resolve the ref. +- If facing issues with outdated tables in the dev schema, `--favor-state` is an alternative to defer. + +For a clean slate, it's a good practice to drop the dev schema at the start and end of your development cycle. ## Required setup From 1da1108401184321e93f7614e4baf6019803eb66 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Fri, 10 Nov 2023 10:51:40 +0000 Subject: [PATCH 089/152] consistency --- website/docs/docs/cloud/about-cloud-develop-defer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 48c4388578c..1861a6d8a79 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -18,9 +18,9 @@ Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to pr For specific scenarios: - Use [`--favor-state`](/reference/node-selection/defer#favor-state) to always use production artifacts to resolve the ref. -- If facing issues with outdated tables in the dev schema, `--favor-state` is an alternative to defer. +- If facing issues with outdated tables in the development schema, `--favor-state` is an alternative to defer. -For a clean slate, it's a good practice to drop the dev schema at the start and end of your development cycle. +For a clean slate, it's a good practice to drop the development schema at the start and end of your development cycle. ## Required setup From e0e8309bc879fdc7202f69b25430aa0e19e832e1 Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 13:35:10 -0700 Subject: [PATCH 090/152] Update page title --- website/docs/reference/source-properties.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/website/docs/reference/source-properties.md b/website/docs/reference/source-properties.md index d20ef5f2877..d107881967e 100644 --- a/website/docs/reference/source-properties.md +++ b/website/docs/reference/source-properties.md @@ -1,5 +1,5 @@ --- -title: "About source properties" +title: "Source properties" description: "Learn how to use source properties in dbt." --- @@ -8,9 +8,13 @@ description: "Learn how to use source properties in dbt." - [Declaring resource properties](/reference/configs-and-properties) ## Overview -Source properties can be declared in `.yml` files in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)). -You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory. +import PropsCallout from '/snippets/_config-prop-callout.md'; + +Source properties can be declared in any `properties.yml` file in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)).
    + + +You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory: From 26d7cf1fbb3e1b642b5f62264c35527d0cc62dd6 Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 14:02:52 -0700 Subject: [PATCH 091/152] Small whitespace and verbiage changes --- website/docs/reference/configs-and-properties.md | 5 ++++- website/docs/reference/dbt_project.yml.md | 3 ++- website/docs/reference/resource-configs/docs.md | 10 ++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/website/docs/reference/configs-and-properties.md b/website/docs/reference/configs-and-properties.md index c2ad5b77629..75c8e1594b1 100644 --- a/website/docs/reference/configs-and-properties.md +++ b/website/docs/reference/configs-and-properties.md @@ -11,7 +11,7 @@ A rule of thumb: properties declare things _about_ your project resources; confi For example, you can use resource **properties** to: * Describe models, snapshots, seed files, and their columns -- Assert "truths" about a model, in the form of [tests](/docs/build/tests), e.g. "this `id` column is unique" +* Assert "truths" about a model, in the form of [tests](/docs/build/tests), e.g. "this `id` column is unique" * Define pointers to existing tables that contain raw data, in the form of [sources](/docs/build/sources), and assert the expected "freshness" of this raw data * Define official downstream uses of your data models, in the form of [exposures](/docs/build/exposures) @@ -67,12 +67,14 @@ Previous versions of the docs referred to these as `schema.yml` files — we've dbt has the ability to define node configs in `.yml` files, in addition to `config()` blocks and `dbt_project.yml`. But the reverse isn't always true: there are some things in `.yml` files that can _only_ be defined there. Certain properties are special, because: + - They have a unique Jinja rendering context - They create new project resources - They don't make sense as hierarchical configuration - They're older properties that haven't yet been redefined as configs These properties are: + - [`description`](/reference/resource-properties/description) - [`tests`](/reference/resource-properties/tests) - [`docs`](/reference/resource-configs/docs) @@ -202,3 +204,4 @@ Runtime Error ``` This error occurred because a semicolon (`;`) was accidentally used instead of a colon (`:`) after the `description` field. To resolve issues like this, find the `.yml` file referenced in the error message and fix any syntax errors present in the file. There are online YAML validators that can be helpful here, but please be mindful of submitting sensitive information to third-party applications! + diff --git a/website/docs/reference/dbt_project.yml.md b/website/docs/reference/dbt_project.yml.md index 9bd85d0d5dd..caf501c27ab 100644 --- a/website/docs/reference/dbt_project.yml.md +++ b/website/docs/reference/dbt_project.yml.md @@ -1,5 +1,5 @@ -Every [dbt project](/docs/build/projects) needs a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. It also contains important information that tells dbt how to operate on your project. +Every [dbt project](/docs/build/projects) needs a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. It also contains important information that tells dbt how to operate your project. @@ -96,6 +96,7 @@ vars: + ```yml diff --git a/website/docs/reference/resource-configs/docs.md b/website/docs/reference/resource-configs/docs.md index 0ccd21d7504..d300979a826 100644 --- a/website/docs/reference/resource-configs/docs.md +++ b/website/docs/reference/resource-configs/docs.md @@ -17,10 +17,12 @@ default_value: {show: true} { label: 'Macros', value: 'macros', }, ] }> + + ```yml version: 2 @@ -29,7 +31,6 @@ models: docs: show: true | false node_color: "black" - ``` @@ -53,9 +54,7 @@ seeds: - name: seed_name docs: show: true | false - ``` - @@ -71,9 +70,7 @@ snapshots: - name: snapshot_name docs: show: true | false - ``` - @@ -90,7 +87,6 @@ analyses: docs: show: true | false ``` -
    @@ -110,9 +106,7 @@ macros: - name: macro_name docs: show: true | false - ``` -
    Also refer to [macro properties](/reference/macro-properties). From 5863a3d7978aa5b1745ae871541812cff5313f77 Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 14:14:05 -0700 Subject: [PATCH 092/152] Revert "Update page title" This reverts commit e0e8309bc879fdc7202f69b25430aa0e19e832e1. --- website/docs/reference/source-properties.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/website/docs/reference/source-properties.md b/website/docs/reference/source-properties.md index d107881967e..d20ef5f2877 100644 --- a/website/docs/reference/source-properties.md +++ b/website/docs/reference/source-properties.md @@ -1,5 +1,5 @@ --- -title: "Source properties" +title: "About source properties" description: "Learn how to use source properties in dbt." --- @@ -8,13 +8,9 @@ description: "Learn how to use source properties in dbt." - [Declaring resource properties](/reference/configs-and-properties) ## Overview +Source properties can be declared in `.yml` files in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)). -import PropsCallout from '/snippets/_config-prop-callout.md'; - -Source properties can be declared in any `properties.yml` file in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)).
    - - -You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory: +You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory. From 6431a3a898537fb59127171456f15e8d431db786 Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 14:17:27 -0700 Subject: [PATCH 093/152] Update page title --- website/docs/reference/source-properties.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/reference/source-properties.md b/website/docs/reference/source-properties.md index d20ef5f2877..1fc7a4aef55 100644 --- a/website/docs/reference/source-properties.md +++ b/website/docs/reference/source-properties.md @@ -1,5 +1,5 @@ --- -title: "About source properties" +title: "Source properties" description: "Learn how to use source properties in dbt." --- From 05e744905f411c94e6c9d9abb757cea1d7721c2a Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 14:40:14 -0700 Subject: [PATCH 094/152] Remove hyphenation --- website/docs/reference/configs-and-properties.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/reference/configs-and-properties.md b/website/docs/reference/configs-and-properties.md index 75c8e1594b1..8a557c762ed 100644 --- a/website/docs/reference/configs-and-properties.md +++ b/website/docs/reference/configs-and-properties.md @@ -35,11 +35,11 @@ dbt prioritizes configurations in order of specificity, from most specificity to Note - Generic tests work a little differently when it comes to specificity. See [test configs](/reference/test-configs). -Within the project file, configurations are also applied hierarchically. The most-specific config always "wins": In the project file, configurations applied to a `marketing` subdirectory will take precedence over configurations applied to the entire `jaffle_shop` project. To apply a configuration to a model, or directory of models, define the resource path as nested dictionary keys. +Within the project file, configurations are also applied hierarchically. The most specific config always "wins": In the project file, configurations applied to a `marketing` subdirectory will take precedence over configurations applied to the entire `jaffle_shop` project. To apply a configuration to a model, or directory of models, define the resource path as nested dictionary keys. ### Combining configs -Most configurations are "clobbered" when applied hierarchically. Whenever a more-specific value is available, it will completely replace the less-specific value. Note that a few configs have different merge behavior: +Most configurations are "clobbered" when applied hierarchically. Whenever a more specific value is available, it will completely replace the less specific value. Note that a few configs have different merge behavior: - [`tags`](tags) are additive. If a model has some tags configured in `dbt_project.yml`, and more tags applied in its `.sql` file, the final set of tags will include all of them. - [`meta`](/reference/resource-configs/meta) dictionaries are merged (a more specific key-value pair replaces a less specific value with the same key) - [`pre-hook` and `post-hook`](/reference/resource-configs/pre-hook-post-hook) are also additive. From 926c8cec9734b3429d2d068551cc57dc89c3f814 Mon Sep 17 00:00:00 2001 From: Doug Beatty <44704949+dbeatty10@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:54:01 -0700 Subject: [PATCH 095/152] Small formatting changes for configs & properties (#4438) ### Previews - [configs-and-properties](https://docs-getdbt-com-git-dbeatty-config-properties-f-871117-dbt-labs.vercel.app/reference/configs-and-properties) - [dbt_project.yml](https://docs-getdbt-com-git-dbeatty-config-properties-f-871117-dbt-labs.vercel.app/reference/dbt_project.yml) - [docs](https://docs-getdbt-com-git-dbeatty-config-properties-f-871117-dbt-labs.vercel.app/reference/resource-configs/docs) - [source-properties](https://docs-getdbt-com-git-dbeatty-config-properties-f-871117-dbt-labs.vercel.app/reference/source-properties) ## What are you changing in this pull request and why? These are small formatting these found while working on #3989. Merging these changes independently will reduce the diffs in #3989 and make easier to review some of the tricky technical content in that PR. ## Checklist - [x] Review the [Content style guide](https://github.com/dbt-labs/docs.getdbt.com/blob/current/contributing/content-style-guide.md) and [About versioning](https://github.com/dbt-labs/docs.getdbt.com/blob/current/contributing/single-sourcing-content.md#adding-a-new-version) so my content adheres to these guidelines. - [x] I have examined each preview to make sure it looks correct --- website/docs/reference/configs-and-properties.md | 9 ++++++--- website/docs/reference/dbt_project.yml.md | 3 ++- website/docs/reference/resource-configs/docs.md | 10 ++-------- website/docs/reference/source-properties.md | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/website/docs/reference/configs-and-properties.md b/website/docs/reference/configs-and-properties.md index c2ad5b77629..8a557c762ed 100644 --- a/website/docs/reference/configs-and-properties.md +++ b/website/docs/reference/configs-and-properties.md @@ -11,7 +11,7 @@ A rule of thumb: properties declare things _about_ your project resources; confi For example, you can use resource **properties** to: * Describe models, snapshots, seed files, and their columns -- Assert "truths" about a model, in the form of [tests](/docs/build/tests), e.g. "this `id` column is unique" +* Assert "truths" about a model, in the form of [tests](/docs/build/tests), e.g. "this `id` column is unique" * Define pointers to existing tables that contain raw data, in the form of [sources](/docs/build/sources), and assert the expected "freshness" of this raw data * Define official downstream uses of your data models, in the form of [exposures](/docs/build/exposures) @@ -35,11 +35,11 @@ dbt prioritizes configurations in order of specificity, from most specificity to Note - Generic tests work a little differently when it comes to specificity. See [test configs](/reference/test-configs). -Within the project file, configurations are also applied hierarchically. The most-specific config always "wins": In the project file, configurations applied to a `marketing` subdirectory will take precedence over configurations applied to the entire `jaffle_shop` project. To apply a configuration to a model, or directory of models, define the resource path as nested dictionary keys. +Within the project file, configurations are also applied hierarchically. The most specific config always "wins": In the project file, configurations applied to a `marketing` subdirectory will take precedence over configurations applied to the entire `jaffle_shop` project. To apply a configuration to a model, or directory of models, define the resource path as nested dictionary keys. ### Combining configs -Most configurations are "clobbered" when applied hierarchically. Whenever a more-specific value is available, it will completely replace the less-specific value. Note that a few configs have different merge behavior: +Most configurations are "clobbered" when applied hierarchically. Whenever a more specific value is available, it will completely replace the less specific value. Note that a few configs have different merge behavior: - [`tags`](tags) are additive. If a model has some tags configured in `dbt_project.yml`, and more tags applied in its `.sql` file, the final set of tags will include all of them. - [`meta`](/reference/resource-configs/meta) dictionaries are merged (a more specific key-value pair replaces a less specific value with the same key) - [`pre-hook` and `post-hook`](/reference/resource-configs/pre-hook-post-hook) are also additive. @@ -67,12 +67,14 @@ Previous versions of the docs referred to these as `schema.yml` files — we've dbt has the ability to define node configs in `.yml` files, in addition to `config()` blocks and `dbt_project.yml`. But the reverse isn't always true: there are some things in `.yml` files that can _only_ be defined there. Certain properties are special, because: + - They have a unique Jinja rendering context - They create new project resources - They don't make sense as hierarchical configuration - They're older properties that haven't yet been redefined as configs These properties are: + - [`description`](/reference/resource-properties/description) - [`tests`](/reference/resource-properties/tests) - [`docs`](/reference/resource-configs/docs) @@ -202,3 +204,4 @@ Runtime Error ``` This error occurred because a semicolon (`;`) was accidentally used instead of a colon (`:`) after the `description` field. To resolve issues like this, find the `.yml` file referenced in the error message and fix any syntax errors present in the file. There are online YAML validators that can be helpful here, but please be mindful of submitting sensitive information to third-party applications! + diff --git a/website/docs/reference/dbt_project.yml.md b/website/docs/reference/dbt_project.yml.md index 9bd85d0d5dd..caf501c27ab 100644 --- a/website/docs/reference/dbt_project.yml.md +++ b/website/docs/reference/dbt_project.yml.md @@ -1,5 +1,5 @@ -Every [dbt project](/docs/build/projects) needs a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. It also contains important information that tells dbt how to operate on your project. +Every [dbt project](/docs/build/projects) needs a `dbt_project.yml` file — this is how dbt knows a directory is a dbt project. It also contains important information that tells dbt how to operate your project. @@ -96,6 +96,7 @@ vars: + ```yml diff --git a/website/docs/reference/resource-configs/docs.md b/website/docs/reference/resource-configs/docs.md index 0ccd21d7504..d300979a826 100644 --- a/website/docs/reference/resource-configs/docs.md +++ b/website/docs/reference/resource-configs/docs.md @@ -17,10 +17,12 @@ default_value: {show: true} { label: 'Macros', value: 'macros', }, ] }> + + ```yml version: 2 @@ -29,7 +31,6 @@ models: docs: show: true | false node_color: "black" - ``` @@ -53,9 +54,7 @@ seeds: - name: seed_name docs: show: true | false - ``` - @@ -71,9 +70,7 @@ snapshots: - name: snapshot_name docs: show: true | false - ``` - @@ -90,7 +87,6 @@ analyses: docs: show: true | false ``` - @@ -110,9 +106,7 @@ macros: - name: macro_name docs: show: true | false - ``` - Also refer to [macro properties](/reference/macro-properties). diff --git a/website/docs/reference/source-properties.md b/website/docs/reference/source-properties.md index d20ef5f2877..1fc7a4aef55 100644 --- a/website/docs/reference/source-properties.md +++ b/website/docs/reference/source-properties.md @@ -1,5 +1,5 @@ --- -title: "About source properties" +title: "Source properties" description: "Learn how to use source properties in dbt." --- From dac137daca30774e38d46580da95479deb5108cd Mon Sep 17 00:00:00 2001 From: Doug Beatty Date: Fri, 10 Nov 2023 15:16:38 -0700 Subject: [PATCH 096/152] Callout snippet for properties and configs --- website/docs/reference/analysis-properties.md | 4 +++- website/docs/reference/exposure-properties.md | 6 +++++- website/docs/reference/macro-properties.md | 7 +++++-- website/docs/reference/source-properties.md | 8 ++++++-- website/snippets/_config-prop-callout.md | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 website/snippets/_config-prop-callout.md diff --git a/website/docs/reference/analysis-properties.md b/website/docs/reference/analysis-properties.md index fbc7b05538f..880aeddbb0d 100644 --- a/website/docs/reference/analysis-properties.md +++ b/website/docs/reference/analysis-properties.md @@ -2,7 +2,9 @@ title: Analysis properties --- -We recommend you define analysis properties in your `analyses/` directory, which is illustrated in the [`analysis-paths`](/reference/project-configs/analysis-paths) configuration. +import PropsCallout from '/snippets/_config-prop-callout.md'; + +We recommend you define analysis properties in your `analyses/` directory, which is illustrated in the [`analysis-paths`](/reference/project-configs/analysis-paths) configuration.
    You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `analyses/` or `models/` directory. diff --git a/website/docs/reference/exposure-properties.md b/website/docs/reference/exposure-properties.md index aaed2a20a09..0bd4cf771af 100644 --- a/website/docs/reference/exposure-properties.md +++ b/website/docs/reference/exposure-properties.md @@ -8,7 +8,11 @@ description: "Read this guide to understand exposure properties in dbt." - [Declaring resource properties](/reference/configs-and-properties) ## Overview -Exposures are defined in `.yml` files nested under an `exposures:` key. You may define `exposures` in YAML files that also define define `sources` or `models`. + +import PropsCallout from '/snippets/_config-prop-callout.md'; + +Exposures are defined in `properties.yml` files nested under an `exposures:` key. You may define `exposures` in YAML files that also define `sources` or `models`.
    + You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory. diff --git a/website/docs/reference/macro-properties.md b/website/docs/reference/macro-properties.md index 9919835f3c5..91a616ded0d 100644 --- a/website/docs/reference/macro-properties.md +++ b/website/docs/reference/macro-properties.md @@ -1,10 +1,13 @@ --- title: Macro properties +id: macro-properties --- -Macro properties can be declared in `.yml` files. +import PropsCallout from '/snippets/_config-prop-callout.md'; -You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders. +Macro properties can be declared in any `properties.yml` file. + +You can name these files `whatever_you_want.yml` and nest them arbitrarily deep in sub-folders. diff --git a/website/docs/reference/source-properties.md b/website/docs/reference/source-properties.md index 1fc7a4aef55..d107881967e 100644 --- a/website/docs/reference/source-properties.md +++ b/website/docs/reference/source-properties.md @@ -8,9 +8,13 @@ description: "Learn how to use source properties in dbt." - [Declaring resource properties](/reference/configs-and-properties) ## Overview -Source properties can be declared in `.yml` files in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)). -You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory. +import PropsCallout from '/snippets/_config-prop-callout.md'; + +Source properties can be declared in any `properties.yml` file in your `models/` directory (as defined by the [`model-paths` config](/reference/project-configs/model-paths)).
    + + +You can name these files `whatever_you_want.yml`, and nest them arbitrarily deeply in subfolders within the `models/` directory: diff --git a/website/snippets/_config-prop-callout.md b/website/snippets/_config-prop-callout.md new file mode 100644 index 00000000000..f21c335734a --- /dev/null +++ b/website/snippets/_config-prop-callout.md @@ -0,0 +1 @@ +{props.title} are "special properties" in that you can't configure them in the dbt_project.yml file or using config() blocks. Refer to Configs and properties for more info. From 875eb0c59a2c3cba437305b84f0b783c1d8d97b9 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:21:00 -0800 Subject: [PATCH 097/152] Update website/docs/best-practices/best-practice-workflows.md don't need the domain for links when page is within our own docs site --- website/docs/best-practices/best-practice-workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/best-practices/best-practice-workflows.md b/website/docs/best-practices/best-practice-workflows.md index 4760aeff782..f06e785c6db 100644 --- a/website/docs/best-practices/best-practice-workflows.md +++ b/website/docs/best-practices/best-practice-workflows.md @@ -58,7 +58,7 @@ All subsequent data models should be built on top of these models, reducing the Earlier versions of this documentation recommended implementing “base models” as the first layer of transformation, and gave advice on the SQL within these models. We realized that while the reasons behind this convention were valid, the specific advice around "base models" represented an opinion, so we moved it out of the official documentation. -You can instead find our opinions on [how we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). +You can instead find our opinions on [how we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview). ::: From 164a80cc2d8baf62365c1c4ba7394f9de3b9b392 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:27:52 -0800 Subject: [PATCH 098/152] Update website/blog/2023-04-24-framework-refactor-alteryx-dbt.md don't need the domain for links when page is within our own docs site --- website/blog/2023-04-24-framework-refactor-alteryx-dbt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md index 9b6135b0984..0049a16ff39 100644 --- a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md +++ b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md @@ -94,7 +94,7 @@ It is essential to click on each data source (the green book icons on the leftmo For this step, we identified which operators were used in the data source (for example, joining data, order columns, group by, etc). Usually the Alteryx operators are pretty self-explanatory and all the information needed for understanding appears on the left side of the menu. We also checked the documentation to understand how each Alteryx operator works behind the scenes. -We followed dbt Labs' guide on how to refactor legacy SQL queries in dbt and some [best practices](https://docs.getdbt.com/guides/refactoring-legacy-sql). After we finished refactoring all the Alteryx workflows, we checked if the Alteryx output matched the output of the refactored model built on dbt. +We followed dbt Labs' guide on how to refactor legacy SQL queries in dbt and some [best practices](/guides/refactoring-legacy-sql). After we finished refactoring all the Alteryx workflows, we checked if the Alteryx output matched the output of the refactored model built on dbt. #### Step 3: Use the `audit_helper` package to audit refactored data models From 96c344f7dbbd148451cded980e97d270146ddcdd Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:29:41 -0800 Subject: [PATCH 099/152] Update website/blog/2023-04-24-framework-refactor-alteryx-dbt.md don't need the domain for links when page is within our own docs site --- website/blog/2023-04-24-framework-refactor-alteryx-dbt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md index 0049a16ff39..46cfcb58cdd 100644 --- a/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md +++ b/website/blog/2023-04-24-framework-refactor-alteryx-dbt.md @@ -131,4 +131,4 @@ As we can see, refactoring Alteryx to dbt was an important step in the direction > > [Audit_helper in dbt: Bringing data auditing to a higher level](https://docs.getdbt.com/blog/audit-helper-for-migration) > -> [Refactoring legacy SQL to dbt](https://docs.getdbt.com/guides/refactoring-legacy-sql) +> [Refactoring legacy SQL to dbt](/guides/refactoring-legacy-sql) From ebbf184c3c582b3a814a411dca2c88e7c02705d6 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:32:42 -0800 Subject: [PATCH 100/152] Update website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md don't need the domain for links when page is within our own docs site --- .../best-practices/how-we-structure/5-semantic-layer-marts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md index aca0ca3f283..d064490354c 100644 --- a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md +++ b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md @@ -3,7 +3,7 @@ title: "Marts for the Semantic Layer" id: "5-semantic-layer-marts" --- -The Semantic Layer alters some fundamental principles of how you organize your project. Using dbt without the Semantic Layer necessitates creating the most useful combinations of your building block components into wide, denormalized marts. On the other hand, the Semantic Layer leverages MetricFlow to denormalize every possible combination of components we've encoded dynamically. As such we're better served to bring more normalized models through from the logical layer into the Semantic Layer to maximize flexibility. This section will assume familiarity with the best practices laid out in the [How we build our metrics](https://docs.getdbt.com/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) guide, so check that out first for a more hands-on introduction to the Semantic Layer. +The Semantic Layer alters some fundamental principles of how you organize your project. Using dbt without the Semantic Layer necessitates creating the most useful combinations of your building block components into wide, denormalized marts. On the other hand, the Semantic Layer leverages MetricFlow to denormalize every possible combination of components we've encoded dynamically. As such we're better served to bring more normalized models through from the logical layer into the Semantic Layer to maximize flexibility. This section will assume familiarity with the best practices laid out in the [How we build our metrics](/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) guide, so check that out first for a more hands-on introduction to the Semantic Layer. ## Semantic Layer: Files and folders From c0e825303403b7c94d1b31282a18bcaf99acf6fb Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:33:35 -0800 Subject: [PATCH 101/152] Update website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md don't need the domain for links when page is within our own docs site --- .../best-practices/how-we-structure/5-semantic-layer-marts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md index d064490354c..62e07a72e36 100644 --- a/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md +++ b/website/docs/best-practices/how-we-structure/5-semantic-layer-marts.md @@ -39,7 +39,7 @@ models ## When to make a mart - ❓ If we can go directly to staging models and it's better to serve normalized models to the Semantic Layer, then when, where, and why would we make a mart? - - 🕰️ We have models that have measures but no time dimension to aggregate against. The details of this are laid out in the [Semantic Layer guide](https://docs.getdbt.com/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) but in short, we need a time dimension to aggregate against in MetricFlow. Dimensional tables that + - 🕰️ We have models that have measures but no time dimension to aggregate against. The details of this are laid out in the [Semantic Layer guide](/best-practices/how-we-build-our-metrics/semantic-layer-1-intro) but in short, we need a time dimension to aggregate against in MetricFlow. Dimensional tables that - 🧱 We want to **materialize** our model in various ways. - 👯 We want to **version** our model. - 🛒 We have various related models that make more sense as **one wider component**. From 9f2b122fb0bbf492c50aa47d81fb54ebafd3bac2 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:35:02 -0800 Subject: [PATCH 102/152] Update website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md don't need the domain for links when page is within our own docs site --- .../best-practices/how-we-style/6-how-we-style-conclusion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md b/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md index 75551f095d3..24103861b97 100644 --- a/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md +++ b/website/docs/best-practices/how-we-style/6-how-we-style-conclusion.md @@ -31,7 +31,7 @@ Our models (typically) fit into two main categories:\ Things to note: - There are different types of models that typically exist in each of the above categories. See [Model Layers](#model-layers) for more information. -- Read [How we structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview) for an example and more details around organization. +- Read [How we structure our dbt projects](/best-practices/how-we-structure/1-guide-overview) for an example and more details around organization. ## Model Layers From 09a859378a937ece3af59d87ec32b8b57c57bdc7 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:45:55 -0800 Subject: [PATCH 103/152] Update website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md fix link --- website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md index b90ac1bce01..0ceb4929530 100644 --- a/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md +++ b/website/docs/docs/cloud/dbt-cloud-ide/dbt-cloud-tips.md @@ -46,7 +46,7 @@ There are default keyboard shortcuts that can help make development more product - Use [severity](/reference/resource-configs/severity) thresholds to set an acceptable number of failures for a test. - Use [incremental_strategy](/docs/build/incremental-models#about-incremental_strategy) in your incremental model config to implement the most effective behavior depending on the volume of your data and reliability of your unique keys. - Set `vars` in your `dbt_project.yml` to define global defaults for certain conditions, which you can then override using the `--vars` flag in your commands. -- Use [for loops](/guides/using-jinja#use-a-for-loop-in-models-for-repeated-sql) in Jinja to [DRY](https://docs.getdbt.com/terms/dry) up repetitive logic, such as selecting a series of columns that all require the same transformations and naming patterns to be applied. +- Use [for loops](/guides/using-jinja?step=3) in Jinja to DRY up repetitive logic, such as selecting a series of columns that all require the same transformations and naming patterns to be applied. - Instead of relying on post-hooks, use the [grants config](/reference/resource-configs/grants) to apply permission grants in the warehouse resiliently. - Define [source-freshness](/docs/build/sources#snapshotting-source-data-freshness) thresholds on your sources to avoid running transformations on data that has already been processed. - Use the `+` operator on the left of a model `dbt build --select +model_name` to run a model and all of its upstream dependencies. Use the `+` operator on the right of the model `dbt build --select model_name+` to run a model and everything downstream that depends on it. From 17bc23ada5ef5e65c941a2b8eb8c0bea63cc6059 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:54:50 -0800 Subject: [PATCH 104/152] Update website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md --- .../24-Nov-2022/dbt-databricks-unity-catalog-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md index ee46cb5f558..ce702434cf3 100644 --- a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md +++ b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md @@ -8,6 +8,6 @@ tags: [Nov-2022, v1.1.66.15] dbt Cloud is the easiest and most reliable way to develop and deploy a dbt project. It helps remove complexity while also giving you more features and better performance. A simpler Databricks connection experience with support for Databricks’ Unity Catalog and better modeling defaults is now available for your use. -For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](/guides/migrate-from-spark-to-databricks) your connection to the [dbt-databricks adapter](https://docs.getdbt.com/reference/warehouse-setups/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. +For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](/guides/migrate-from-spark-to-databricks) your connection to the [dbt-databricks adapter](/reference/warehouse-setups/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. Check out our [live blog post](https://www.getdbt.com/blog/dbt-cloud-databricks-experience/) to learn more. From 830da91f8c4ea602ef16085331c46589425e3f27 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:56:27 -0800 Subject: [PATCH 105/152] Update website/docs/docs/deploy/deploy-environments.md --- website/docs/docs/deploy/deploy-environments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/deploy/deploy-environments.md b/website/docs/docs/deploy/deploy-environments.md index 8f3353d07d1..237626dffc9 100644 --- a/website/docs/docs/deploy/deploy-environments.md +++ b/website/docs/docs/deploy/deploy-environments.md @@ -13,7 +13,7 @@ Deployment environments in dbt Cloud are crucial for deploying dbt jobs in produ A dbt Cloud project can have multiple deployment environments, providing you the flexibility and customization to tailor the execution of dbt jobs. You can use deployment environments to [create and schedule jobs](/docs/deploy/deploy-jobs#create-and-schedule-jobs), [enable continuous integration](/docs/deploy/continuous-integration), or more based on your specific needs or requirements. :::tip Learn how to manage dbt Cloud environments -To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview). +To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview). ::: This page reviews the different types of environments and how to configure your deployment environment in dbt Cloud. From 3d3da06e91ebebd4ff9e2bdbafd0df7f6498f290 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:57:41 -0800 Subject: [PATCH 106/152] Update website/docs/docs/deploy/deploy-environments.md --- website/docs/docs/deploy/deploy-environments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/deploy/deploy-environments.md b/website/docs/docs/deploy/deploy-environments.md index 237626dffc9..21308784434 100644 --- a/website/docs/docs/deploy/deploy-environments.md +++ b/website/docs/docs/deploy/deploy-environments.md @@ -186,7 +186,7 @@ This section allows you to determine the credentials that should be used when co ## Related docs -- [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview) - [Deploy jobs](/docs/deploy/deploy-jobs) - [CI jobs](/docs/deploy/continuous-integration) - [Delete a job or environment in dbt Cloud](/faqs/Environments/delete-environment-job) From 4d0ba52ad55c91e0fc8e6e465444eb8f6c5da5ba Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:59:36 -0800 Subject: [PATCH 107/152] Update website/docs/docs/environments-in-dbt.md --- website/docs/docs/environments-in-dbt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/environments-in-dbt.md b/website/docs/docs/environments-in-dbt.md index 0361a272c4f..ab899b09516 100644 --- a/website/docs/docs/environments-in-dbt.md +++ b/website/docs/docs/environments-in-dbt.md @@ -33,7 +33,7 @@ Configure environments to tell dbt Cloud or dbt Core how to build and execute yo ## Related docs -- [dbt Cloud environment best practices](https://docs.getdbt.com/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview) - [Deployment environments](/docs/deploy/deploy-environments) - [About dbt Core versions](/docs/dbt-versions/core) - [Set Environment variables in dbt Cloud](/docs/build/environment-variables#special-environment-variables) From 29e0de831e8f07cfbb187151af52c894bce25be1 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:01:57 -0800 Subject: [PATCH 108/152] Update website/docs/faqs/Project/multiple-resource-yml-files.md --- website/docs/faqs/Project/multiple-resource-yml-files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/faqs/Project/multiple-resource-yml-files.md b/website/docs/faqs/Project/multiple-resource-yml-files.md index a60c198de5d..04e1702a162 100644 --- a/website/docs/faqs/Project/multiple-resource-yml-files.md +++ b/website/docs/faqs/Project/multiple-resource-yml-files.md @@ -9,4 +9,4 @@ It's up to you: - Some folks find it useful to have one file per model (or source / snapshot / seed etc) - Some find it useful to have one per directory, documenting and testing multiple models in one file -Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). +Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](/best-practices/how-we-structure/1-guide-overview). From 48ad5e25e5665cd81a47a2ab5934d1e106667b64 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:02:47 -0800 Subject: [PATCH 109/152] Update website/docs/faqs/Project/resource-yml-name.md --- website/docs/faqs/Project/resource-yml-name.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/faqs/Project/resource-yml-name.md b/website/docs/faqs/Project/resource-yml-name.md index 78d541cbd54..c26cff26474 100644 --- a/website/docs/faqs/Project/resource-yml-name.md +++ b/website/docs/faqs/Project/resource-yml-name.md @@ -10,4 +10,4 @@ It's up to you! Here's a few options: - Use the same name as your directory (assuming you're using sensible names for your directories) - If you test and document one model (or seed, snapshot, macro etc.) per file, you can give it the same name as the model (or seed, snapshot, macro etc.) -Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). +Choose what works for your team. We have more recommendations in our guide on [structuring dbt projects](/best-practices/how-we-structure/1-guide-overview). From 6d4959f9242d3036d0be114ab4a2d2b1ac2ae3e8 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:03:44 -0800 Subject: [PATCH 110/152] Update website/docs/faqs/Project/structure-a-project.md --- website/docs/faqs/Project/structure-a-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/faqs/Project/structure-a-project.md b/website/docs/faqs/Project/structure-a-project.md index 136c5b188bf..a9ef53f5c8f 100644 --- a/website/docs/faqs/Project/structure-a-project.md +++ b/website/docs/faqs/Project/structure-a-project.md @@ -8,4 +8,4 @@ id: structure-a-project There's no one best way to structure a project! Every organization is unique. -If you're just getting started, check out how we (dbt Labs) [structure our dbt projects](https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview). +If you're just getting started, check out how we (dbt Labs) [structure our dbt projects](/best-practices/how-we-structure/1-guide-overview). From 3b4e603ce5b1a7a578558803fde2b543f17f7e26 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:08:44 -0800 Subject: [PATCH 111/152] Update website/docs/guides/debug-schema-names.md --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index 171e1544a19..795128d83ff 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -27,7 +27,7 @@ You can also follow along via this video: ## Search for a macro named `generate_schema_name` Do a file search to check if you have a macro named `generate_schema_name` in the `macros` directory of your project. -### You do not have a macro named `generate_schema_name` in my project +### You do not have a macro named `generate_schema_name` in your project This means that you are using dbt's default implementation of the macro, as defined [here](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/include/global_project/macros/get_custom_name/get_custom_schema.sql#L47C1-L60) ```sql From c77fe1ebf695fe8c2e6edc25c984dc649e0c0fa0 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:10:26 -0800 Subject: [PATCH 112/152] Update website/docs/guides/debug-schema-names.md --- website/docs/guides/debug-schema-names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/debug-schema-names.md b/website/docs/guides/debug-schema-names.md index 795128d83ff..c7bf1a195b1 100644 --- a/website/docs/guides/debug-schema-names.md +++ b/website/docs/guides/debug-schema-names.md @@ -49,7 +49,7 @@ This means that you are using dbt's default implementation of the macro, as defi Note that this logic is designed so that two dbt users won't accidentally overwrite each other's work by writing to the same schema. -### You have a `generate_schema_name` macro in my project that calls another macro +### You have a `generate_schema_name` macro in a project that calls another macro If your `generate_schema_name` macro looks like so: ```sql {% macro generate_schema_name(custom_schema_name, node) -%} From 5013b67b56b9e5be1ff0b5c27cd3c149d785a73e Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:17:22 -0800 Subject: [PATCH 113/152] Update website/docs/sql-reference/aggregate-functions/sql-array-agg.md --- website/docs/sql-reference/aggregate-functions/sql-array-agg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/aggregate-functions/sql-array-agg.md b/website/docs/sql-reference/aggregate-functions/sql-array-agg.md index 9f4af7ca1fc..a6f508a7bef 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-array-agg.md +++ b/website/docs/sql-reference/aggregate-functions/sql-array-agg.md @@ -59,4 +59,4 @@ Looking at the query results—this makes sense! We’d expect newer orders to l There are definitely too many use cases to list out for using the ARRAY_AGG function in your dbt models, but it’s very likely that ARRAY_AGG is used pretty downstream in your since you likely don’t want your data so bundled up earlier in your DAG to improve modularity and dryness. A few downstream use cases for ARRAY_AGG: - In [`export_` models](https://www.getdbt.com/open-source-data-culture/reverse-etl-playbook) that are used to send data to platforms using a tool to pair down multiple rows into a single row. Some downstream platforms, for example, require certain values that we’d usually keep as separate rows to be one singular row per customer or user. ARRAY_AGG is handy to bring multiple column values together by a singular id, such as creating an array of all items a user has ever purchased and sending that array downstream to an email platform to create a custom email campaign. -- Similar to export models, you may see ARRAY_AGG used in [mart tables](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to create final aggregate arrays per a singular dimension; performance concerns of ARRAY_AGG in these likely larger tables can potentially be bypassed with use of [incremental models in dbt](https://docs.getdbt.com/docs/build/incremental-models). +- Similar to export models, you may see ARRAY_AGG used in [mart tables](/best-practices/how-we-structure/4-marts) to create final aggregate arrays per a singular dimension; performance concerns of ARRAY_AGG in these likely larger tables can potentially be bypassed with use of [incremental models in dbt](/docs/build/incremental-models). From b49f8c40e962646199d1ab254104bac8af790243 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:18:24 -0800 Subject: [PATCH 114/152] Update website/docs/sql-reference/aggregate-functions/sql-avg.md --- website/docs/sql-reference/aggregate-functions/sql-avg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/aggregate-functions/sql-avg.md b/website/docs/sql-reference/aggregate-functions/sql-avg.md index afb766f12e2..d1dba119292 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-avg.md +++ b/website/docs/sql-reference/aggregate-functions/sql-avg.md @@ -48,7 +48,7 @@ Snowflake, Databricks, Google BigQuery, and Amazon Redshift all support the abil ## AVG function use cases We most commonly see the AVG function used in data work to calculate: -- The average of key metrics (ex. Average CSAT, average lead time, average order amount) in downstream [fact or dim models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) +- The average of key metrics (ex. Average CSAT, average lead time, average order amount) in downstream [fact or dim models](/best-practices/how-we-structure/4-marts) - Rolling or moving averages (ex. 7-day, 30-day averages for key metrics) using window functions - Averages in [dbt metrics](https://docs.getdbt.com/docs/build/metrics) From ace443fcd3484b674408c434f59834ea9f483063 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:19:51 -0800 Subject: [PATCH 115/152] Update website/docs/sql-reference/aggregate-functions/sql-round.md --- website/docs/sql-reference/aggregate-functions/sql-round.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/aggregate-functions/sql-round.md b/website/docs/sql-reference/aggregate-functions/sql-round.md index 7652c881789..bc9669e22cb 100644 --- a/website/docs/sql-reference/aggregate-functions/sql-round.md +++ b/website/docs/sql-reference/aggregate-functions/sql-round.md @@ -57,7 +57,7 @@ Google BigQuery, Amazon Redshift, Snowflake, and Databricks all support the abil ## ROUND function use cases -If you find yourself rounding numeric data, either in data models or ad-hoc analyses, you’re probably rounding to improve the readability and usability of your data using downstream [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) or [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts). Specifically, you’ll likely use the ROUND function to: +If you find yourself rounding numeric data, either in data models or ad-hoc analyses, you’re probably rounding to improve the readability and usability of your data using downstream [intermediate](/best-practices/how-we-structure/3-intermediate) or [mart models](/best-practices/how-we-structure/4-marts). Specifically, you’ll likely use the ROUND function to: - Make numeric calculations using division or averages a little cleaner and easier to understand - Create concrete buckets of data for a cleaner distribution of values during ad-hoc analysis From f16389426375a6f97782a75d0199bba89a3e3d4d Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:21:07 -0800 Subject: [PATCH 116/152] Update website/docs/sql-reference/clauses/sql-limit.md --- website/docs/sql-reference/clauses/sql-limit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/clauses/sql-limit.md b/website/docs/sql-reference/clauses/sql-limit.md index a2c49866592..a02b851e37d 100644 --- a/website/docs/sql-reference/clauses/sql-limit.md +++ b/website/docs/sql-reference/clauses/sql-limit.md @@ -51,7 +51,7 @@ This simple query using the [Jaffle Shop’s](https://github.com/dbt-labs/jaffle After ensuring that this is the result you want from this query, you can omit the LIMIT in your final data model. :::tip Save money and time by limiting data in development -You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](https://docs.getdbt.com/best-practices/best-practice-workflows#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. +You could limit your data used for development by manually adding a LIMIT statement, a WHERE clause to your query, or by using a [dbt macro to automatically limit data based](/best-practices/best-practice-workflows#limit-the-data-processed-when-in-development) on your development environment to help reduce your warehouse usage during dev periods. ::: ## LIMIT syntax in Snowflake, Databricks, BigQuery, and Redshift From 4036a140d8d47b1471cd3810a62f275587e7e68c Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:26:02 -0800 Subject: [PATCH 117/152] Update website/docs/sql-reference/clauses/sql-order-by.md --- website/docs/sql-reference/clauses/sql-order-by.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/clauses/sql-order-by.md b/website/docs/sql-reference/clauses/sql-order-by.md index 39337de1e48..d18946d0d16 100644 --- a/website/docs/sql-reference/clauses/sql-order-by.md +++ b/website/docs/sql-reference/clauses/sql-order-by.md @@ -57,7 +57,7 @@ Since the ORDER BY clause is a SQL fundamental, data warehouses, including Snowf ## ORDER BY use cases We most commonly see the ORDER BY clause used in data work to: -- Analyze data for both initial exploration of raw data sources and ad hoc querying of [mart datasets](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) +- Analyze data for both initial exploration of raw data sources and ad hoc querying of [mart datasets](/best-practices/how-we-structure/4-marts) - Identify the top 5/10/50/100 of a dataset when used in pair with a [LIMIT](/sql-reference/limit) - (For Snowflake) Optimize the performance of large incremental models that use both a `cluster_by` [configuration](https://docs.getdbt.com/reference/resource-configs/snowflake-configs#using-cluster_by) and ORDER BY statement - Control the ordering of window function partitions (ex. `row_number() over (partition by user_id order by updated_at)`) From ec705a277db0c95959b503dcc6d9adc59fae470e Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:26:34 -0800 Subject: [PATCH 118/152] Update website/docs/sql-reference/joins/sql-self-join.md --- website/docs/sql-reference/joins/sql-self-join.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/joins/sql-self-join.md b/website/docs/sql-reference/joins/sql-self-join.md index bb4237319f0..6d9a7d3261e 100644 --- a/website/docs/sql-reference/joins/sql-self-join.md +++ b/website/docs/sql-reference/joins/sql-self-join.md @@ -66,6 +66,6 @@ This query utilizing a self join adds the `parent_name` of skus that have non-nu ## SQL self join use cases -Again, self joins are probably rare in your dbt project and will most often be utilized in tables that contain a hierarchical structure, such as consisting of a column which is a foreign key to the primary key of the same table. If you do have use cases for self joins, such as in the example above, you’ll typically want to perform that self join early upstream in your , such as in a [staging](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) or [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) model; if your raw, unjoined table is going to need to be accessed further downstream sans self join, that self join should happen in a modular intermediate model. +Again, self joins are probably rare in your dbt project and will most often be utilized in tables that contain a hierarchical structure, such as consisting of a column which is a foreign key to the primary key of the same table. If you do have use cases for self joins, such as in the example above, you’ll typically want to perform that self join early upstream in your , such as in a [staging](/best-practices/how-we-structure/2-staging) or [intermediate](/best-practices/how-we-structure/3-intermediate) model; if your raw, unjoined table is going to need to be accessed further downstream sans self join, that self join should happen in a modular intermediate model. You can also use self joins to create a cartesian product (aka a cross join) of a table against itself. Again, slim use cases, but still there for you if you need it 😉 From 2a07b7b0294ae3421d29b376fe84026110a99a2e Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:27:29 -0800 Subject: [PATCH 119/152] Update website/docs/sql-reference/joins/sql-left-join.md --- website/docs/sql-reference/joins/sql-left-join.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/joins/sql-left-join.md b/website/docs/sql-reference/joins/sql-left-join.md index 24fbb2bfa0c..914f83bb7e3 100644 --- a/website/docs/sql-reference/joins/sql-left-join.md +++ b/website/docs/sql-reference/joins/sql-left-join.md @@ -73,4 +73,4 @@ Left joins are a fundamental in data modeling and analytics engineering work—t Something to note if you use left joins: if there are multiple records for an individual key in the left join database object, be aware that duplicates can potentially be introduced in the final query result. This is where dbt tests, such as testing for uniqueness and [equal row count](https://github.com/dbt-labs/dbt-utils#equal_rowcount-source) across upstream source tables and downstream child models, can help you identify faulty data modeling logic and improve data quality. ::: -Where you will not (and should not) see left joins is in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to improve modularity and cleanliness. +Where you will not (and should not) see left joins is in [staging models](/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](/best-practices/how-we-structure/3-intermediate) and [mart models](/best-practices/how-we-structure/4-marts) to improve modularity and cleanliness. From 7c065e1d1567be21ad7938589cfb6fbb1d89a8bf Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:28:07 -0800 Subject: [PATCH 120/152] Update website/docs/sql-reference/joins/sql-inner-join.md --- website/docs/sql-reference/joins/sql-inner-join.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sql-reference/joins/sql-inner-join.md b/website/docs/sql-reference/joins/sql-inner-join.md index e1c2d6151c8..951e3675bc7 100644 --- a/website/docs/sql-reference/joins/sql-inner-join.md +++ b/website/docs/sql-reference/joins/sql-inner-join.md @@ -66,5 +66,5 @@ Because there’s no `user_id` = 4 in Table A and no `user_id` = 2 in Table B, r ## SQL inner join use cases -There are probably countless scenarios where you’d want to inner join multiple tables together—perhaps you have some really nicely structured tables with the exact same primary keys that should really just be one larger, wider table or you’re joining two tables together don’t want any null or missing column values if you used a left or right join—it’s all pretty dependent on your source data and end use cases. Where you will not (and should not) see inner joins is in [staging models](https://docs.getdbt.com/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](https://docs.getdbt.com/best-practices/how-we-structure/3-intermediate) and [mart models](https://docs.getdbt.com/best-practices/how-we-structure/4-marts) to improve modularity and DAG cleanliness. +There are probably countless scenarios where you’d want to inner join multiple tables together—perhaps you have some really nicely structured tables with the exact same primary keys that should really just be one larger, wider table or you’re joining two tables together don’t want any null or missing column values if you used a left or right join—it’s all pretty dependent on your source data and end use cases. Where you will not (and should not) see inner joins is in [staging models](/best-practices/how-we-structure/2-staging) that are used to clean and prep raw source data for analytics uses. Any joins in your dbt projects should happen further downstream in [intermediate](/best-practices/how-we-structure/3-intermediate) and [mart models](/best-practices/how-we-structure/4-marts) to improve modularity and DAG cleanliness. From d866035e5cfc05742c827f19401e26ba42964bf1 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:50:08 -0800 Subject: [PATCH 121/152] Update website/docs/guides/custom-cicd-pipelines.md per style guide, use sentence case for titles --- website/docs/guides/custom-cicd-pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/custom-cicd-pipelines.md b/website/docs/guides/custom-cicd-pipelines.md index bf781204fc5..672c6e6dab8 100644 --- a/website/docs/guides/custom-cicd-pipelines.md +++ b/website/docs/guides/custom-cicd-pipelines.md @@ -1,5 +1,5 @@ --- -title: Customizing CI/CD with Custom Pipelines +title: Customizing CI/CD with custom pipelines id: custom-cicd-pipelines description: "Learn the benefits of version-controlled analytics code and custom pipelines in dbt for enhanced code testing and workflow automation during the development process." displayText: Learn version-controlled code, custom pipelines, and enhanced code testing. From 0fe005a39997e67dde10184ec3bb7b91377d3f37 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:53:06 -0800 Subject: [PATCH 122/152] Update website/docs/docs/cloud/billing.md --- website/docs/docs/cloud/billing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/billing.md b/website/docs/docs/cloud/billing.md index f66e2aad363..31b7689ceb9 100644 --- a/website/docs/docs/cloud/billing.md +++ b/website/docs/docs/cloud/billing.md @@ -215,7 +215,7 @@ If you want to ensure that you're building views whenever the logic is changed, Executing `dbt build` in this context is unnecessary because the CI job was used to both run and test the code that just got merged into main. 5. Under the **Execution Settings**, select the default production job to compare changes against: - **Defer to a previous run state** — Select the “Merge Job” you created so the job compares and identifies what has changed since the last merge. -6. In your dbt project, follow the steps in Run a dbt Cloud job on merge in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide to create a script to trigger the dbt Cloud API to run your job after a merge happens within your git repository or watch this [video](https://www.loom.com/share/e7035c61dbed47d2b9b36b5effd5ee78?sid=bcf4dd2e-b249-4e5d-b173-8ca204d9becb). +6. In your dbt project, follow the steps in Run a dbt Cloud job on merge in the [Customizing CI/CD with custom pipelines](/guides/custom-cicd-pipelines) guide to create a script to trigger the dbt Cloud API to run your job after a merge happens within your git repository or watch this [video](https://www.loom.com/share/e7035c61dbed47d2b9b36b5effd5ee78?sid=bcf4dd2e-b249-4e5d-b173-8ca204d9becb). The purpose of the merge job is to: From 7de9d7285fe915a7db1c12e25c7f2de5f506f036 Mon Sep 17 00:00:00 2001 From: Ly Nguyen <107218380+nghi-ly@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:54:03 -0800 Subject: [PATCH 123/152] Update website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md --- .../dbt-versions/release-notes/07-June-2023/product-docs-jun.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md index 4ead401a759..db73597cd63 100644 --- a/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md +++ b/website/docs/docs/dbt-versions/release-notes/07-June-2023/product-docs-jun.md @@ -32,4 +32,4 @@ Here's what's new to [docs.getdbt.com](http://docs.getdbt.com/) in June: ## New 📚 Guides, ✏️ blog posts, and FAQs -- Add an Azure DevOps example in the [Customizing CI/CD with Custom Pipelines](/guides/custom-cicd-pipelines) guide. +- Add an Azure DevOps example in the [Customizing CI/CD with custom pipelines](/guides/custom-cicd-pipelines) guide. From 6d1473150d8105dd70e30fdaff3190c01610794b Mon Sep 17 00:00:00 2001 From: Ly Nguyen Date: Fri, 10 Nov 2023 16:21:25 -0800 Subject: [PATCH 124/152] Fix old redirects, remove duplicate steps --- .../dbt-databricks-unity-catalog-support.md | 2 +- .../docs/docs/deploy/deploy-environments.md | 4 ++-- website/docs/docs/environments-in-dbt.md | 2 +- website/docs/guides/dbt-python-snowpark.md | 21 ------------------- 4 files changed, 4 insertions(+), 25 deletions(-) diff --git a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md index ce702434cf3..012615e1e4e 100644 --- a/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md +++ b/website/docs/docs/dbt-versions/release-notes/24-Nov-2022/dbt-databricks-unity-catalog-support.md @@ -8,6 +8,6 @@ tags: [Nov-2022, v1.1.66.15] dbt Cloud is the easiest and most reliable way to develop and deploy a dbt project. It helps remove complexity while also giving you more features and better performance. A simpler Databricks connection experience with support for Databricks’ Unity Catalog and better modeling defaults is now available for your use. -For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](/guides/migrate-from-spark-to-databricks) your connection to the [dbt-databricks adapter](/reference/warehouse-setups/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. +For all the Databricks customers already using dbt Cloud with the dbt-spark adapter, you can now [migrate](/guides/migrate-from-spark-to-databricks) your connection to the [dbt-databricks adapter](/docs/core/connect-data-platform/databricks-setup) to get the benefits. [Databricks](https://www.databricks.com/blog/2022/11/17/introducing-native-high-performance-integration-dbt-cloud.html) is committed to maintaining and improving the adapter, so this integrated experience will continue to provide the best of dbt and Databricks. Check out our [live blog post](https://www.getdbt.com/blog/dbt-cloud-databricks-experience/) to learn more. diff --git a/website/docs/docs/deploy/deploy-environments.md b/website/docs/docs/deploy/deploy-environments.md index 21308784434..650fdb1c28a 100644 --- a/website/docs/docs/deploy/deploy-environments.md +++ b/website/docs/docs/deploy/deploy-environments.md @@ -13,7 +13,7 @@ Deployment environments in dbt Cloud are crucial for deploying dbt jobs in produ A dbt Cloud project can have multiple deployment environments, providing you the flexibility and customization to tailor the execution of dbt jobs. You can use deployment environments to [create and schedule jobs](/docs/deploy/deploy-jobs#create-and-schedule-jobs), [enable continuous integration](/docs/deploy/continuous-integration), or more based on your specific needs or requirements. :::tip Learn how to manage dbt Cloud environments -To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview). +To learn different approaches to managing dbt Cloud environments and recommendations for your organization's unique needs, read [dbt Cloud environment best practices](/guides/set-up-ci). ::: This page reviews the different types of environments and how to configure your deployment environment in dbt Cloud. @@ -186,7 +186,7 @@ This section allows you to determine the credentials that should be used when co ## Related docs -- [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](/guides/set-up-ci) - [Deploy jobs](/docs/deploy/deploy-jobs) - [CI jobs](/docs/deploy/continuous-integration) - [Delete a job or environment in dbt Cloud](/faqs/Environments/delete-environment-job) diff --git a/website/docs/docs/environments-in-dbt.md b/website/docs/docs/environments-in-dbt.md index ab899b09516..f0691761dd6 100644 --- a/website/docs/docs/environments-in-dbt.md +++ b/website/docs/docs/environments-in-dbt.md @@ -33,7 +33,7 @@ Configure environments to tell dbt Cloud or dbt Core how to build and execute yo ## Related docs -- [dbt Cloud environment best practices](/best-practices/environment-setup/1-env-guide-overview) +- [dbt Cloud environment best practices](/guides/set-up-ci) - [Deployment environments](/docs/deploy/deploy-environments) - [About dbt Core versions](/docs/dbt-versions/core) - [Set Environment variables in dbt Cloud](/docs/build/environment-variables#special-environment-variables) diff --git a/website/docs/guides/dbt-python-snowpark.md b/website/docs/guides/dbt-python-snowpark.md index 35842eb8d91..55e6b68c172 100644 --- a/website/docs/guides/dbt-python-snowpark.md +++ b/website/docs/guides/dbt-python-snowpark.md @@ -67,27 +67,6 @@ Overall we are going to set up the environments, build scalable pipelines in dbt 6. Finally, create a new Worksheet by selecting **+ Worksheet** in the upper right corner. - -33 1. Log in to your trial Snowflake account. You can [sign up for a Snowflake Trial Account using this form](https://signup.snowflake.com/) if you don’t have one. -2. Ensure that your account is set up using **AWS** in the **US East (N. Virginia)**. We will be copying the data from a public AWS S3 bucket hosted by dbt Labs in the us-east-1 region. By ensuring our Snowflake environment setup matches our bucket region, we avoid any multi-region data copy and retrieval latency issues. - - - -3. After creating your account and verifying it from your sign-up email, Snowflake will direct you back to the UI called Snowsight. - -4. When Snowsight first opens, your window should look like the following, with you logged in as the ACCOUNTADMIN with demo worksheets open: - - - - -5. Navigate to **Admin > Billing & Terms**. Click **Enable > Acknowledge & Continue** to enable Anaconda Python Packages to run in Snowflake. - - - - - -6. Finally, create a new Worksheet by selecting **+ Worksheet** in the upper right corner. - ## Connect to data source We need to obtain our data source by copying our Formula 1 data into Snowflake tables from a public S3 bucket that dbt Labs hosts. From cf000ccaa9d552e34ede9b2858d134f759d79b65 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 10:59:48 +0000 Subject: [PATCH 125/152] folding in williams' feedback --- .../semantic-layer-4-build-metrics.md | 2 +- website/docs/docs/build/metricflow-commands.md | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md index 8b112f2f828..da83adbdc69 100644 --- a/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md +++ b/website/docs/best-practices/how-we-build-our-metrics/semantic-layer-4-build-metrics.md @@ -36,7 +36,7 @@ metrics: Use [MetricFlow commands](/docs/build/metricflow-commands#metricflow) for metric validation or queries during development, and apply the following conventions based on your environment: -- For dbt Cloud, use the `dbt sl` prefix before the command (such as, `dbt sl parse` or `dbt sl query`). +- For dbt Cloud, use the `dbt sl` prefix before the command (such as, `dbt sl query`). - For dbt Core, use the `mf` prefix (such as `mf validate-configs` or `mf query)`. Follow these best practices when updating your semantic layer code, using the `mf` command as an example (replace `mf` with `dbt sl` if you're using dbt Cloud): diff --git a/website/docs/docs/build/metricflow-commands.md b/website/docs/docs/build/metricflow-commands.md index fd120591900..4d2477ad2ed 100644 --- a/website/docs/docs/build/metricflow-commands.md +++ b/website/docs/docs/build/metricflow-commands.md @@ -179,8 +179,9 @@ Options: ### Validate-configs -This command performs validations against the defined semantic model configurations: - +The following command performs validations against the defined semantic model configurations. + +Note, in dbt Cloud you don't need to validate the Semantic Layer config separately. Running a dbt command (such as `dbt parse`, `dbt build`, `dbt compile`, `dbt run`) automatically checks it. ```bash @@ -205,11 +206,12 @@ Options: ### Health checks -This command performs a health check against the data platform you provided in the configs: - +The following command performs a health check against the data platform you provided in the configs. + +Note, in dbt Cloud the `health-checks` command isn't required since it uses dbt Cloud's credentials to perform the health check. ```bash -mf health-checks #in dbt Core +mf health-checks # In dbt Core ``` ### Tutorial From 437f78faa2351005bc94256ecc948fcc39ca37b9 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:02:11 +0000 Subject: [PATCH 126/152] Update website/snippets/_sl-deprecation-notice.md Co-authored-by: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> --- website/snippets/_sl-deprecation-notice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index 8d107f39fff..07b672c1929 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,6 +1,6 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer For users on dbt version 1.5 or earlier — starting on December 15th, 2023, support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. -After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. +After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. ::: From e106c524c36ee3188070bb92d0e8ae0a0b734a53 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 11:12:53 +0000 Subject: [PATCH 127/152] clarify language --- website/snippets/_sl-deprecation-notice.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index 07b672c1929..95a7c7e26c7 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,5 +1,6 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer -For users on dbt version 1.5 or earlier — starting on December 15th, 2023, support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. +For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access enhanced features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/migration/sl-migration). + After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. From 4062f9e1e3175eca691b467beb988fde1443db61 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 11:13:13 +0000 Subject: [PATCH 128/152] use latest --- website/snippets/_sl-deprecation-notice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index 95a7c7e26c7..0e07f315976 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,5 +1,5 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer -For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access enhanced features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/migration/sl-migration). +For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/migration/sl-migration). After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. From 0e9bd1518cc99a41f8a50b4dd87223177842a070 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 11:15:40 +0000 Subject: [PATCH 129/152] fix link --- website/snippets/_sl-deprecation-notice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index 0e07f315976..f521fedc7b4 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,5 +1,5 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer -For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/migration/sl-migration). +For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. From a36c3e71a56210159931fbf432aa68172a5dedcb Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 11:16:19 +0000 Subject: [PATCH 130/152] fix link --- website/snippets/_legacy-sl-callout.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/snippets/_legacy-sl-callout.md b/website/snippets/_legacy-sl-callout.md index 61d3a1c910f..ae40151ab1c 100644 --- a/website/snippets/_legacy-sl-callout.md +++ b/website/snippets/_legacy-sl-callout.md @@ -2,9 +2,8 @@ The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), introducing new features such as dbt Semantic Layer APIs. The APIs integrate with data applications, such as Tableau and Google Sheets, to query metrics and unlock insights. -For users on dbt version 1.5 or earlier, starting on December 15th, 2023, support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. To benefit from the latest features, upgrade to the most [recent version](/guides/migration/sl-migration) of dbt and embrace the enhanced dbt Semantic Layer, powered by MetricFlow. +For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). -After this date, dbt Labs won't offer support, bug fixes, or security updates for these deprecated features. Documentation for them will be discontinued, and they will be removed from the dbt Cloud user interface. +After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. ::: -support for dbt Metrics and the old dbt Semantic Layer (v1.5 and below) will end. From 882d19bbf68d9cc992a139a02feecb40c726a5dd Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 11:17:21 +0000 Subject: [PATCH 131/152] consistency --- website/snippets/_legacy-sl-callout.md | 2 +- website/snippets/_sl-deprecation-notice.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/snippets/_legacy-sl-callout.md b/website/snippets/_legacy-sl-callout.md index ae40151ab1c..b0575b60729 100644 --- a/website/snippets/_legacy-sl-callout.md +++ b/website/snippets/_legacy-sl-callout.md @@ -2,7 +2,7 @@ The dbt Semantic Layer has undergone a [significant revamp](https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/), introducing new features such as dbt Semantic Layer APIs. The APIs integrate with data applications, such as Tableau and Google Sheets, to query metrics and unlock insights. -For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). +For users of the dbt Semantic Layer on version 1.5 or lower — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. diff --git a/website/snippets/_sl-deprecation-notice.md b/website/snippets/_sl-deprecation-notice.md index f521fedc7b4..19bf19c2d90 100644 --- a/website/snippets/_sl-deprecation-notice.md +++ b/website/snippets/_sl-deprecation-notice.md @@ -1,5 +1,5 @@ :::info Deprecation of dbt Metrics and the legacy dbt Semantic Layer -For users of the dbt Semantic Layer on version 1.5 or earlier — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). +For users of the dbt Semantic Layer on version 1.5 or lower — Support for dbt Metrics and the legacy dbt Semantic Layer ends on December 15th, 2023. To access the latest features, migrate to the updated version using the [dbt Semantic Layer migration guide](/guides/sl-migration). After December 15th, dbt Labs will no longer support these deprecated features, they will be removed from the dbt Cloud user interface, and their documentation removed from the docs site. From 9abe20d09b5a67f058591ec7244571f70b6612ab Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:40:10 +0000 Subject: [PATCH 132/152] Update meta.md fix header --- website/docs/reference/resource-configs/meta.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/reference/resource-configs/meta.md b/website/docs/reference/resource-configs/meta.md index 9ccf2cc60dc..bc0c0c7c041 100644 --- a/website/docs/reference/resource-configs/meta.md +++ b/website/docs/reference/resource-configs/meta.md @@ -277,8 +277,8 @@ seeds: select 1 as id ``` - - +

    + ### Assign owner in the dbt_project.yml as a config property From 39449daf6f9e4211af4b43346d2efd249140bda9 Mon Sep 17 00:00:00 2001 From: mirnawong1 Date: Mon, 13 Nov 2023 15:44:18 +0000 Subject: [PATCH 133/152] tweak defer lanuge --- .../docs/docs/cloud/about-cloud-develop-defer.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 1861a6d8a79..302a3548351 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -9,16 +9,12 @@ pagination_next: "docs/cloud/cloud-cli-installation" [Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). This is powered by using a production manifest for comparison, and dbt will resolve the `{{ ref() }}` function with upstream production artifacts. -By default, dbt follows these rules: +Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. By default, dbt follows these rules: -- Defers to the production environment when there's no development schema. -- If a development schema exists, dbt will prioritize those changes, which minimizes development time and avoids unnecessary model builds. - -Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. - -For specific scenarios: -- Use [`--favor-state`](/reference/node-selection/defer#favor-state) to always use production artifacts to resolve the ref. -- If facing issues with outdated tables in the development schema, `--favor-state` is an alternative to defer. +- dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. +- If a development version of a deferred model exists, dbt preferentially uses that table when resolving the reference. +- Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag will override this behavior and _always_ resolve refs using the production metadata, regardless of the presence of a development relation. + - If facing issues with outdated tables in the development schema, `--favor-state` is an alternative to defer. For a clean slate, it's a good practice to drop the development schema at the start and end of your development cycle. From 5467027d0d2f4c8c7d2c65d236e48a1c49397ddd Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:49:59 +0000 Subject: [PATCH 134/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: dave-connors-3 <73915542+dave-connors-3@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 302a3548351..f0fcaec9f39 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -12,7 +12,7 @@ pagination_next: "docs/cloud/cloud-cli-installation" Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. By default, dbt follows these rules: - dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. -- If a development version of a deferred model exists, dbt preferentially uses that table when resolving the reference. +- If a development version of a deferred model exists, dbt preferentially uses that database location when resolving the reference. - Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag will override this behavior and _always_ resolve refs using the production metadata, regardless of the presence of a development relation. - If facing issues with outdated tables in the development schema, `--favor-state` is an alternative to defer. From caa28652e56f0bff48a13029262c6a035624ad16 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:52:45 +0000 Subject: [PATCH 135/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: dave-connors-3 <73915542+dave-connors-3@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 1 - 1 file changed, 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index f0fcaec9f39..fca09530f62 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -14,7 +14,6 @@ Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to pr - dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. - If a development version of a deferred model exists, dbt preferentially uses that database location when resolving the reference. - Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag will override this behavior and _always_ resolve refs using the production metadata, regardless of the presence of a development relation. - - If facing issues with outdated tables in the development schema, `--favor-state` is an alternative to defer. For a clean slate, it's a good practice to drop the development schema at the start and end of your development cycle. From 2ea6ba1b02fd369d7ff33cd1e5b20fd08f8b6983 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:53:55 +0000 Subject: [PATCH 136/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md --- website/docs/docs/cloud/about-cloud-develop-defer.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index fca09530f62..d261a990b90 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -9,7 +9,9 @@ pagination_next: "docs/cloud/cloud-cli-installation" [Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). This is powered by using a production manifest for comparison, and dbt will resolve the `{{ ref() }}` function with upstream production artifacts. -Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. By default, dbt follows these rules: +Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. + +By default, dbt follows these rules: - dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. - If a development version of a deferred model exists, dbt preferentially uses that database location when resolving the reference. From 08cd63b4675e2c44ca329e6d8062e6b30fde4c28 Mon Sep 17 00:00:00 2001 From: Doug Beatty <44704949+dbeatty10@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:55:43 -0700 Subject: [PATCH 137/152] Fix rendering of hyperlink (#4450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Preview - Click on the "macros" tab: - https://docs-getdbt-com-git-dbeatty-docs-config-fix-link-dbt-labs.vercel.app/reference/resource-configs/docs ## What are you changing in this pull request and why? This hyperlink isn't rendering correctly: image ### 🎩 Preview after the fix: image ## Checklist - [x] Review the [Content style guide](https://github.com/dbt-labs/docs.getdbt.com/blob/current/contributing/content-style-guide.md) and [About versioning](https://github.com/dbt-labs/docs.getdbt.com/blob/current/contributing/single-sourcing-content.md#adding-a-new-version) so my content adheres to these guidelines. - [x] I have checked the preview renders correctly --- website/docs/reference/resource-configs/docs.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/reference/resource-configs/docs.md b/website/docs/reference/resource-configs/docs.md index d300979a826..c5e35dd64f4 100644 --- a/website/docs/reference/resource-configs/docs.md +++ b/website/docs/reference/resource-configs/docs.md @@ -108,7 +108,9 @@ macros: show: true | false ``` + Also refer to [macro properties](/reference/macro-properties). + From 9a9118b827f42ea7983027ec62cd081ca2659063 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:46:04 +0000 Subject: [PATCH 138/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: Leona B. Campbell <3880403+runleonarun@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index d261a990b90..0c961cf0db1 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -15,7 +15,7 @@ By default, dbt follows these rules: - dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. - If a development version of a deferred model exists, dbt preferentially uses that database location when resolving the reference. -- Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag will override this behavior and _always_ resolve refs using the production metadata, regardless of the presence of a development relation. +- Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag overrides the default behavior and _always_ resolve refs using production metadata, regardless of the presence of a development relation. For a clean slate, it's a good practice to drop the development schema at the start and end of your development cycle. From e24ee7074cd5ae781d4a8027d116b49c6b6842f7 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:46:10 +0000 Subject: [PATCH 139/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: Leona B. Campbell <3880403+runleonarun@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 0c961cf0db1..5d3b76c7430 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -14,7 +14,7 @@ Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to pr By default, dbt follows these rules: - dbt uses the production locations of parent models to resolve `{{ ref() }}` functions, based on metadata from the production environment. -- If a development version of a deferred model exists, dbt preferentially uses that database location when resolving the reference. +- If a development version of a deferred model exists, dbt preferentially uses the development database location when resolving the reference. - Passing the [`--favor-state`](/reference/node-selection/defer#favor-state) flag overrides the default behavior and _always_ resolve refs using production metadata, regardless of the presence of a development relation. For a clean slate, it's a good practice to drop the development schema at the start and end of your development cycle. From c5c4a69d028595eb0fd556475a493afaf5bd2de9 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:46:18 +0000 Subject: [PATCH 140/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: Leona B. Campbell <3880403+runleonarun@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 5d3b76c7430..0158ed8a7eb 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -7,7 +7,7 @@ pagination_next: "docs/cloud/cloud-cli-installation" --- -[Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). This is powered by using a production manifest for comparison, and dbt will resolve the `{{ ref() }}` function with upstream production artifacts. +[Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). dbt powers this by using a production manifest for comparison, and resolves the `{{ ref() }}` function with upstream production artifacts. Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. From 2cba7d1baac86cf4796158f748b57b1395646163 Mon Sep 17 00:00:00 2001 From: mirnawong1 <89008547+mirnawong1@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:46:25 +0000 Subject: [PATCH 141/152] Update website/docs/docs/cloud/about-cloud-develop-defer.md Co-authored-by: Leona B. Campbell <3880403+runleonarun@users.noreply.github.com> --- website/docs/docs/cloud/about-cloud-develop-defer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud-develop-defer.md b/website/docs/docs/cloud/about-cloud-develop-defer.md index 0158ed8a7eb..37bfaacfd0c 100644 --- a/website/docs/docs/cloud/about-cloud-develop-defer.md +++ b/website/docs/docs/cloud/about-cloud-develop-defer.md @@ -9,7 +9,7 @@ pagination_next: "docs/cloud/cloud-cli-installation" [Defer](/reference/node-selection/defer) is a powerful feature that allows developers to only build and run and test models they've edited without having to first run and build all the models that come before them (upstream parents). dbt powers this by using a production manifest for comparison, and resolves the `{{ ref() }}` function with upstream production artifacts. -Both the dbt Cloud IDE and the dbt Cloud CLI allow users to natively defer to production metadata directly in their development workflows. +Both the dbt Cloud IDE and the dbt Cloud CLI enable users to natively defer to production metadata directly in their development workflows. By default, dbt follows these rules: From accbc41a38ae612ffca5d20d1bf564f5dca8ff0e Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:52:13 -0800 Subject: [PATCH 142/152] Fixing set up ci forwarder (#4453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What are you changing in this pull request and why? Fixing link so old search goes to an actual page for https://docs.getdbt.com/guides/orchestration/set-up-ci/lint-on-push. It was forwarding to `https://docs.getdbt.com/%20/guides/set-up-ci` but now forwards correctly to `https://docs.getdbt.com/guides/set-up-ci` ❓ One question for reviewers: Is this forwarded link helpful? I wavered between directly linking to Step 4 for SQL Fluff and linking to the entire guide. I opted for the guide because it feels less fragile. --- website/vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/vercel.json b/website/vercel.json index 7c054b0947e..7b240c35553 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -434,7 +434,7 @@ }, { "source": "/guides/orchestration/set-up-ci/lint-on-push", - "destination": " /guides/set-up-ci", + "destination": "/guides/set-up-ci", "permanent": true }, { From 6983bdb43f7ced6a923c2e0dfa73391187cac3f0 Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:15:28 -0500 Subject: [PATCH 143/152] Correcting grain: MONTH syntax --- website/docs/docs/dbt-cloud-apis/sl-graphql.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/docs/dbt-cloud-apis/sl-graphql.md b/website/docs/docs/dbt-cloud-apis/sl-graphql.md index 0e39f50f60a..71ad175c11c 100644 --- a/website/docs/docs/dbt-cloud-apis/sl-graphql.md +++ b/website/docs/docs/dbt-cloud-apis/sl-graphql.md @@ -282,7 +282,7 @@ mutation { createQuery( environmentId: BigInt! metrics: [{name: "order_total"}] - groupBy: [{name: "metric_time", grain: "month"}] + groupBy: [{name: "metric_time", grain: MONTH}] ) { queryId } @@ -298,7 +298,7 @@ mutation { createQuery( environmentId: BigInt! metrics: [{name: "food_order_amount"}, {name: "order_gross_profit"}] - groupBy: [{name: "metric_time, grain: "month"}, {name: "customer__customer_type"}] + groupBy: [{name: "metric_time, grain: MONTH}, {name: "customer__customer_type"}] ) { queryId } @@ -320,7 +320,7 @@ mutation { createQuery( environmentId: BigInt! metrics:[{name: "order_total"}] - groupBy:[{name: "customer__customer_type"}, {name: "metric_time", grain: "month"}] + groupBy:[{name: "customer__customer_type"}, {name: "metric_time", grain: MONTH}] where:[{sql: "{{ Dimension('customer__customer_type') }} = 'new'"}, {sql:"{{ Dimension('metric_time').grain('month') }} > '2022-10-01'"}] ) { queryId @@ -335,8 +335,8 @@ mutation { createQuery( environmentId: BigInt! metrics: [{name: "order_total"}] - groupBy: [{name: "metric_time", grain: "month"}] - orderBy: [{metric: {name: "order_total"}}, {groupBy: {name: "metric_time", grain: "month"}, descending:true}] + groupBy: [{name: "metric_time", grain: MONTH}] + orderBy: [{metric: {name: "order_total"}}, {groupBy: {name: "metric_time", grain: MONTH}, descending:true}] ) { queryId } @@ -351,7 +351,7 @@ mutation { createQuery( environmentId: BigInt! metrics: [{name:"food_order_amount"}, {name: "order_gross_profit"}] - groupBy: [{name:"metric_time, grain: "month"}, {name: "customer__customer_type"}] + groupBy: [{name:"metric_time, grain: MONTH}, {name: "customer__customer_type"}] limit: 10 ) { queryId From 6f98fdf2396795f3bf99430890623eca3621e807 Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:30:01 -0500 Subject: [PATCH 144/152] Additional change --- website/docs/docs/dbt-cloud-apis/sl-graphql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/dbt-cloud-apis/sl-graphql.md b/website/docs/docs/dbt-cloud-apis/sl-graphql.md index 71ad175c11c..f73007c9a02 100644 --- a/website/docs/docs/dbt-cloud-apis/sl-graphql.md +++ b/website/docs/docs/dbt-cloud-apis/sl-graphql.md @@ -368,7 +368,7 @@ mutation { compileSql( environmentId: BigInt! metrics: [{name:"food_order_amount"} {name:"order_gross_profit"}] - groupBy: [{name:"metric_time, grain:"month"}, {name:"customer__customer_type"}] + groupBy: [{name:"metric_time, grain: MONTH}, {name:"customer__customer_type"}] ) { sql } From c1be1ec66e2713c50c79e78f3e572f06f4f33336 Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:45:47 -0500 Subject: [PATCH 145/152] Fixing generic examples on group page --- .../docs/reference/resource-configs/group.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/website/docs/reference/resource-configs/group.md b/website/docs/reference/resource-configs/group.md index bce2a72136e..df4d5ab8e3b 100644 --- a/website/docs/reference/resource-configs/group.md +++ b/website/docs/reference/resource-configs/group.md @@ -35,8 +35,8 @@ Support for grouping models was added in dbt Core v1.5 version: 2 models: - - name: model_name - group: finance + - name: MODEL_NAME + group: GROUP_NAME ``` @@ -46,7 +46,7 @@ models: ```yml models: [](resource-path): - +group: finance + +group: GROUP_NAME ``` @@ -57,7 +57,7 @@ models: ```sql {{ config( - group='finance' + group='GROUP_NAME' ) }} select ... @@ -85,7 +85,7 @@ Support for grouping seeds was added in dbt Core v1.5 ```yml models: [](resource-path): - +group: finance + +group: GROUP_NAME ``` @@ -94,8 +94,8 @@ models: ```yml seeds: - - name: [] - group: finance + - name: [SEED_NAME] + group: GROUP_NAME ``` @@ -120,7 +120,7 @@ Support for grouping snapshots was added in dbt Core v1.5 ```yml snapshots: [](resource-path): - +group: finance + +group: GROUP_NAME ``` @@ -131,7 +131,7 @@ snapshots: {% snapshot [snapshot_name](snapshot_name) %} {{ config( - group='finance' + group='GROUP_NAME' ) }} select ... @@ -161,7 +161,7 @@ Support for grouping tests was added in dbt Core v1.5 ```yml tests: [](resource-path): - +group: finance + +group: GROUP_NAME ``` @@ -176,7 +176,7 @@ version: 2 tests: - : config: - group: finance + group: GROUP_NAME ``` @@ -187,7 +187,7 @@ version: 2 {% test () %} {{ config( - group='finance' + group='GROUP_NAME' ) }} select ... @@ -202,7 +202,7 @@ select ... ```sql {{ config( - group='finance' + group='GROUP_NAME' ) }} ``` @@ -220,8 +220,8 @@ select ... version: 2 analyses: - - name: - group: finance + - name: ANALYSIS_NAME + group: GROUP_NAME ``` @@ -244,7 +244,7 @@ Support for grouping metrics was added in dbt Core v1.5 ```yaml metrics: [](resource-path): - [+](plus-prefix)group: finance + [+](plus-prefix)group: GROUP_NAME ``` @@ -255,8 +255,8 @@ metrics: version: 2 metrics: - - name: [] - group: finance + - name: [METRIC_NAME] + group: GROUP_NAME ``` @@ -281,8 +281,8 @@ Support for grouping semantic models has been added in dbt Core v1.7. ```yaml semantic_models: - - name: - group: + - name: SEMANTIC_MODEL_NAME + group: GROUP_NAME ``` @@ -293,7 +293,7 @@ semantic_models: ```yaml semantic-models: [](resource-path): - [+](plus-prefix)group: + [+](plus-prefix)group: GROUP_NAME ``` From 5d55661685b61b6e0f638f4dd1b6c6354ef043ef Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:29:48 -0500 Subject: [PATCH 146/152] Updating order of examples --- .../reference/resource-configs/enabled.md | 48 +++++++++---------- .../docs/reference/resource-configs/group.md | 36 +++++++------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/website/docs/reference/resource-configs/enabled.md b/website/docs/reference/resource-configs/enabled.md index d146f229494..552777c5c81 100644 --- a/website/docs/reference/resource-configs/enabled.md +++ b/website/docs/reference/resource-configs/enabled.md @@ -20,6 +20,17 @@ default_value: true }> + + +```yml +models: + [](/reference/resource-configs/resource-path): + +enabled: true | false + +``` + + + ```sql @@ -35,10 +46,15 @@ select ... + + + + + ```yml -models: +seeds: [](/reference/resource-configs/resource-path): +enabled: true | false @@ -48,13 +64,12 @@ models: - - + ```yml -seeds: +snapshots: [](/reference/resource-configs/resource-path): +enabled: true | false @@ -62,10 +77,6 @@ seeds: - - - - ```sql @@ -83,10 +94,14 @@ select ... + + + + ```yml -snapshots: +tests: [](/reference/resource-configs/resource-path): +enabled: true | false @@ -94,10 +109,6 @@ snapshots: - - - - ```sql @@ -125,17 +136,6 @@ select ... - - -```yml -tests: - [](/reference/resource-configs/resource-path): - +enabled: true | false - -``` - - - diff --git a/website/docs/reference/resource-configs/group.md b/website/docs/reference/resource-configs/group.md index bce2a72136e..a096feb0eef 100644 --- a/website/docs/reference/resource-configs/group.md +++ b/website/docs/reference/resource-configs/group.md @@ -29,27 +29,27 @@ Support for grouping models was added in dbt Core v1.5 - + ```yml -version: 2 - models: - - name: model_name - group: finance + [](resource-path): + +group: finance ``` + - + ```yml +version: 2 + models: - [](resource-path): - +group: finance + - name: model_name + group: finance ``` - @@ -277,23 +277,23 @@ Support for grouping semantic models has been added in dbt Core v1.7. - + ```yaml -semantic_models: - - name: - group: - +semantic-models: + [](resource-path): + [+](plus-prefix)group: GROUP_NAME ``` - + ```yaml -semantic-models: - [](resource-path): - [+](plus-prefix)group: +semantic_models: + - name: SEMANTIC_MODEL_NAME + group: GROUP_NAME + ``` From 7ff60bd12d588aee53cb29a146147311533084ba Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:26:59 -0500 Subject: [PATCH 147/152] Removing Future Release Dates --- website/snippets/core-versions-table.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/website/snippets/core-versions-table.md b/website/snippets/core-versions-table.md index 71e11974a56..f1241d8301b 100644 --- a/website/snippets/core-versions-table.md +++ b/website/snippets/core-versions-table.md @@ -12,11 +12,3 @@ | [**v1.0**](/docs/dbt-versions/core-upgrade/upgrading-to-v1.0) ⚠️ | Dec 3, 2021 | Deprecated ⛔️ | Deprecated ⛔️ | | **v0.X** ⛔️ | (Various dates) | Deprecated ⛔️ | Deprecated ⛔️ | _*All versions of dbt Core since v1.0 are available in dbt Cloud until further notice. Versions that are EOL do not receive any fixes. For the best support, we recommend upgrading to a version released within the past 12 months._ -### Planned future releases - -_Future release dates are tentative and subject to change._ - -| dbt Core | Planned Release | Critical & dbt Cloud Support Until | -|----------|-----------------|-------------------------------------| -| **v1.8** | _Jan 2024_ | _Jan 2025_ | -| **v1.9** | _Apr 2024_ | _Apr 2025_ | From af6e2d1ed53375a818063fc5f46b33208aba200b Mon Sep 17 00:00:00 2001 From: "Leona B. Campbell" <3880403+runleonarun@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:50:03 -0800 Subject: [PATCH 148/152] Adding mesh guide redirects (#4458) ## What are you changing in this pull request and why? I missed the mesh guides redirects when working on the quickstarts project. It was published after I ran the script for the URLs affected by the changes. --- website/vercel.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/website/vercel.json b/website/vercel.json index 7b240c35553..269ae29116d 100644 --- a/website/vercel.json +++ b/website/vercel.json @@ -22,6 +22,21 @@ "destination": "/guides/debug-errors", "permanent": true }, + { + "source": "/guides/best-practices/how-we-mesh/mesh-1-intro", + "destination": "/best-practices/how-we-mesh/mesh-1-intro", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-mesh/mesh-2-structures", + "destination": "/best-practices/how-we-mesh/mesh-2-structures", + "permanent": true + }, + { + "source": "/guides/best-practices/how-we-mesh/mesh-3-implementation", + "destination": "/best-practices/how-we-mesh/mesh-3-implementation", + "permanent": true + }, { "source": "/guides/best-practices/how-we-build-our-metrics/semantic-layer-1-intro", "destination": "/best-practices/how-we-build-our-metrics/semantic-layer-1-intro", @@ -434,7 +449,7 @@ }, { "source": "/guides/orchestration/set-up-ci/lint-on-push", - "destination": "/guides/set-up-ci", + "destination": " /guides/set-up-ci", "permanent": true }, { From 4c6ff0efae43111464b1ab4991f7e194f501ff58 Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:08:33 -0500 Subject: [PATCH 149/152] Updating run results Schema to v5 --- website/docs/reference/artifacts/run-results-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/reference/artifacts/run-results-json.md b/website/docs/reference/artifacts/run-results-json.md index dd92a9c4e53..5b3549db55b 100644 --- a/website/docs/reference/artifacts/run-results-json.md +++ b/website/docs/reference/artifacts/run-results-json.md @@ -3,7 +3,7 @@ title: "Run results JSON file" sidebar_label: "Run results" --- -**Current schema**: [`v4`](https://schemas.getdbt.com/dbt/run-results/v4/index.html) +**Current schema**: [`v5`](https://schemas.getdbt.com/dbt/run-results/v5/index.html) **Produced by:** [`build`](/reference/commands/build) From 5d70a1ab3b5258889c07497c3eabfc7710ca8ee6 Mon Sep 17 00:00:00 2001 From: Matt Shaver <60105315+matthewshaver@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:11:52 -0500 Subject: [PATCH 150/152] Adding enteprise availability to multicell --- website/docs/docs/cloud/about-cloud/regions-ip-addresses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/docs/cloud/about-cloud/regions-ip-addresses.md b/website/docs/docs/cloud/about-cloud/regions-ip-addresses.md index 64743f85afa..cc1c2531f56 100644 --- a/website/docs/docs/cloud/about-cloud/regions-ip-addresses.md +++ b/website/docs/docs/cloud/about-cloud/regions-ip-addresses.md @@ -12,7 +12,7 @@ dbt Cloud is [hosted](/docs/cloud/about-cloud/architecture) in multiple regions | Region | Location | Access URL | IP addresses | Developer plan | Team plan | Enterprise plan | |--------|----------|------------|--------------|----------------|-----------|-----------------| | North America multi-tenant [^1] | AWS us-east-1 (N. Virginia) | cloud.getdbt.com | 52.45.144.63
    54.81.134.249
    52.22.161.231 | ✅ | ✅ | ✅ | -| North America Cell 1 [^1] | AWS us-east-1 (N.Virginia) | {account prefix}.us1.dbt.com | [Located in Account Settings](#locating-your-dbt-cloud-ip-addresses) | ❌ | ❌ | ❌ | +| North America Cell 1 [^1] | AWS us-east-1 (N.Virginia) | {account prefix}.us1.dbt.com | [Located in Account Settings](#locating-your-dbt-cloud-ip-addresses) | ❌ | ❌ | ✅ | | EMEA [^1] | AWS eu-central-1 (Frankfurt) | emea.dbt.com | 3.123.45.39
    3.126.140.248
    3.72.153.148 | ❌ | ❌ | ✅ | | APAC [^1] | AWS ap-southeast-2 (Sydney)| au.dbt.com | 52.65.89.235
    3.106.40.33
    13.239.155.206
    | ❌ | ❌ | ✅ | | Virtual Private dbt or Single tenant | Customized | Customized | Ask [Support](/community/resources/getting-help#dbt-cloud-support) for your IPs | ❌ | ❌ | ✅ | From 62f130ac0c79fbef5f033ab5dbfe3e062cacb9d0 Mon Sep 17 00:00:00 2001 From: Joel Labes Date: Tue, 14 Nov 2023 13:43:53 +1300 Subject: [PATCH 151/152] Create devblog for environment differentiation (#4407) ## What are you changing in this pull request and why? Adds a new developer blog article explaining the importance of splitting out your production and CI environments if you haven't already, to take advantage of dbt Explorer and the improved Slim CI experience. --- .../2023-11-14-specify-prod-environment.md | 73 ++ website/blog/categories.yml | 2 + .../data-landscape.excalidraw | 1008 +++++++++++++++++ .../data-landscape.png | Bin 0 -> 889660 bytes 4 files changed, 1083 insertions(+) create mode 100644 website/blog/2023-11-14-specify-prod-environment.md create mode 100644 website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.excalidraw create mode 100644 website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.png diff --git a/website/blog/2023-11-14-specify-prod-environment.md b/website/blog/2023-11-14-specify-prod-environment.md new file mode 100644 index 00000000000..c6ad2b31027 --- /dev/null +++ b/website/blog/2023-11-14-specify-prod-environment.md @@ -0,0 +1,73 @@ +--- + +title: Why you should specify a production environment in dbt Cloud +description: "The bottom line: You should split your Environments in dbt Cloud based on their purposes (e.g. Production and Staging/CI) and mark one environment as Production. This will improve your CI experience and enable you to use dbt Explorer." +slug: specify-prod-environment + +authors: [joel_labes] + +tags: [dbt Cloud] +hide_table_of_contents: false + +date: 2023-11-14 +is_featured: false + +--- + +:::tip The Bottom Line: +You should [split your Jobs](#how) across Environments in dbt Cloud based on their purposes (e.g. Production and Staging/CI) and set one environment as Production. This will improve your CI experience and enable you to use dbt Explorer. +::: + +[Environmental segmentation](/docs/environments-in-dbt) has always been an important part of the analytics engineering workflow: + +- When developing new models you can [process a smaller subset of your data](/reference/dbt-jinja-functions/target#use-targetname-to-limit-data-in-dev) by using `target.name` or an environment variable. +- By building your production-grade models into [a different schema and database](https://docs.getdbt.com/docs/build/custom-schemas#managing-environments), you can experiment in peace without being worried that your changes will accidentally impact downstream users. +- Using dedicated credentials for production runs, instead of an analytics engineer's individual dev credentials, ensures that things don't break when that long-tenured employee finally hangs up their IDE. + +Historically, dbt Cloud required a separate environment for _Development_, but was otherwise unopinionated in how you configured your account. This mostly just worked – as long as you didn't have anything more complex than a CI job mixed in with a couple of production jobs – because important constructs like deferral in CI and documentation were only ever tied to a single job. + +But as companies' dbt deployments have grown more complex, it doesn't make sense to assume that a single job is enough anymore. We need to exchange a job-oriented strategy for a more mature and scalable environment-centric view of the world. To support this, a recent change in dbt Cloud enables project administrators to [mark one of their environments as the Production environment](/docs/deploy/deploy-environments#set-as-production-environment-beta), just as has long been possible for the Development environment. + +Explicitly separating your Production workloads lets dbt Cloud be smarter with the metadata it creates, and is particularly important for two new features: dbt Explorer and the revised CI workflows. + + + +## Make sure dbt Explorer always has the freshest information available + +**The old way**: Your dbt docs site was based on a single job's run. + +**The new way**: dbt Explorer uses metadata from across every invocation in a defined Production environment to build the richest and most up-to-date understanding of your project. + +Because dbt docs could only be updated by a single predetermined job, users who needed their documentation to immediately reflect changes deployed throughout the day (regardless of which job executed them) would find themselves forced to run a dedicated job which did nothing other than run `dbt docs generate` on a regular schedule. + +The Discovery API that powers dbt Explorer ingests all metadata generated by any dbt invocation, which means that it can always be up to date with the applied state of your project. However it doesn't make sense for dbt Explorer to show docs based on a PR that hasn't been merged yet. + +To avoid this conflation, you need to mark an environment as the Production environment. All runs completed in _that_ environment will contribute to dbt Explorer's, while others will be excluded. (Future versions of Explorer will support environment selection, so that you can preview your documentation changes as well.) + +## Run Slimmer CI than ever with environment-level deferral + +**The old way**: [Slim CI](/guides/set-up-ci?step=2) deferred to a single job, and would only detect changes as of that job's last build time. + +**The new way**: Changes are detected regardless of the job they were deployed in, removing false positives and overbuilding of models in CI. + +Just like dbt docs, relying on a single job to define your state for comparison purposes leads to a choice between unnecessarily rebuilding models which were deployed by another job, or creating a dedicated job that runs `dbt compile` on repeat to keep on top of all changes. + +By using the environment as the arbiter of state, any time a change is made to your Production deployment it will immediately be taken into consideration by subsequent Slim CI runs. + +## The easiest way to break apart your jobs {#how} + + + +For most projects, changing from a job-centric to environment-centric approach to metadata is straightforward and immediately pays dividends as described above. Assuming that your Staging/CI and Production jobs are currently intermingled, you can extricate them as follows: + +1. Create a new dbt Cloud environment called Staging +2. For each job that belongs to the Staging environment, edit the job and update its environment +3. Tick the ["Mark as Production environment" box](/docs/deploy/deploy-environments#set-as-production-environment-beta) in your original environment's settings + +## Conclusion + +Until very recently, I only thought of Environments in dbt Cloud as a way to use different authentication credentials in different contexts. And until very recently, I was mostly right. + +Not anymore. The metadata dbt creates is critical for effective data teams – whether you're concerned about cost savings, discoverability, increased development speed or reliable results across your organization – but is only fully effective if it's segmented by the environment that created it. + +Take a few minutes to clean up your environments - it'll make all the difference. diff --git a/website/blog/categories.yml b/website/blog/categories.yml index 8103f58cc33..45acf246dff 100644 --- a/website/blog/categories.yml +++ b/website/blog/categories.yml @@ -19,3 +19,5 @@ display_title: SQL magic description: Stories of dbt developers making SQL sing across warehouses. is_featured: true +- name: dbt Cloud + description: Using dbt Cloud to build for scale \ No newline at end of file diff --git a/website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.excalidraw b/website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.excalidraw new file mode 100644 index 00000000000..be8a0923345 --- /dev/null +++ b/website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.excalidraw @@ -0,0 +1,1008 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 129, + "versionNonce": 1482751856, + "isDeleted": false, + "id": "Udy5rK-Jz8k0Fq4imQCvd", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 341.8627251519098, + "y": 39.92541164822043, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 2156.810506184896, + "height": 1036.2360975477432, + "seed": 1146131484, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163923, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 93, + "versionNonce": 432336784, + "isDeleted": false, + "id": "yFoHv_ZLVYq5DdwJxTXrQ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 393.7163289388021, + "y": 248.74700927734375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 142.6879425048828, + "height": 70, + "seed": 1947174180, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163923, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Data \nWarehouse", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data \nWarehouse", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 147, + "versionNonce": 82809200, + "isDeleted": false, + "id": "18XKDb5pYJYJQPYEshmg4", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 411.73432668050134, + "y": 485.4708455403644, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 106.65194702148438, + "height": 35, + "seed": 574631460, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163923, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "git repo", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "git repo", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 72, + "versionNonce": 815824272, + "isDeleted": false, + "id": "5eUI6A7oSEvd69NrXYNoc", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 398.70032246907556, + "y": 803.185655381944, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 132.71995544433594, + "height": 35, + "seed": 1782580380, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163923, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "dbt Cloud", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "dbt Cloud", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 138, + "versionNonce": 98296688, + "isDeleted": false, + "id": "L_y_veh-MeHyKFX1eH_jc", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 625.8938259548611, + "y": 154.95136854383676, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 506.8489583333335, + "height": 875.4567464192709, + "seed": 89476252, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163923, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 353, + "versionNonce": 1088048016, + "isDeleted": false, + "id": "cXSP5cBcW7nCrji0UWwUB", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1247.2498236762153, + "y": 154.95136854383676, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 506.8489583333335, + "height": 875.4567464192709, + "seed": 303420452, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 211, + "versionNonce": 158201200, + "isDeleted": false, + "id": "Zx2HStRZKGZ9rinR0kE5E", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1868.892550998264, + "y": 155.0846964518229, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 506.8489583333335, + "height": 875.4567464192709, + "seed": 650869924, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 91, + "versionNonce": 78272912, + "isDeleted": false, + "id": "epO5kSyuGRV5wTUD-s9zZ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 853.561743842231, + "y": 97.15853542751734, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 51.79998779296875, + "height": 35, + "seed": 2015553052, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Dev", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Dev", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 209, + "versionNonce": 887998320, + "isDeleted": false, + "id": "fT31nBE0B8HbloZlX-nDt", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1484.1716715494792, + "y": 97.15853542751734, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 33.2919921875, + "height": 35, + "seed": 549760924, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "CI", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "CI", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 184, + "versionNonce": 793290640, + "isDeleted": false, + "id": "eTKqHyL2wEJwRjyzONlcj", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2091.321043226454, + "y": 97.15853542751734, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 61.991973876953125, + "height": 35, + "seed": 1462334492, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Prod", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Prod", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 96, + "versionNonce": 1151555952, + "isDeleted": false, + "id": "VhROTjE1abDd9ZOeAMg-U", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 689.6759711371528, + "y": 201.00466579861114, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 376.7974175347222, + "height": 165.48468695746527, + "seed": 2047365540, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 141, + "versionNonce": 1290614160, + "isDeleted": false, + "id": "k5MJxECeWuQJb8SwV3oVW", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 721.2427775065105, + "y": 248.74700927734375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 313.3758544921875, + "height": 70, + "seed": 1555453724, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Dev DB\n(user-specific schemas)", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Dev DB\n(user-specific schemas)", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 184, + "versionNonce": 36425616, + "isDeleted": false, + "id": "BO6Wxrm4rZ1QDt8vBFRFl", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1293.0316162109375, + "y": 201.0046657986111, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 376.7974175347222, + "height": 165.48468695746527, + "seed": 334336668, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413166096, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 284, + "versionNonce": 621357968, + "isDeleted": false, + "id": "Xavgjps0qvZVKUHlsPVna", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1317.1822781032986, + "y": 248.74700927734375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 330.65185546875, + "height": 70, + "seed": 1180229404, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413169453, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "QA DB\n(temporary PR schemas)", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "QA DB\n(temporary PR schemas)", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "rectangle", + "version": 284, + "versionNonce": 886029680, + "isDeleted": false, + "id": "oDdg4x6QABrYXglaDBIHt", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1926.055840386285, + "y": 201.0046657986111, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 376.7974175347222, + "height": 165.48468695746527, + "seed": 576422428, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 376, + "versionNonce": 254349712, + "isDeleted": false, + "id": "iTxeg4gQp5KRDrzokIgmt", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1973.012624104818, + "y": 248.74700927734375, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 282.88385009765625, + "height": 70, + "seed": 1079828124, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Prod DB\n(production schemas)", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Prod DB\n(production schemas)", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 139, + "versionNonce": 550932336, + "isDeleted": false, + "id": "HQeJiZeuOVrvP9ru2C-78", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 689.5203925238716, + "y": 467.98152669270837, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 379.5958251953125, + "height": 70, + "seed": 508113948, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "branches:\nfeature branches from main", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "branches:\nfeature branches from main", + "lineHeight": 1.25, + "baseline": 60 + }, + { + "type": "text", + "version": 125, + "versionNonce": 1764777872, + "isDeleted": false, + "id": "M7ejyx8utxANmCPivcWvL", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1370.0543535020618, + "y": 485.48152669270837, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 261.2398986816406, + "height": 35, + "seed": 558908580, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "PRs targeting main", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "PRs targeting main", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 27, + "versionNonce": 1211310448, + "isDeleted": false, + "id": "QmOKeCjEaTMIfbw46sNFl", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2041.1730711195205, + "y": 485.48152669270837, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 162.2879180908203, + "height": 35, + "seed": 672916900, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "branch: main", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "branch: main", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "rectangle", + "version": 355, + "versionNonce": 1629351312, + "isDeleted": false, + "id": "L8G-_VdBlPmtNgQKn0G2x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 649.1752794053821, + "y": 426.521769205729, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1692.284138997396, + "height": 153.18637424045136, + "seed": 1687815964, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 110, + "versionNonce": 976057200, + "isDeleted": false, + "id": "rUcnW6bo9uKCXl1tVfZXO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 668.4320305718317, + "y": 397.55624728732636, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 89.83990478515625, + "height": 25, + "seed": 1724197028, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "dbt_repo", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "dbt_repo", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 552, + "versionNonce": 1613347728, + "isDeleted": false, + "id": "1L7lF8fe7tHoTO95AB_6Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 649.1752794053821, + "y": 666.7112833658852, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1704.8221164279514, + "height": 306.15851508246516, + "seed": 147107236, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 290, + "versionNonce": 1561167216, + "isDeleted": false, + "id": "br8j-OyjfCajGYHkvT2_0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 668.4320305718317, + "y": 635.5079413519964, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 176.5398406982422, + "height": 25, + "seed": 1051154724, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "dbt Cloud Project", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "dbt Cloud Project", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 84, + "versionNonce": 1753991568, + "isDeleted": false, + "id": "HaZZKdXMOfsniJsylOZZA", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 698.8494194878472, + "y": 706.6618109809027, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 365.6057400173611, + "height": 232.40898980034729, + "seed": 1118957596, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 144, + "versionNonce": 236672880, + "isDeleted": false, + "id": "GVw8HsZ3jf_eN3qat8EnV", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 752.6179436577692, + "y": 726.0786539713541, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 256.78790283203125, + "height": 175, + "seed": 36319388, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Env Name:\nDevelopment (IDE)\n\nJobs:\nnone", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Env Name:\nDevelopment (IDE)\n\nJobs:\nnone", + "lineHeight": 1.25, + "baseline": 165 + }, + { + "type": "rectangle", + "version": 306, + "versionNonce": 1971066768, + "isDeleted": false, + "id": "25b7C18GbdjocbHu7mNFv", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1272.3475477430557, + "y": 697.7114122178818, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 456.6535101996529, + "height": 232.40898980034729, + "seed": 503421476, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 321, + "versionNonce": 1100933488, + "isDeleted": false, + "id": "YGODDCDk9jIVk8fCbxC-f", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1290.6323869493274, + "y": 726.2222222222222, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 420.0838317871094, + "height": 175, + "seed": 1720890788, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Env Name:\nCI\n\nJobs:\nSlim CI: feature branch > main", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Env Name:\nCI\n\nJobs:\nSlim CI: feature branch > main", + "lineHeight": 1.25, + "baseline": 165 + }, + { + "type": "rectangle", + "version": 246, + "versionNonce": 1530045840, + "isDeleted": false, + "id": "--xRdulSAOVkraMLOgPcU", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1904.9254014756943, + "y": 697.5177951388889, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 434.7832573784722, + "height": 232.40898980034729, + "seed": 807301284, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 287, + "versionNonce": 585120624, + "isDeleted": false, + "id": "7C0I5zrwwMb_rw70yiGVh", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2022.917074415419, + "y": 726.3555501302083, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 198.79991149902344, + "height": 175, + "seed": 2077277220, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "Env Name:\nProducation\n\nJobs:\nScheduled jobs", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Env Name:\nProducation\n\nJobs:\nScheduled jobs", + "lineHeight": 1.25, + "baseline": 165 + }, + { + "type": "text", + "version": 43, + "versionNonce": 652702608, + "isDeleted": false, + "id": "0vfDvWeVlJv5WnFpmnAXX", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1139.5400729709202, + "y": -17.269870334201613, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 561.455810546875, + "height": 45, + "seed": 2048509604, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1699413163924, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Organization's Data Landscape", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Organization's Data Landscape", + "lineHeight": 1.25, + "baseline": 32 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.png b/website/static/img/blog/2023-11-06-differentiate-prod-and-staging-environments/data-landscape.png new file mode 100644 index 0000000000000000000000000000000000000000..71f94531fcf972946210d6ec78428a290375118c GIT binary patch literal 889660 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 nZAtLB1+*}MV=KeUJhK7S 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%0D+Hp%0ekH7b!Z6Mg6 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 literal 0 HcmV?d00001 From dc660efbb0255e75903238152207ba97269cf250 Mon Sep 17 00:00:00 2001 From: Marta Paes Date: Tue, 14 Nov 2023 02:29:20 +0100 Subject: [PATCH 152/152] Update source-freshness.md Minor patches as I read through the page. --- website/docs/docs/deploy/source-freshness.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/docs/deploy/source-freshness.md b/website/docs/docs/deploy/source-freshness.md index 78500416c56..2f9fe6bc007 100644 --- a/website/docs/docs/deploy/source-freshness.md +++ b/website/docs/docs/deploy/source-freshness.md @@ -13,7 +13,7 @@ dbt Cloud provides a helpful interface around dbt's [source data freshness](/doc [`dbt build`](reference/commands/build) does _not_ include source freshness checks when building and testing resources in your DAG. Instead, you can use one of these common patterns for defining jobs: - Add `dbt build` to the run step to run models, tests, and so on. - Select the **Generate docs on run** checkbox to automatically [generate project docs](/docs/collaborate/build-and-view-your-docs#set-up-a-documentation-job). -- Select the **Run on source freshness** checkbox to enable [source freshness](#checkbox) as the first to step of the job. +- Select the **Run source freshness** checkbox to enable [source freshness](#checkbox) as the first step of the job. @@ -24,7 +24,7 @@ Review the following options and outcomes: | Options | Outcomes | |--------| ------- | | **Select checkbox ** | The **Run source freshness** checkbox in your **Execution Settings** will run `dbt source freshness` as the first step in your job and won't break subsequent steps if it fails. If you wanted your job dedicated *exclusively* to running freshness checks, you still need to include at least one placeholder step, such as `dbt compile`. | -| **Add as a run step** | Add the `dbt source freshness` command to a job anywhere in your list of run steps. However, if your source data is out of date — this step will "fail', and subsequent steps will not run. dbt Cloud will trigger email notifications (if configured) based on the end state of this step.

    You can create a new job to snapshot source freshness.

    If you *do not* want your models to run if your source data is out of date, then it could be a good idea to run `dbt source freshness` as the first step in your job. Otherwise, we recommend adding `dbt source freshness` as the last step in the job, or creating a separate job just for this task. | +| **Add as a run step** | Add the `dbt source freshness` command to a job anywhere in your list of run steps. However, if your source data is out of date — this step will "fail", and subsequent steps will not run. dbt Cloud will trigger email notifications (if configured) based on the end state of this step.

    You can create a new job to snapshot source freshness.

    If you *do not* want your models to run if your source data is out of date, then it could be a good idea to run `dbt source freshness` as the first step in your job. Otherwise, we recommend adding `dbt source freshness` as the last step in the job, or creating a separate job just for this task. |