Skip to content

Commit

Permalink
Merge branch 'current' into patch-3
Browse files Browse the repository at this point in the history
  • Loading branch information
mirnawong1 authored Sep 12, 2023
2 parents da0d797 + 85e801a commit cbd024d
Show file tree
Hide file tree
Showing 45 changed files with 4,538 additions and 890 deletions.
2 changes: 1 addition & 1 deletion contributing/single-sourcing-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ This component can be added directly to a markdown file in a similar way as othe
Both properties can be used together to set a range where the content should show. In the example below, this content will only show if the selected version is between **0.21** and **1.0**:

```markdown
<VersionBlock lastVersion="1.0">
<VersionBlock lastVersion="1.6">

Versioned content here

Expand Down
169 changes: 169 additions & 0 deletions website/api/get-discourse-comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const axios = require('axios')
require("dotenv").config();

const { DISCOURSE_DEVBLOG_API_KEY , DISCOURSE_USER_SYSTEM } = process.env
const DEVBLOG_PROD_URL = 'https://docs.getdbt.com/blog/'
const DEV_ENV = 'dev-'
const PREVIEW_ENV = 'deploy-preview-'

// Set API endpoint and headers
let discourse_endpoint = `https://discourse.getdbt.com`
let headers = {
'Accept': 'application/json',
'Api-Key': DISCOURSE_DEVBLOG_API_KEY,
'Api-Username': DISCOURSE_USER_SYSTEM,
}

async function getDiscourseComments(request, response) {
let topicId, comments, DISCOURSE_TOPIC_ID;

const blogUrl = await getBlogUrl(request)

if (blogUrl === DEVBLOG_PROD_URL) {
DISCOURSE_TOPIC_ID = 21
} else {
DISCOURSE_TOPIC_ID = 2
}

try {
const env =
blogUrl === DEVBLOG_PROD_URL
? ""
: blogUrl.includes("localhost")
? DEV_ENV
: PREVIEW_ENV;
const postTitle = `${env}${request.query.title}`;
const postSlug = request.query.slug;
const cleanSlug = cleanUrl(request.query.slug);
const externalId = truncateString(`${env}${cleanSlug}`);

console.table({
blogUrl,
postTitle,
postSlug,
cleanSlug,
externalId,
});


if (!postSlug) throw new Error("Unable to query Discourse API. Error reading slug.");

topicId = await searchDiscourseExternalId(externalId);

// First check if the dev blog post exists in Discourse
// Get the comments if it does
if (typeof topicId === "number") {
comments = await getDiscourseTopicbyID(topicId);
} else {
// If the dev blog post does not exist in Discourse
// Create a new topic and get the comments
topicId = await createDiscourseTopic(postTitle, externalId, cleanSlug, blogUrl, DISCOURSE_TOPIC_ID);
if (typeof topicId === "number") {
comments = await getDiscourseTopicbyID(topicId);
comments.shift();
comments = { topicId, comments };

return await response.status(200).json(comments);
} else {
console.log("Unable to create Discourse topic TopicID is not a number.");
return await response.status(500).json({ error: "Unable to create Discourse topic TopicID is not a number." });
}
}

comments.shift();
comments = { topicId, comments };

return await response.status(200).json(comments);
} catch (err) {
console.log("err on getDiscourseComments", err);
return await response.status(500).json({ error: "Unable to get topics from Discourse." });
}
}

async function createDiscourseTopic(title, externalId, slug, blogUrl, DISCOURSE_TOPIC_ID) {
console.log(`Creating a new topic in Discourse - ${title}`)
try {
const response = await axios.post(`${discourse_endpoint}/posts`, {
title: title,
raw: `This is a companion discussion topic for the original entry at ${blogUrl}${slug}`,
category: DISCOURSE_TOPIC_ID,
embed_url: `${blogUrl}${slug}`,
external_id: externalId,
tags: ['devblog'],
visible: false
}, { headers })

let topicId = await response.data.topic_id

console.log('Topic successfully created with topic_id', topicId)

return topicId

} catch(err) {
console.log('err on createDiscourseTopic', err)
return err
}
}

async function getDiscourseTopicbyID(topicId) {
console.log(`Topic found setting topic id - ${topicId}`)
try {
let response = await axios.get(`${discourse_endpoint}/t/${topicId}.json`, { headers })
let { data } = await response
let post_stream = data.post_stream
let post_count = data.posts_count

// If there is more than one comment make the topic visibile in Discourse
if (post_count > 1 && data.visible === false) {
console.log(`Topic has more than one comment. Changing visibility to visible.`)
await axios.put(`${discourse_endpoint}/t/${topicId}`, {
visible: true
}, { headers })
}

// Filter only 'regular' posts in Discourse. (e.g. not moderator actions, small_actions, whispers)
post_stream.posts = post_stream.posts.filter(post => post.post_type === 1)

return post_stream.posts
} catch(err) {
console.log('err on getDiscourseTopicbyID', err)
return err
}
}

