Skip to content

Spike - Add IDs to docs to trace a doc across versions #490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions app/api/v2/content-versions/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import { expect, test, vi, beforeEach, afterEach } from 'vitest'
import { GET } from './route'

import { vol } from 'memfs'

// Mock fs module
vi.mock('node:fs')
vi.mock('node:fs/promises')

// Mock PRODUCT_CONFIG
vi.mock('@utils/productConfig.mjs', () => {
return {
PRODUCT_CONFIG: {
'terraform-enterprise': {
assetDir: 'img',
basePaths: ['enterprise'],
contentDir: 'docs',
dataDir: 'data',
productSlug: 'terraform',
versionedDocs: true,
websiteDir: 'website',
},
},
}
})

vi.mock('../../docsPathsAllVersions.json', () => {
return {
default: {
'terraform-enterprise': {
'v202505-1': [
{
path: 'terraform/enterprise/api-docs/account',
itemPath:
'content/terraform-enterprise/v202505-1/docs/enterprise/api-docs/account.mdx',
id: '92a97263-e804-474f-9eb6-86fd0d53f33c',
created_at: '2025-05-29T11:04:37-04:00',
},
],
'v202505-2': [
{
path: 'terraform/enterprise/api-docs/account-2',
itemPath:
'content/terraform-enterprise/v202505-1/docs/enterprise/api-docs/account.mdx',
id: '92a97263-e804-474f-9eb6-86fd0d53f33c',
created_at: '2025-05-29T11:04:37-04:00',
},
],
'v202505-3': [
{
path: 'terraform/enterprise/api-docs/account',
itemPath:
'content/terraform-enterprise/v202505-1/docs/enterprise/api-docs/account.mdx',
id: '92a97263-e804-474f-9eb6-86fd0d53f33d',
created_at: '2025-05-29T11:04:37-04:00',
},
],
},
},
}
})

beforeEach(() => {
// Reset the state of in-memory fs
vol.reset()
})

afterEach(() => {
// Reset all mocks after each test
vi.resetAllMocks()
})

test('should return 400 if `product` query parameter is missing', async () => {
const mockRequest = (url: string) => {
return new Request(url)
}
const request = mockRequest(
'http://localhost:8080/api/content-versions?id=test',
)
const response = await GET(request)
expect(response.status).toBe(400)
const text = await response.text()
expect(text).toBe(
'Missing `product` query parameter. Please provide the `product` under which the requested document is expected to be found, for example `vault`.',
)
})

test('should return 400 if `id` query parameter is missing', async () => {
const mockRequest = (url: string) => {
return new Request(url)
}
const request = mockRequest(
'http://localhost:8080/api/content-versions?product=terraform-enterprise',
)
const response = await GET(request)
expect(response.status).toBe(400)
const text = await response.text()
expect(text).toBe('Missing `id` query parameter.')
})

test('should return 404 if the product is invalid', async () => {
vol.fromJSON({})

const mockRequest = (url: string) => {
return new Request(url)
}
const request = mockRequest(
'http://localhost:8080/api/content-versions?product=nonexistent&id=test',
)
const response = await GET(request)
expect(response.status).toBe(404)
const text = await response.text()
expect(text).toBe('Not found')
})

test('should return 200 and array of strings on valid params', async () => {
const mockedResponse = {
versions: [
{
version: 'v202505-1',
path: 'terraform/enterprise/api-docs/account',
},
{
version: 'v202505-2',
path: 'terraform/enterprise/api-docs/account-2',
},
],
}

const mockRequest = (url: string) => {
return new Request(url)
}
const request = mockRequest(
`http://localhost:8080/api/content-versions?product=terraform-enterprise&id=92a97263-e804-474f-9eb6-86fd0d53f33c`,
)
const response = await GET(request)
expect(response.status).toBe(200)
const json = await response.json()
expect(json).toEqual(mockedResponse)
})
57 changes: 57 additions & 0 deletions app/api/v2/content-versions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { PRODUCT_CONFIG } from '@utils/productConfig.mjs'
import docsPathsAllVersions from '../../docsPathsAllVersions.json'

