Skip to content

Commit 0a5244e

Browse files
committed
feat: add isDraft property and feature flags for content visibility
Add lightweight feature flag system (SHOW_DRAFTS, EXAMPLES_FEATURE) to control content visibility across the site. Recipes, templates, and examples now support an optional isDraft property that hides them from llms.txt, API markdown endpoints, and UI resource lists unless SHOW_DRAFTS is enabled. The EXAMPLES_FEATURE flag gates all example content (routes, filters, landing page carousel, llms.txt section). Both flags default to disabled when unset.
1 parent 9d9dc17 commit 0a5244e

15 files changed

Lines changed: 399 additions & 85 deletions

api/content-markdown.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import { hasMarkdownSlug } from "../src/lib/content-markdown";
66
import { expandMdxImports } from "../src/lib/expand-mdx";
77
import {
88
examples,
9+
filterPublished,
910
recipes,
1011
recipesInOrder,
1112
templates,
1213
} from "../src/lib/recipes/recipes";
14+
import { showDrafts, examplesEnabled } from "../src/lib/feature-flags-server";
1315
import { solutions } from "../src/lib/solutions/solutions";
1416

1517
export type MarkdownSection =
@@ -212,32 +214,40 @@ function readResourceMarkdown(rootDir: string, slug: string): string {
212214

213215
/** Markdown index served at /resources.md — lists all templates, recipes, and examples. */
214216
function readResourcesIndex(): string {
217+
const includeDrafts = showDrafts();
218+
const includeExamples = examplesEnabled();
219+
const publishedTemplates = filterPublished(templates, includeDrafts);
220+
const publishedRecipes = filterPublished(recipesInOrder, includeDrafts);
221+
const publishedExamples = includeExamples
222+
? filterPublished(examples, includeDrafts)
223+
: [];
224+
215225
const lines: string[] = [
216226
"# Resources",
217227
"",
218228
"Guides and examples for building on Databricks.",
219229
"",
220230
];
221231

222-
if (templates.length > 0) {
232+
if (publishedTemplates.length > 0) {
223233
lines.push("## Guides", "");
224-
for (const t of templates) {
234+
for (const t of publishedTemplates) {
225235
lines.push(`- [${t.name}](/resources/${t.id}.md): ${t.description}`);
226236
}
227237
lines.push("");
228238
}
229239

230-
if (recipesInOrder.length > 0) {
240+
if (publishedRecipes.length > 0) {
231241
lines.push("## Recipes", "");
232-
for (const r of recipesInOrder) {
242+
for (const r of publishedRecipes) {
233243
lines.push(`- [${r.name}](/resources/${r.id}.md): ${r.description}`);
234244
}
235245
lines.push("");
236246
}
237247

238-
if (examples.length > 0) {
248+
if (publishedExamples.length > 0) {
239249
lines.push("## Examples", "");
240-
for (const e of examples) {
250+
for (const e of publishedExamples) {
241251
lines.push(`- [${e.name}](/resources/${e.id}.md): ${e.description}`);
242252
}
243253
lines.push("");

docusaurus.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ const config: Config = {
1212
title: "Databricks Developer",
1313
tagline: "Build intelligent data and AI applications in minutes, not months",
1414
favicon: "img/favicon.svg",
15+
customFields: {
16+
showDrafts:
17+
process.env.SHOW_DRAFTS === "true" || process.env.SHOW_DRAFTS === "1",
18+
examplesFeature:
19+
process.env.EXAMPLES_FEATURE === "true" ||
20+
process.env.EXAMPLES_FEATURE === "1",
21+
},
1522

1623
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
1724
future: {

plugins/content-entries.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { readFileSync } from "fs";
22
import { resolve } from "path";
33
import type { LoadContext, Plugin } from "@docusaurus/types";
44
import { getMarkdownSlugs } from "../src/lib/content-markdown";
5-
import { recipes, examples, templates } from "../src/lib/recipes/recipes";
5+
import {
6+
recipes,
7+
examples,
8+
templates,
9+
filterPublished,
10+
} from "../src/lib/recipes/recipes";
11+
import { showDrafts, examplesEnabled } from "../src/lib/feature-flags-server";
612
import { solutions } from "../src/lib/solutions/solutions";
713

814
function assertNoDuplicateSlugs(): void {
@@ -84,6 +90,22 @@ export default function ExampleEntryPage(): ReactNode {
8490
}
8591

8692
function getRegistrySlugs(entryType: EntryType): string[] {
93+
const includeDrafts = showDrafts();
94+
if (entryType === "recipe") {
95+
return filterPublished(recipes, includeDrafts)
96+
.map((recipe) => recipe.id)
97+
.sort();
98+
}
99+
if (entryType === "example") {
100+
if (!examplesEnabled()) return [];
101+
return filterPublished(examples, includeDrafts)
102+
.map((example) => example.id)
103+
.sort();
104+
}
105+
return solutions.map((solution) => solution.id).sort();
106+
}
107+
108+
function getAllRegistrySlugs(entryType: EntryType): string[] {
87109
if (entryType === "recipe") {
88110
return recipes.map((recipe) => recipe.id).sort();
89111
}
@@ -93,15 +115,11 @@ function getRegistrySlugs(entryType: EntryType): string[] {
93115
return solutions.map((solution) => solution.id).sort();
94116
}
95117

96-
function assertSlugParity(
97-
entryType: EntryType,
98-
contentSlugs: string[],
99-
registrySlugs: string[],
100-
): void {
101-
const onlyInContent = contentSlugs.filter(
102-
(slug) => !registrySlugs.includes(slug),
103-
);
104-
const onlyInRegistry = registrySlugs.filter(
118+
function assertSlugParity(entryType: EntryType, contentSlugs: string[]): void {
119+
const allSlugs = getAllRegistrySlugs(entryType);
120+
121+
const onlyInContent = contentSlugs.filter((slug) => !allSlugs.includes(slug));
122+
const onlyInRegistry = allSlugs.filter(
105123
(slug) => !contentSlugs.includes(slug),
106124
);
107125

@@ -135,11 +153,12 @@ export default function contentEntriesPlugin(
135153
context.siteDir,
136154
options.contentSection,
137155
);
138-
const registrySlugs = getRegistrySlugs(options.entryType);
139-
assertSlugParity(options.entryType, contentSlugs, registrySlugs);
156+
assertSlugParity(options.entryType, contentSlugs);
157+
158+
const publishedSlugs = getRegistrySlugs(options.entryType);
140159

141160
const rawMarkdownBySlug: Record<string, string> = {};
142-
for (const slug of contentSlugs) {
161+
for (const slug of publishedSlugs) {
143162
const filePath = resolve(
144163
context.siteDir,
145164
"content",
@@ -152,11 +171,11 @@ export default function contentEntriesPlugin(
152171
setGlobalData({
153172
entryType: options.entryType,
154173
routeBasePath: options.routeBasePath,
155-
slugs: contentSlugs,
174+
slugs: publishedSlugs,
156175
rawMarkdownBySlug,
157176
});
158177

159-
for (const slug of contentSlugs) {
178+
for (const slug of publishedSlugs) {
160179
const modulePath = await createData(
161180
`${options.id}-${slug}-route.tsx`,
162181
createRouteModuleSource(options.entryType, slug),

plugins/llms-txt.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
templates,
77
recipesInOrder,
88
examples,
9+
filterPublished,
910
} from "../src/lib/recipes/recipes";
1011
import { expandMdxImports } from "../src/lib/expand-mdx";
12+
import { showDrafts, examplesEnabled } from "../src/lib/feature-flags-server";
1113

1214
type Section = {
1315
title: string;
@@ -136,6 +138,14 @@ function readDoc(
136138
}
137139

138140
function generateLlmsTxt(baseUrl: string, docsDir: string): string {
141+
const includeDrafts = showDrafts();
142+
const includeExamples = examplesEnabled();
143+
const publishedTemplates = filterPublished(templates, includeDrafts);
144+
const publishedRecipes = filterPublished(recipesInOrder, includeDrafts);
145+
const publishedExamples = includeExamples
146+
? filterPublished(examples, includeDrafts)
147+
: [];
148+
139149
const allSections: Section[] = SIDEBAR_SECTIONS.map((section) => ({
140150
title: section.title,
141151
description: section.description,
@@ -191,35 +201,35 @@ function generateLlmsTxt(baseUrl: string, docsDir: string): string {
191201
"",
192202
);
193203

194-
if (templates.length > 0) {
204+
if (publishedTemplates.length > 0) {
195205
lines.push(
196206
"### Guides",
197207
"",
198-
...templates.map(
208+
...publishedTemplates.map(
199209
(t) =>
200210
`- [${t.name}](${baseUrl}/resources/${t.id}.md): ${t.description}`,
201211
),
202212
"",
203213
);
204214
}
205215

206-
if (recipesInOrder.length > 0) {
216+
if (publishedRecipes.length > 0) {
207217
lines.push(
208218
"### Recipes",
209219
"",
210-
...recipesInOrder.map(
220+
...publishedRecipes.map(
211221
(r) =>
212222
`- [${r.name}](${baseUrl}/resources/${r.id}.md): ${r.description}`,
213223
),
214224
"",
215225
);
216226
}
217227

218-
if (examples.length > 0) {
228+
if (publishedExamples.length > 0) {
219229
lines.push(
220230
"### Examples",
221231
"",
222-
...examples.map(
232+
...publishedExamples.map(
223233
(e) =>
224234
`- [${e.name}](${baseUrl}/resources/${e.id}.md): ${e.description}`,
225235
),

src/components/home/template-preview.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import {
1515
CarouselPrevious,
1616
CarouselNext,
1717
} from "@/components/ui/carousel";
18+
import { useFeatureFlags } from "@/lib/feature-flags";
1819
import {
19-
landingResources,
20+
buildLandingResources,
2021
type LandingResourceItem,
2122
} from "@/lib/landing-content";
2223

@@ -171,13 +172,20 @@ function ResourceCard({
171172
}
172173

173174
export function TemplatePreview(): ReactNode {
175+
const { showDrafts: includeDrafts, examplesEnabled: includeExamples } =
176+
useFeatureFlags();
177+
const landingResources = buildLandingResources(
178+
includeDrafts,
179+
includeExamples,
180+
);
181+
174182
return (
175183
<section className="bg-white py-16 dark:bg-db-navy md:py-20">
176184
<div className="container px-4">
177185
<div className="mx-auto mb-8 max-w-6xl">
178186
<p className="mb-3 inline-flex items-center gap-2 text-[10px] font-semibold tracking-[0.12em] text-black/50 uppercase dark:text-white/50">
179187
<span className="text-db-lava">&#9679;</span>
180-
Guides and Examples
188+
{includeExamples ? "Guides and Examples" : "Guides"}
181189
</p>
182190
<div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
183191
<h2 className="max-w-xl text-3xl leading-tight font-medium tracking-tight text-black dark:text-white md:text-4xl">
Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { Checkbox } from "@/components/ui/checkbox";
2+
import { useFeatureFlags } from "@/lib/feature-flags";
23
import { SERVICES, type Service } from "@/lib/recipes/recipes";
34

45
export type ResourceType = "examples" | "guides";
56

7+
const ALL_RESOURCE_TYPES: ReadonlyArray<{
8+
type: ResourceType;
9+
label: string;
10+
}> = [
11+
{ type: "examples", label: "Examples" },
12+
{ type: "guides", label: "Guides" },
13+
];
14+
615
export function ResourceFilters({
716
selectedServices,
817
onToggleService,
@@ -14,6 +23,12 @@ export function ResourceFilters({
1423
selectedResourceTypes: Set<ResourceType>;
1524
onToggleResourceType: (type: ResourceType) => void;
1625
}) {
26+
const { examplesEnabled } = useFeatureFlags();
27+
28+
const visibleResourceTypes = examplesEnabled
29+
? ALL_RESOURCE_TYPES
30+
: ALL_RESOURCE_TYPES.filter((rt) => rt.type !== "examples");
31+
1732
return (
1833
<nav className="space-y-6" aria-label="Filters">
1934
<div>
@@ -37,31 +52,28 @@ export function ResourceFilters({
3752
</div>
3853
</div>
3954

40-
<div className="border-t border-black/8 pt-6 dark:border-white/8">
41-
<h3 className="mb-3 text-xs font-semibold tracking-wider text-black/50 uppercase dark:text-white/50">
42-
Type
43-
</h3>
44-
<div className="space-y-2">
45-
{(
46-
[
47-
{ type: "examples" as const, label: "Examples" },
48-
{ type: "guides" as const, label: "Guides" },
49-
] as const
50-
).map(({ type, label }) => (
51-
<label
52-
key={type}
53-
className="flex cursor-pointer items-center gap-2.5 rounded-md px-1 py-1 text-sm text-black/80 transition-colors hover:bg-black/4 dark:text-white/80 dark:hover:bg-white/4"
54-
>
55-
<Checkbox
56-
checked={selectedResourceTypes.has(type)}
57-
onCheckedChange={() => onToggleResourceType(type)}
58-
aria-label={label}
59-
/>
60-
<span className="leading-tight">{label}</span>
61-
</label>
62-
))}
55+
{visibleResourceTypes.length > 1 && (
56+
<div className="border-t border-black/8 pt-6 dark:border-white/8">
57+
<h3 className="mb-3 text-xs font-semibold tracking-wider text-black/50 uppercase dark:text-white/50">
58+
Type
59+
</h3>
60+
<div className="space-y-2">
61+
{visibleResourceTypes.map(({ type, label }) => (
62+
<label
63+
key={type}
64+
className="flex cursor-pointer items-center gap-2.5 rounded-md px-1 py-1 text-sm text-black/80 transition-colors hover:bg-black/4 dark:text-white/80 dark:hover:bg-white/4"
65+
>
66+
<Checkbox
67+
checked={selectedResourceTypes.has(type)}
68+
onCheckedChange={() => onToggleResourceType(type)}
69+
aria-label={label}
70+
/>
71+
<span className="leading-tight">{label}</span>
72+
</label>
73+
))}
74+
</div>
6375
</div>
64-
</div>
76+
)}
6577
</nav>
6678
);
6779
}

src/lib/feature-flags-server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function showDrafts(): boolean {
2+
const value = process.env.SHOW_DRAFTS;
3+
return value === "true" || value === "1";
4+
}
5+
6+
export function examplesEnabled(): boolean {
7+
const value = process.env.EXAMPLES_FEATURE;
8+
return value === "true" || value === "1";
9+
}

src/lib/feature-flags.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
2+
3+
export function useFeatureFlags() {
4+
const { siteConfig } = useDocusaurusContext();
5+
const fields = siteConfig.customFields as Record<string, unknown>;
6+
return {
7+
showDrafts: fields.showDrafts === true,
8+
examplesEnabled: fields.examplesFeature === true,
9+
};
10+
}

0 commit comments

Comments
 (0)