diff --git a/docusaurus.config.ts b/docusaurus.config.ts index c8bdc924209..018dfb5fbef 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -26,6 +26,10 @@ const config: Config = { customFields: { SEGMENT_API_KEY: process.env.SEGMENT_API_KEY, HARNESS_GENERIC_READ_ONLY_KEY: process.env.HARNESS_GENERIC_READ_ONLY_KEY, + future: { + v4: true, + experimental_faster: true, + }, }, //Mermaid Diagram Functionality @@ -47,10 +51,6 @@ const config: Config = { locales: ['en'], }, - future: { - experimental_faster: true, - }, - presets: [ [ 'classic', @@ -439,6 +439,10 @@ const config: Config = { label: 'Feature Requests', to: 'https://ideas.harness.io', }, + { + label: 'Feature Flags GA List', + to: '/feature-flags', + }, { label: 'Instructor-Led Training', to: '/university?ilt', @@ -528,6 +532,16 @@ const config: Config = { }, }, ], + [ + path.resolve(__dirname, './plugins/docs-rss-plugin'), + { + id: 'feature-flags', + path: 'feature-flags', + routeBasePath: 'feature-flags', + exclude: ['**/shared/**', '**/static/**', '**/content/**'], + editUrl: 'https://github.com/harness/developer-hub/tree/main', + }, + ], // redirect plugin start [ path.resolve(__dirname, './plugins/docsEnhanced-plugin'), @@ -613,6 +627,7 @@ const config: Config = { path.join(__dirname, '/plugins/utmcookie-plugin'), path.join(__dirname, '/plugins/focusOnAnchor-plugin'), path.join(__dirname, '/plugins/feedback-plugin'), + path.join(__dirname, '/plugins/feature-flags-rss-plugin'), ], clientModules: [ path.join(__dirname, '/client-modules/searchBar'), diff --git a/feature-flags/index.md b/feature-flags/index.md new file mode 100644 index 00000000000..2e18a726831 --- /dev/null +++ b/feature-flags/index.md @@ -0,0 +1,13 @@ +--- +sidebar_position: 0 +hide_table_of_contents: true +hide_title: true +title: Feature Flags +slug: / +date: 2024-11-13T10:00 +--- + +import FeatureFlagsLanding from '@site/src/components/FeatureFlagGA/FeatureFlagsLanding'; +import ffGAFeed from './static/ff-ga-feed.json'; + + diff --git a/feature-flags/static/ff-ga-feed.json b/feature-flags/static/ff-ga-feed.json new file mode 100644 index 00000000000..aeca9ee93db --- /dev/null +++ b/feature-flags/static/ff-ga-feed.json @@ -0,0 +1,128 @@ +[ + { + "flagKey": "CDS_ADD_DEPLOYMENT_FREEZE_BYPASS_AUDIT", + "description": "Enables audit logging for deployment freeze bypass actions.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_DEPLOYMENT_FREEZE_GRANULAR_RBAC", + "description": "Provides granular role-based access control for deployment freeze operations based on environment type.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_GITLAB_TRIGGER_TAG_EVENT", + "description": "Enables GitLab tag event triggers for pipelines.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_TF_POLICY_EVALUATION", + "description": "Enables Terraform policy evaluation in Continuous Delivery workflows.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_TEXTAREA_FOR_OVERRIDE_VARIABLES", + "description": "Provides textarea input for override variables in deployment configurations.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_TAS_LOGIN_OPTIMIZATION", + "description": "Optimizes Tanzu Application Service (TAS) login performance and reliability by reducing the number of CLI initialisation calls.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_SUPPORT_TF_CLOUD_PLAN_REFRESH_TYPE", + "description": "Adds support for Terraform Cloud plan refresh type operations.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_UI_ENABLE_DISALLOWED_USER_EMAILS_IN_APPROVAL_STEP", + "description": "Enables configuration to block certain users from approving in Harness Approval steps based on emails.", + "gaStartDate": "2025-09-15", + "module": "Pipeline" + }, + { + "flagKey": "CDS_AZURE_OIDC_AUTHENTICATION", + "description": "Enables OIDC authentication for Azure connectors.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_GCP_LIST_PROJECTS_AUTH_FIX", + "description": "Fixes authentication issues when listing Google Cloud Platform projects.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_GCP_OIDC_CONNECTOR_CROSS_PROJECT_ACCESS", + "description": "Enables cross-project access for Google Cloud Platform connectors.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_OPTIONAL_VALUES_YAML", + "description": "Enables optional values.yaml files in K8s & Helm deployments.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_ENFORCE_GIT_EXPERIENCE", + "description": "Enforces Git-based experience for CD entities such as service, environment, infrastructure and override configurations.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_K8S_DETAILED_LOGS", + "description": "Provides detailed logging for Kubernetes operations in CD.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_INFRA_DEFINITION_VALIDATION", + "description": "Enforces certain validation for infrastructure definitions in CD.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_USE_SECONDARY_NODE_FOR_TIMESCALE_QUERIES", + "description": "Internal optimisation to use secondary database nodes for TimescaleDB queries to improve performance.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_ADD_EXPRESSION_RESOLUTION_FOR_OVERRIDES_V2_AT_SERVICE_STEP", + "description": "Fixes a bug that resolves expressions in overrides at the service step.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_OVERRIDES_GITX", + "description": "Enables Git experience for service and infrastructure overrides management.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_EVENT_BRIDGE_WEBHOOK", + "description": "Enables Event Relay triggers for pipelines.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_SERVICE_INFRA_FAILURE_STRATEGY", + "description": "Implements failure strategies for service and infrastructure deployment steps.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + }, + { + "flagKey": "CDS_OVERRIDES_V2_IDENTIFIER_SUPPORT", + "description": "Adds identifier support for version 2 service and infrastructure overrides.", + "gaStartDate": "2025-09-15", + "module": "Continuous Delivery" + } +] \ No newline at end of file diff --git a/package.json b/package.json index 468f423d2ca..e2d73b8eeb7 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "license": "MIT", "private": true, "scripts": { + "prebuild": "node scripts/generate-feature-flags-rss.js", "docusaurus": "docusaurus", "start": "docusaurus start --host 0.0.0.0", "build": "export NODE_OPTIONS=--max-old-space-size=7296 && DOCUSAURUS_IGNORE_SSG_WARNINGS=true docusaurus build", diff --git a/plugins/feature-flags-rss-plugin/index.js b/plugins/feature-flags-rss-plugin/index.js new file mode 100644 index 00000000000..7750a388ece --- /dev/null +++ b/plugins/feature-flags-rss-plugin/index.js @@ -0,0 +1,72 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { Feed } = require('feed'); + +function filterAndSortLast3Months(entries) { + const now = new Date(); + const threeMonthsAgo = new Date(); + threeMonthsAgo.setMonth(now.getMonth() - 3); + const filtered = entries.filter(entry => { + const entryDate = new Date(entry.gaStartDate); + return entryDate >= threeMonthsAgo; + }); + filtered.sort((a, b) => new Date(b.gaStartDate) - new Date(a.gaStartDate)); + return filtered; +} + +// WARNING: This plugin generates the RSS file in postBuild, so it will NOT be available in dev mode (docusaurus start), +// and links to /feature-flags/rss.xml will be flagged as broken unless onBrokenLinks is set to 'warn'. + +async function featureFlagsRssPlugin(context, options) { + return { + name: 'feature-flags-rss-plugin', + async postBuild({ outDir }) { + const jsonPath = path.join( + context.siteDir, + 'feature-flags/static/ff-ga-feed.json' + ); + if (!fs.existsSync(jsonPath)) { + console.warn('[feature-flags-rss-plugin] JSON file not found:', jsonPath); + return; + } + const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); + const filtered = filterAndSortLast3Months(data); + if (!filtered || filtered.length === 0) { + console.warn('[feature-flags-rss-plugin] No entries for RSS feed. Skipping RSS file generation.'); + return; + } + const siteUrl = context.siteConfig.url || ''; + const baseUrl = context.siteConfig.baseUrl || '/'; + const rssFeed = new Feed({ + title: 'Feature Flags GA RSS Feed', + id: siteUrl + baseUrl + 'feature-flags/rss.xml', + link: siteUrl + baseUrl + 'feature-flags/rss.xml', + updated: new Date(), + feedLinks: { + rss: siteUrl + baseUrl + 'feature-flags/rss.xml', + }, + }); + filtered.forEach(item => { + rssFeed.addItem({ + title: item.flagKey, + id: item.flagKey, + link: siteUrl + baseUrl + 'feature-flags/', + description: item.description, + date: new Date(item.gaStartDate), + category: [ + { + name: item.module, + term: item.module, + }, + ], + }); + }); + const outputPathRSS = path.join(outDir, 'feature-flags', 'rss.xml'); + fs.ensureDirSync(path.dirname(outputPathRSS)); + fs.writeFileSync(outputPathRSS, rssFeed.rss2()); + console.log('[feature-flags-rss-plugin] RSS feed generated at', outputPathRSS); + }, + }; +} + +module.exports = featureFlagsRssPlugin; \ No newline at end of file diff --git a/scripts/generate-feature-flags-rss.js b/scripts/generate-feature-flags-rss.js new file mode 100644 index 00000000000..d82ba5e8d79 --- /dev/null +++ b/scripts/generate-feature-flags-rss.js @@ -0,0 +1,45 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { Feed } = require('feed'); + +const jsonPath = path.join(__dirname, '../feature-flags/static/ff-ga-feed.json'); +const outputPath = path.join(__dirname, '../build/feature-flags/rss.xml'); +const siteUrl = 'https://developer.harness.io'; +const baseUrl = '/'; + +if (!fs.existsSync(jsonPath)) { + console.warn('[ff-ga-rss] JSON file not found:', jsonPath); + process.exit(0); +} +const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); +if (!Array.isArray(data) || data.length === 0) { + console.warn('[ff-ga-rss] No entries for RSS feed. Skipping RSS file generation.'); + process.exit(0); +} +const feed = new Feed({ + title: 'Feature Flags GA RSS Feed', + id: siteUrl + baseUrl + 'feature-flags/rss.xml', + link: siteUrl + baseUrl + 'feature-flags/rss.xml', + updated: new Date(), + feedLinks: { + rss: siteUrl + baseUrl + 'feature-flags/rss.xml', + }, +}); +data.forEach(item => { + feed.addItem({ + title: item.flagKey, + id: item.flagKey, + link: siteUrl + baseUrl + 'feature-flags/', + description: item.description, + date: new Date(item.gaStartDate), + category: [ + { + name: item.module, + term: item.module, + }, + ], + }); +}); +fs.ensureDirSync(path.dirname(outputPath)); +fs.writeFileSync(outputPath, feed.rss2()); +console.log('[ff-ga-rss] RSS feed generated at', outputPath); \ No newline at end of file diff --git a/src/components/FeatureFlagGA/FeatureFlagsGAListPage.tsx b/src/components/FeatureFlagGA/FeatureFlagsGAListPage.tsx new file mode 100644 index 00000000000..5c0bdbc8f5a --- /dev/null +++ b/src/components/FeatureFlagGA/FeatureFlagsGAListPage.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import styles from './ga-list.module.css'; +import releaseNotesStyles from '@site/src/components/ReleaseNotes/styles.module.scss'; + +function FeatureFlagsGATable({ flags }) { + return ( +
+ + + + + + + + + + + {flags.map((flag) => ( + + + + + + + ))} + +
Flag KeyDescriptionGA Start DateModule
{flag.flagKey}{flag.description}{new Date(flag.gaStartDate).toLocaleDateString()}{flag.module}
+
+ ); +} + +export default function FeatureFlagsGAListPage({ flags = [], loading = false, error = null, compact = false }) { + const { siteConfig: { baseUrl = '/' } = {} } = useDocusaurusContext(); + if (loading) return

Loading...

; + if (error) return

{error}

; + if (compact) { + return ; + } + return ( +
+
+
+
+ + + +
+
+
+
+

+ This page lists all Feature Flags that have reached General Availability (GA) in the last several months. You can track when a flag was GA'd, view its description, and filter by module. For a machine-readable feed, see the Atom link above. +

+
+
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/FeatureFlagGA/FeatureFlagsLanding.tsx b/src/components/FeatureFlagGA/FeatureFlagsLanding.tsx new file mode 100644 index 00000000000..301a28e4ba6 --- /dev/null +++ b/src/components/FeatureFlagGA/FeatureFlagsLanding.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import FeatureFlagsGAListPage from './FeatureFlagsGAListPage'; + +export default function FeatureFlagsLanding({ staticFlags }) { + return ( +
+
+
+
+

Feature Flags GA Timeline

+
+
+
+

+ Track when Harness Feature Flags reach General Availability (GA). This timeline shows GA dates, descriptions, and the modules where each feature flag was implemented. +

+
+
+
+ About This Timeline +
    +
  • View GA dates for all major Harness Feature Flags
  • +
  • Filter Feature Flags by module and date
  • +
  • Access detailed descriptions of each Feature Flag
  • +
  • Subscribe to updates via the Atom feed below
  • +
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/FeatureFlagGA/ga-list.module.css b/src/components/FeatureFlagGA/ga-list.module.css new file mode 100644 index 00000000000..34965c6d5f7 --- /dev/null +++ b/src/components/FeatureFlagGA/ga-list.module.css @@ -0,0 +1,79 @@ +.infoBlock { + background: var(--ifm-alert-background-color, #f5f6fa); + border-left: 4px solid var(--ifm-color-primary, #00adef); + padding: 1rem 1.5rem; + margin-bottom: 1.5rem; + border-radius: 4px; + color: var(--ifm-font-color-base, #222); +} + +.tableWrapper { + overflow-x: auto; + margin-top: 1.5rem; + margin-bottom: 2rem; +} + +.gaTable { + width: 100%; + border-collapse: collapse; + background: white; + box-shadow: 0 1.5px 3px 0 rgb(0 0 0 / 8%); +} + +.gaTable th, .gaTable td { + border: 1px solid var(--ifm-table-border-color, #eaeaea); + padding: 0.75rem 1rem; + text-align: left; +} + +.gaTable th { + background: var(--ifm-table-head-background, #f5f6fa); + color: var(--ifm-color-primary, #00adef); + font-weight: 600; +} + +.gaTable tr:nth-child(even) { + background: var(--ifm-table-stripe-background, #fafbfc); +} + +.gaTable tr:hover { + background: var(--ifm-hover-overlay, #f0f4fa); +} + +.btnContainer { + display: flex; + align-items: center; + align-content: center; + flex-shrink: 0; + flex-flow: row wrap; + gap: 12px; +} + +.btn { + padding: 12px 20px; + min-width: 180px; + background: var(--gray-100); + background: #f3f3fa; + border: 1px solid var(--gray-300); + border-radius: 4px; + font-weight: 600; + font-size: 16px; + line-height: 24px; + color: var(--gray-600); + white-space: nowrap; + display: flex; + align-items: center; + cursor: pointer; + filter: drop-shadow(0px 0px 1px rgba(40, 41, 61, 0.04)) + drop-shadow(0px 2px 4px rgba(96, 97, 112, 0.16)); + transition: background 0.2s, color 0.2s; +} +.btn:hover { + color: var(--primary-8); + background: #fafbfc; +} +.btn img { + margin-right: 4px; + height: 24px; + width: auto; +} \ No newline at end of file diff --git a/src/components/FeatureFlagGA/ga-list.tsx b/src/components/FeatureFlagGA/ga-list.tsx new file mode 100644 index 00000000000..195b23e9d39 --- /dev/null +++ b/src/components/FeatureFlagGA/ga-list.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, useState } from 'react'; +import Layout from '@theme/Layout'; +import DocsButton from '../DocsButton'; +import styles from './ga-list.module.css'; + +const FEED_URL = '/feature-flags/static/ff-ga-feed.json'; +const ATOM_URL = 'https://developer.harness.io/feature-flags/rss.xml'; + +interface FFGAEntry { + flagKey: string; + description: string; + gaStartDate: string; + module: string; +} + +const FeatureFlagsGAList: React.FC = () => { + const [flags, setFlags] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetch(FEED_URL) + .then((res) => { + if (!res.ok) throw new Error('Failed to fetch GA feed'); + return res.json(); + }) + .then((data: FFGAEntry[]) => { + // Sort descending by date + setFlags( + data.sort((a, b) => new Date(b.gaStartDate).getTime() - new Date(a.gaStartDate).getTime()) + ); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); + + return ( + +
+

Feature Flags General Availability (GA) List

+
+ About Feature Flags GA List +
    +
  • This page lists all Feature Flags that have reached General Availability (GA) in the last several months.
  • +
  • You can track when a flag was GA'd, view its description, and filter by module.
  • +
  • For a machine-readable feed, see the Atom link below.
  • +
+
+ + {loading &&

Loading...

} + {error &&

{error}

} + {!loading && !error && ( +
+ + + + + + + + + + + {flags.map((flag) => ( + + + + + + + ))} + +
Flag KeyDescriptionGA Start DateModule
{flag.flagKey}{flag.description}{new Date(flag.gaStartDate).toLocaleDateString()}{flag.module}
+
+ )} +
+
+ ); +}; + +export default FeatureFlagsGAList; \ No newline at end of file