Files
openobserve/.github/workflows/auto-update-features.yml
T
Bhargav 576671d14f fix: automate update process of feature master list on about us page (#10010)
### **PR Type**
Enhancement


___

### **Description**
- Centralize features in JSON registry

- Refactor ComparisonTable to load from registry

- Add GitHub Action for auto-updating features.json

- Update Slack PR notification configuration

- Enable JSON module imports in tsconfig


___

### Diagram Walkthrough


```mermaid
flowchart LR
  PRDesc["PR description feature section"]
  AutoUpdate["Auto-update features.json Workflow"]
  featuresJSON["features.json"]
  featuresTS["features.ts registry"]
  TableComp["FeatureComparisonTable.vue"]
  PRDesc -- "triggers" --> AutoUpdate
  AutoUpdate -- "updates" --> featuresJSON
  featuresJSON -- "loaded by" --> featuresTS
  featuresTS -- "used by" --> TableComp
```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>2
files</summary><table>
<tr>
<td><strong>features.ts</strong><dd><code>Add central feature registry
and helper functions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-232fb84d995004ab5e3da68e3a9fc835808f460feb9f4478dca50ea4a48ed057">+104/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>FeatureComparisonTable.vue</strong><dd><code>Refactor table
to load features from registry</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-450bc4b412ffde37d04f96046199090e3d41face1ee85b32e446c9b470ce299e">+31/-30</a>&nbsp;
</td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>6 files</summary><table>
<tr>
<td><strong>auto-update-features.yml</strong><dd><code>Add workflow to
auto-update features.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-7179e615740ebfe7a08143d567c20bdf7d813506f77c6efba463d105b48379ad">+253/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>pr-slack.yml</strong><dd><code>Update Slack action with new
webhook syntax</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-c049020d06d481f16942b242608c22fa8377924730787f97288e03561768f51a">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>features.json</strong><dd><code>Add JSON-based features
configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-f3c3ea1b3f6e34a0718bced5c01c09bfe13b5ba7936ee8b77eb66b8c14767d49">+262/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>features.schema.json</strong><dd><code>Add JSON schema for
features.json validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-42009a609f9d2eb5a6a7264d7c236ef538fd624f29edf02759bd3b231453be27">+71/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>tsconfig.app.json</strong><dd><code>Enable JSON module
imports in app config</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-1fc2dc23685a64f037b0063bf18fc420a8faeb83e98a2cc2b407f1a25c4fc1f5">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>tsconfig.json</strong><dd><code>Enable JSON module imports
in base tsconfig</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/openobserve/openobserve/pull/10010/files#diff-0dafb2d38c20327f4a74c03733790ccca87a4bbda69ec4c03aa362524b0522ca">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

</details>

___
2026-01-17 18:06:23 +05:30

254 lines
10 KiB
YAML

name: Auto-update Features JSON
on:
pull_request:
types: [opened, edited]
branches:
- main
permissions:
contents: write
pull-requests: write
jobs:
update-features:
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Parse PR description and update features.json
uses: actions/github-script@v7
id: parse-feature
with:
script: |
const fs = require('fs');
const path = require('path');
// Get PR body
const prBody = context.payload.pull_request.body || '';
// Check if PR contains feature update section
const featureRegex = /<!-- FEATURE_UPDATE_START -->([\s\S]*?)<!-- FEATURE_UPDATE_END -->/;
const match = prBody.match(featureRegex);
if (!match) {
console.log('No feature update section found in PR description');
core.setOutput('has_feature', 'false');
return;
}
console.log('Feature update section found!');
core.setOutput('has_feature', 'true');
// Parse the feature data from the matched section
const featureSection = match[1].trim();
// Try to parse as JSON or YAML-like format
let featureData;
try {
// Try JSON first
const jsonMatch = featureSection.match(/```json\s*([\s\S]*?)\s*```/);
if (jsonMatch) {
featureData = JSON.parse(jsonMatch[1]);
} else {
// Parse key-value format
featureData = {};
const lines = featureSection.split('\n');
for (const line of lines) {
const kvMatch = line.match(/^\s*-?\s*\*?\*?(\w+)\*?\*?\s*:\s*(.+)$/);
if (kvMatch) {
const key = kvMatch[1].toLowerCase().replace(/\s+/g, '_');
let value = kvMatch[2].trim();
// Handle boolean values
if (value === 'true' || value === 'yes') value = true;
if (value === 'false' || value === 'no') value = false;
// Handle nested objects (availability)
if (key.startsWith('availability_')) {
if (!featureData.availability) featureData.availability = {};
const edition = key.replace('availability_', '');
featureData.availability[edition] = value;
} else {
featureData[key] = value;
}
}
}
}
console.log('Parsed feature data:', JSON.stringify(featureData, null, 2));
// Validate required fields
if (!featureData.id) {
throw new Error('Missing required field: id');
}
if (!featureData.availability) {
throw new Error('Missing required field: availability');
}
if (!featureData.availability.opensource ||
!featureData.availability.enterprise ||
!featureData.availability.cloud) {
throw new Error('Missing required availability fields (opensource, enterprise, cloud)');
}
// Read existing features.json
const featuresPath = path.join(process.env.GITHUB_WORKSPACE, 'web/src/constants/features.json');
const featuresContent = fs.readFileSync(featuresPath, 'utf8');
const featuresJson = JSON.parse(featuresContent);
// Check if feature already exists
const existingIndex = featuresJson.features.findIndex(f => f.id === featureData.id);
if (existingIndex >= 0) {
console.log(`Updating existing feature: ${featureData.id}`);
featuresJson.features[existingIndex] = featureData;
core.setOutput('action', 'updated');
} else {
console.log(`Adding new feature: ${featureData.id}`);
// Determine position
let position = featureData.position || 'end';
delete featureData.position; // Remove position field from feature data
if (position === 'start') {
featuresJson.features.unshift(featureData);
} else if (typeof position === 'number') {
featuresJson.features.splice(position, 0, featureData);
} else {
featuresJson.features.push(featureData);
}
core.setOutput('action', 'added');
}
// Write updated features.json
fs.writeFileSync(featuresPath, JSON.stringify(featuresJson, null, 2) + '\n', 'utf8');
core.setOutput('feature_id', featureData.id);
core.setOutput('success', 'true');
} catch (error) {
console.error('Error parsing feature data:', error.message);
core.setOutput('success', 'false');
core.setOutput('error', error.message);
throw error;
}
- name: Check for translation keys
if: steps.parse-feature.outputs.success == 'true'
id: check-translations
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const featureId = '${{ steps.parse-feature.outputs.feature_id }}';
const expectedKey = `feature_${featureId}`;
// Check if translation exists in en.json
const enJsonPath = path.join(process.env.GITHUB_WORKSPACE, 'web/src/locales/languages/en.json');
const enJson = JSON.parse(fs.readFileSync(enJsonPath, 'utf8'));
const hasTranslation = enJson.about && enJson.about[expectedKey];
core.setOutput('has_translation', hasTranslation ? 'true' : 'false');
core.setOutput('translation_key', `about.${expectedKey}`);
- name: Commit changes
if: steps.parse-feature.outputs.success == 'true'
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
if git diff --quiet web/src/constants/features.json; then
echo "No changes to commit"
exit 0
fi
git add web/src/constants/features.json
git commit -m "chore: auto-update features.json from PR description
Feature ${{ steps.parse-feature.outputs.action }}: ${{ steps.parse-feature.outputs.feature_id }}
🤖 Automated update via GitHub Actions"
git push
- name: Comment on PR - Success
if: steps.parse-feature.outputs.success == 'true'
uses: actions/github-script@v7
with:
script: |
const featureId = '${{ steps.parse-feature.outputs.feature_id }}';
const action = '${{ steps.parse-feature.outputs.action }}';
const hasTranslation = '${{ steps.check-translations.outputs.has_translation }}' === 'true';
const translationKey = '${{ steps.check-translations.outputs.translation_key }}';
let message = `### ✅ Feature ${action === 'added' ? 'Added' : 'Updated'} Successfully!\n\n`;
message += `**Feature ID:** \`${featureId}\`\n`;
message += `**Action:** ${action}\n\n`;
message += `📝 **File Updated:** \`web/src/constants/features.json\`\n\n`;
if (!hasTranslation) {
message += `### ⚠️ Translation Missing\n\n`;
message += `Please add the translation key \`${translationKey}\` to:\n`;
message += `- \`web/src/locales/languages/en.json\`\n`;
message += `- All other language files\n\n`;
message += `Example for \`en.json\`:\n`;
message += `\`\`\`json\n`;
message += `{\n`;
message += ` "about": {\n`;
message += ` "feature_${featureId}": "Your Feature Name Here"\n`;
message += ` }\n`;
message += `}\n`;
message += `\`\`\`\n`;
} else {
message += `✅ Translation key \`${translationKey}\` found!\n`;
}
message += `\n---\n`;
message += `🤖 This feature was automatically added from your PR description.`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
- name: Comment on PR - Error
if: steps.parse-feature.outputs.success == 'false'
uses: actions/github-script@v7
with:
script: |
const error = '${{ steps.parse-feature.outputs.error }}';
const message = `### ❌ Feature Update Failed\n\n` +
`**Error:** ${error}\n\n` +
`Please check your feature definition format in the PR description.\n\n` +
`See the [Feature Update Guide](../../web/src/constants/FEATURES_JSON_GUIDE.md) for the correct format.`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
- name: Add label
if: steps.parse-feature.outputs.success == 'true'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['feature-update', 'auto-generated']
});