async function searchDiscourseExternalId(externalId) {
console.log(`Searching for external_id in Discourse - ${externalId}`);
try {
const data = await axios.get(`${discourse_endpoint}/t/external_id/${externalId}.json`, { headers });
return data.data.id;
} catch (err) {
if (err.response.status === 404) {
console.log("No topics found in Discourse.");
return null;
}
console.log("Unable to search Discourse for external_id.", err);
return err;
}
}


// Truncate external_id to 50 characters per Discourse API requirements
function truncateString(str) {
if (str.length <= 50) {
return str
}
return str.slice(0, 50)
}

// Remove query params and hash from URL to prevent duplicate topics
function cleanUrl(url) {
return url.split("?")[0].split("#")[0];
}

// Create a function to get the host name from the request and add /blog/ to the end
async function getBlogUrl(req) {
const host = req.headers.host
return `https://${host}/blog/`
}

module.exports = getDiscourseComments;
136 changes: 136 additions & 0 deletions website/api/get-discourse-topics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const axios = require('axios')

async function getDiscourseTopics(request, response) {
const { DISCOURSE_API_KEY , DISCOURSE_USER } = process.env

const body = request.body

try {
// Set API endpoint and headers
let discourse_endpoint = `https://discourse.getdbt.com`
let headers = {
'Accept': 'application/json',
'Api-Key': DISCOURSE_API_KEY,
'Api-Username': DISCOURSE_USER,
}

const query = buildQueryString(body)
if(!query) throw new Error('Unable to build query string.')

// Get topics from Discourse
let { data: { posts, topics } } = await axios.get(`${discourse_endpoint}/search?q=${query}`, { headers })

// Return empty array if no topics found for search query
// 200 status is used to prevent triggering Datadog alerts
if(!topics || topics?.length <= 0) {
// Log message with encoded query and end function
console.log('Unable to get results from api request.')
console.log(`Search query: ${query}`)
return await response.status(200).json([])
}

// Set author and like_count for topics if not querying by specific term
let allTopics = topics
if(!body?.term) {
allTopics = topics.reduce((topicsArr, topic) => {
// Get first post in topic
const firstTopicPost = posts?.find(post =>
post?.post_number === 1 &&
post?.topic_id === topic?.id
)
// If post found
// Get username
if(firstTopicPost?.username) {
topic.author = firstTopicPost.username
}
// Get like count
if(firstTopicPost?.like_count) {
topic.like_count = firstTopicPost.like_count
}

if(firstTopicPost?.blurb) {
topic.blurb = firstTopicPost.blurb
}

// Push updated topic to array
topicsArr.push(topic)

return topicsArr
}, [])
}

// Return topics
//return await returnResponse(200, allTopics)
return await response.status(200).json(allTopics)
} catch(err) {
// Log and return the error
console.log('err', err)
return await response.status(500).json({ error: 'Unable to get topics from Discourse.'})
}
}

function buildQueryString(body) {
if(!body) return null

// start with empty query string
let query = ''

// check param and apply to query if set
for (const [key, value] of Object.entries(body)) {
// validate categories
// if valid, add to query string
if(validateItem({ key, value })) {
if(key === 'category') {
query += `#${value} `
} else if(key === 'inString') {
query += `in:${value}`
} else if(key === 'status' && Array.isArray(value)) {
value?.map(item => {
query += `${key}:${item} `
})
} else {
query += `${key}:${value} `
}
}
}

if(query) {
const encodedQuery = encodeURIComponent(query)
return encodedQuery
}
}

function validateItem({ key, value }) {
// predefined Discourse values
// https://docs.discourse.org/#tag/Search/operation/search
const inStringValues = ['title', 'first', 'pinned', 'wiki']
const orderValues = ['latest', 'likes', 'views', 'latest_topic']
const statusValues = ['open', 'closed', 'public', 'archived', 'noreplies', 'single_user', 'solved', 'unsolved']

// validate keys
if(key === 'inString') {
return inStringValues.includes(value)
? true
: false
} else if(key === 'order') {
return orderValues.includes(value)
? true
: false
} else if(key === 'status') {
if(Array.isArray(value)) {
let isValid = true
value?.map(item => {
if(!statusValues.includes(item)) isValid = false
})
return isValid
} else {
return statusValues.includes(value)
? true
: false
}
} else {
return true
}
}

module.exports = getDiscourseTopics
4 changes: 0 additions & 4 deletions website/dbt-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ exports.versions = [
version: "1.1",
EOLDate: "2023-04-28",
},
{
version: "1.0",
EOLDate: "2022-12-03"
},
]

exports.versionedPages = [
Expand Down
Loading

0 comments on commit cbd024d

Please sign in to comment.