diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4ff0ef8c3cb..b37938d86fd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -801,6 +801,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/commands.yml @torkelo /.github/workflows/community-release.yml @grafana/grafana-developer-enablement-squad /.github/workflows/detect-breaking-changes-* @grafana/plugins-platform-frontend +/.github/workflows/detect-plugin-extension-changes.yml @grafana/plugins-platform-frontend /.github/workflows/documentation-ci.yml @grafana/docs-tooling /.github/workflows/deploy-pr-preview.yml @grafana/docs-tooling /.github/workflows/feature-toggles-ci.yml @grafana/docs-tooling diff --git a/.github/workflows/detect-plugin-extension-changes.yml b/.github/workflows/detect-plugin-extension-changes.yml new file mode 100644 index 00000000000..b5ab96725d9 --- /dev/null +++ b/.github/workflows/detect-plugin-extension-changes.yml @@ -0,0 +1,156 @@ +--- +name: Detect Plugin Extension Changes + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +on: + pull_request: + branches: + - 'main' + paths: + - 'packages/**' + - 'public/**' + +env: + # Space-separated list of keywords referring to plugin extensions + PLUGIN_EXTENSION_KEYWORDS: "usePluginLinks, usePluginComponent, usePluginComponents, usePluginFunctions, PluginExtensionPoints" + +jobs: + detect-plugin-extension-changes: + name: Detect Plugin Extension Changes + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + if: github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Check for plugin extension changes + id: check-changes + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + const fs = require('fs'); + + // Plugin extension keywords from environment + const keywords = process.env.PLUGIN_EXTENSION_KEYWORDS.split(','); + const baseSha = '${{ github.event.pull_request.base.sha }}'; + const headSha = '${{ github.event.pull_request.head.sha }}'; + + console.log('Checking for plugin extension changes...'); + console.log('Keywords:', keywords); + + // Get changed files in packages/ and public/ directories + let changedFiles = []; + const diffOutput = execSync( + `git diff --name-only ${baseSha}...${headSha} -- packages/ public/`, + { encoding: 'utf8' } + ).trim(); + + if (diffOutput) { + changedFiles = diffOutput.split('\n').filter(file => { + // Validate file path and ensure it's in target directories + return file.match(/^(packages\/|public\/)/) && + file.match(/^[a-zA-Z0-9._/-]+$/) && + fs.existsSync(file); + }); + } + + console.log('Changed files to check:', changedFiles); + + // Check each file for plugin extension keywords + const filesWithChanges = new Set(); + let hasPluginExtensionChanges = false; + + for (const file of changedFiles) { + try { + // Get the diff for this specific file + const fileDiff = execSync( + `git diff ${baseSha}...${headSha} -- "${file}"`, + { encoding: 'utf8' } + ); + + // Check if any keywords are in the diff + for (const keyword of keywords) { + if (fileDiff.includes(keyword)) { + console.log(`Found ${keyword} in ${file}`); + filesWithChanges.add(file); + hasPluginExtensionChanges = true; + break; // Found at least one keyword, move to next file + } + } + } catch (error) { + console.log(`Error checking file ${file}:`, error.message); + } + } + + // Set outputs + const filesArray = Array.from(filesWithChanges); + const formattedFiles = filesArray.length > 0 + ? '`' + filesArray.join('`\\n- `') + '`' + : ''; + + core.setOutput('plugin_extension_changes', hasPluginExtensionChanges.toString()); + core.setOutput('formatted_changed_files', formattedFiles); + + if (hasPluginExtensionChanges) { + console.log('The following files have changes that may affect plugin extensions:'); + console.log(filesArray); + } else { + console.log('No changes detected in core Grafana extensions or extension points.'); + } + + - name: Send Slack Message via Payload + id: slack + if: steps.check-changes.outputs.plugin_extension_changes == 'true' + uses: grafana/shared-workflows/actions/send-slack-message@0941e3408fa4789fec9062c44a2a9e1832146ba6 #v2.0.1 + with: + method: chat.postMessage + payload-templated: true + payload: | + { + "channel": "C031SLFH6G0", + "text": "Plugin Extension changes in core Grafana *PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} :information_source:", + "icon_emoji": ":grot:", + "username": "Plugin Extension Bot", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Plugin Extensions:* possible changes to extension points in core Grafana." + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>\n*Job:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Job>" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*File(s) with changes:*\n- ${{ steps.check-changes.outputs.formatted_changed_files }}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*What to do?*\nMake sure that:\n- All extension point ids start with `grafana/`\n- All extension point ids are exposed via \n- Core Grafana is not registering extensions to extension points offered by plugins" + } + } + ] + } \ No newline at end of file