export async function GET(request: Request) {
const url = new URL(request.url)
let product = url.searchParams.get('product')
const id = url.searchParams.get('id')

if (product === 'ptfe-releases') {
product = 'terraform-enterprise'
}

// If a `product` parameter has not been provided, return a 400
if (!product) {
return new Response(
'Missing `product` query parameter. Please provide the `product` under which the requested document is expected to be found, for example `vault`.',
{ status: 400 },
)
}
// If a `fullPath` parameter has not been provided, return a 400
if (!id) {
return new Response(
'Missing `id` query parameter.',
{ status: 400 },
)
}

if (!Object.keys(PRODUCT_CONFIG).includes(product)) {
console.error(`API Error: Product, ${product}, not found in contentDirMap`)
return new Response('Not found', { status: 404 })
}

// Get all versions for the given product
const productVersions = docsPathsAllVersions[product] || {}

// Find versions where the id exists in the docs
const versions = Object.entries(productVersions)
.reduce((acc, [version, docs]: [string, any[]]) => {

Check warning on line 42 in app/api/v2/content-versions/route.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected acc to have a type annotation
const doc = (docs as any[]).find((doc) => doc.id === id)

Check warning on line 43 in app/api/v2/content-versions/route.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected doc to have a type annotation
if (doc) {
acc.push({
version,
path: doc?.path,
})
}

return acc
}, [])

return Response.json({
versions,
})
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin API reference for Terraform Enterprise
uuidV4: 92a97263-e804-474f-9eb6-86fd0d53f33c
description: >-
Use the `/admin` set of endpoints to configure and support your Terraform Enterprise installation. Learn about operations available in the HTTP API.
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
page_title: /admin/opa-versions API reference for Terraform Enterprise
uuidV4: bd8d60d6-c7b7-424b-b2db-48d05069d98c
description: >-
Use the `/admin/opa-versions` endpoint to manage available Open Policy Agent (OPA) versions. Learn how to list, show, create, update, and delete OPA versions using the HTTP API.
---
Expand Down Expand Up @@ -34,7 +35,7 @@ description: >-

# Admin OPA Versions API

The `/opa-versions` endpoint lets site administrators manage which versions of OPA you can use to enforce policies.
The `/opa-versions` endpoint lets site administrators manage which versions of OPA you can use to enforce policies.

-> **Terraform Enterprise Only:** The admin API is exclusive to Terraform Enterprise, and can only be used by the admins and operators who install and maintain their organization's Terraform Enterprise instance.

Expand Down
28 changes: 24 additions & 4 deletions scripts/gather-all-versions-docs-paths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import { batchPromises } from './utils/batch-promises.mjs'

const execAsync = promisify(exec)

Check failure on line 14 in scripts/gather-all-versions-docs-paths.mjs

View workflow job for this annotation

GitHub Actions / Lint

'execAsync' is assigned a value but never used

export async function gatherAllVersionsDocsPaths(versionMetadata) {
const allDocsPaths = {}
Expand Down Expand Up @@ -59,6 +59,17 @@
return allDocsPaths
}

const extractIdFromFrontmatter = (content) => {
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/m);
if (!frontmatterMatch) {
return '';
}

const frontmatter = frontmatterMatch[1];
const idMatch = frontmatter.match(/^uuidV4:\s*(.+)$/m);
return idMatch ? idMatch[1].trim() : '';
};

export async function getProductPaths(directory, productSlug) {
const apiPaths = []

Expand All @@ -75,17 +86,22 @@
} else {
const itemName = item.split('.')[0]

const content = fs.readFileSync(itemPath, 'utf8')
const id = extractIdFromFrontmatter(content)

if (itemName === 'index') {
apiPaths.push({
path: path.join(productSlug, relativePath),
itemPath,
id,
})
return
}

apiPaths.push({
path: path.join(productSlug, relativePath, itemName),
itemPath,
id,
})
}
})
Expand All @@ -97,12 +113,16 @@
`Creating change history for files in ${directory}`,
apiPaths,
async (apiPath) => {
const created_at = await execAsync(
`git log --format=%cI --max-count=1 ${apiPath.itemPath}`,
)
// TODO: We only care about a real value if it's a prod build
const created_at = "2025-05-29T11:04:37-04:00"
apiPath.created_at = created_at

// await execAsync(
// `git log --format=%cI --max-count=1 ${apiPath.itemPath}`,
// )

// remove the "\n" from the end of the output
apiPath.created_at = created_at.stdout.slice(0, -1)
// apiPath.created_at = created_at.stdout.slice(0, -1)
},
{ loggingEnabled: false },
)
Expand Down
5 changes: 5 additions & 0 deletions scripts/prebuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ async function main() {
const docsPathsAllVersionsJson = JSON.stringify(docsPathsAllVersions, null, 2)
fs.writeFileSync(DOCS_PATHS_ALL_VERSIONS_FILE, docsPathsAllVersionsJson)

if (process.argv.includes('--all-docs-versions')) {
console.log('Only generating up to all docs steps, skipping other steps.')
return
}

// Apply MDX transforms, writing out transformed MDX files to `public`
await buildMdxTransforms(CONTENT_DIR, CONTENT_DIR_OUT, versionMetadata)

Expand Down
Loading