diff --git a/.github/workflows/delete-test-seed.yml b/.github/workflows/delete-test-seed.yml new file mode 100644 index 00000000..00a371c2 --- /dev/null +++ b/.github/workflows/delete-test-seed.yml @@ -0,0 +1,25 @@ +name: Delete Test Seed + +on: + pull_request: + types: [closed] + paths: + - 'studies/**' + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + env: + REMOTE_SEED_PATH: 'pull/${{ github.event.pull_request.number }}/seed' + + steps: + - name: Delete seed + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + run: | + aws s3 rm "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" + aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/$REMOTE_SEED_PATH" diff --git a/.github/workflows/generate-test-seed.yml b/.github/workflows/generate-test-seed.yml new file mode 100644 index 00000000..6a659344 --- /dev/null +++ b/.github/workflows/generate-test-seed.yml @@ -0,0 +1,103 @@ +name: Generate Test Seed + +on: + pull_request: + paths: + - 'studies/**' + +jobs: + build: + runs-on: ubuntu-latest + env: + REMOTE_SEED_PATH: 'pull/${{ github.event.pull_request.number }}/seed' + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + fetch-depth: 0 + + - name: Comment "Generation In Progress" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const commentBody = + `## 🔄 Generating Test Seed + + A new test seed file is currently being generated for this pull request. + + ### What's Next? + + - The generation process typically takes a few minutes. + - Once the generation is complete, this comment will provide further instructions. + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) + + - name: Install + run: | + npm ci + + - name: Build & Test + run: | + npm run typecheck:scripts + npm run build:proto + npm run typecheck + npm run test + + - name: Lint + run: | + npm run lint -- --base origin/${{ github.event.pull_request.base.ref }} + + - name: Generate seed + run: | + npm run seed_tools -- create_seed studies seed.bin + + - name: Upload seed + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + run: | + gzip -c seed.bin | aws s3 cp - "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" \ + --content-type application/octet-stream \ + --content-encoding gzip + + INVALIDATION_ID=$(aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/$REMOTE_SEED_PATH" --query 'Invalidation.Id' --output text) + aws cloudfront wait invalidation-completed --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --id "$INVALIDATION_ID" + + - name: Comment "Generation Successful" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + const variationsServerURL = `https://griffin.brave.com/${process.env.REMOTE_SEED_PATH}`; + const serialNumberContent = fs.readFileSync('serialnumber', 'utf8'); + const commentBody = + `## 🚀 Test Seed Generated + + To test the new seed, launch the browser with the following command line: + \`\`\` + --accept-empty-variations-seed-signature --variations-server-url=${variationsServerURL} + \`\`\` + + #### Seed Details + - Serial Number: \`${serialNumberContent}\` + - Uploaded: \`${new Date().toISOString()}\` + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) + + - name: Comment "Generation Failed" + if: failure() + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const actionRunURL = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const commentBody = + `## ❌ Test Seed Generation Failed + + [Review the workflow logs for more information.](${actionRunURL}) + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) diff --git a/.github/workflows/scripts/comment.js b/.github/workflows/scripts/comment.js new file mode 100644 index 00000000..f1b03c7d --- /dev/null +++ b/.github/workflows/scripts/comment.js @@ -0,0 +1,32 @@ +// Creates or updates a single comment which is looked up by the workflow name. + +module.exports = async (github, context, commentBody) => { + const uniqueCommentTag = ``; + commentBody = `${commentBody}\n${uniqueCommentTag}`; + + const comments = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + const existingComment = comments.data.find((comment) => + comment.body.includes(uniqueCommentTag), + ); + + if (existingComment) { + await github.rest.issues.updateComment({ + comment_id: existingComment.id, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody, + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody, + }); + } +}; diff --git a/src/seed_tools/commands/create_seed.ts b/src/seed_tools/commands/create_seed.ts index d00da3fe..0b6c5bfa 100644 --- a/src/seed_tools/commands/create_seed.ts +++ b/src/seed_tools/commands/create_seed.ts @@ -24,6 +24,7 @@ export default new Command('create_seed') .option( '--serial_number_path ', 'file path to write the serial number to', + './serialnumber', ) .option('--mock_serial_number ', 'mock serial number') .action(main); diff --git a/src/seed_tools/utils/diff_strings.test.ts b/src/seed_tools/utils/diff_strings.test.ts index 3406c86f..f19f5f15 100644 --- a/src/seed_tools/utils/diff_strings.test.ts +++ b/src/seed_tools/utils/diff_strings.test.ts @@ -9,8 +9,8 @@ describe('diffStrings', () => { it('should return the diff between two strings', async () => { const string1 = 'Hello, world!'; const string2 = 'Hello, brave!'; - const displayFileName1 = 'file1.txt'; - const displayFileName2 = 'file2.txt'; + const displayFileName1 = 'file1-test.txt'; + const displayFileName2 = 'file2-test.txt'; const result = await diffStrings( string1, @@ -19,7 +19,25 @@ describe('diffStrings', () => { displayFileName2, ); + expect(result).toContain(displayFileName1); + expect(result).toContain(displayFileName2); expect(result).toContain('-Hello, world!'); expect(result).toContain('+Hello, brave!'); }); + + it('should return empty diff between two equal strings', async () => { + const string1 = 'Hello, brave!'; + const string2 = 'Hello, brave!'; + const displayFileName1 = 'file1-test.txt'; + const displayFileName2 = 'file2-test.txt'; + + const result = await diffStrings( + string1, + string2, + displayFileName1, + displayFileName2, + ); + + expect(result).toBe(''); + }); }); diff --git a/src/seed_tools/utils/diff_strings.ts b/src/seed_tools/utils/diff_strings.ts index df425f72..1c9c311b 100644 --- a/src/seed_tools/utils/diff_strings.ts +++ b/src/seed_tools/utils/diff_strings.ts @@ -19,11 +19,12 @@ export default async function diffStrings( ): Promise { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'diffString-')); + const dateStr = Date.now().toString(); const tmpFile1 = path - .join(tmpDir, `file1-${Date.now()}.txt`) + .join(tmpDir, `file1-${dateStr}.txt`) .replaceAll('\\', '/'); const tmpFile2 = path - .join(tmpDir, `file2-${Date.now()}.txt`) + .join(tmpDir, `file2-${dateStr}.txt`) .replaceAll('\\', '/'); // Write strings to temporary files. @@ -39,11 +40,13 @@ export default async function diffStrings( ); return ''; } catch (error) { + // Handle the case where git diff returns 1 due to differences. if (error.code === 1) { - // Handle the case where git diff returns 1 due to differences. + // Remove root forward slashes from the temporary file paths as git diff + // does not include them. const result = error.stdout - .replaceAll(tmpFile1, displayFileName1) - .replaceAll(tmpFile2, displayFileName2); + .replaceAll(tmpFile1.replace(/^\//, ''), displayFileName1) + .replaceAll(tmpFile2.replace(/^\//, ''), displayFileName2); return result; } else { diff --git a/studies/AdBlockComponentUpdateIntervalStudy.json b/studies/AdBlockComponentUpdateIntervalStudy.json new file mode 100644 index 00000000..d45848a4 --- /dev/null +++ b/studies/AdBlockComponentUpdateIntervalStudy.json @@ -0,0 +1,36 @@ +[ + { + "name": "AdBlockComponentUpdateIntervalStudy", + "experiment": [ + { + "name": "Enabled", + "probability_weight": 100, + "feature_association": { + "enable_feature": [ + "AdBlockDefaultResourceUpdateInterval", + "AllowCertainClientHints" + ] + }, + "param": [ + { + "name": "update_interval_mins", + "value": "1" + } + ] + } + ], + "filter": { + "channel": [ + "NIGHTLY", + "BETA", + "RELEASE" + ], + "platform": [ + "PLATFORM_WINDOWS", + "PLATFORM_MAC", + "PLATFORM_LINUX", + "PLATFORM_ANDROID" + ] + } + } +] diff --git a/studies/AllowCertainClientHintsStudy.json b/studies/AllowCertainClientHintsStudy.json new file mode 100644 index 00000000..7e3d8ad9 --- /dev/null +++ b/studies/AllowCertainClientHintsStudy.json @@ -0,0 +1,34 @@ +[ + { + "name": "AllowCertainClientHintsStudy", + "experiment": [ + { + "name": "Enabled", + "probability_weight": 100, + "feature_association": { + "enable_feature": [ + "AllowCertainClientHints" + ] + } + }, + { + "name": "Default", + "probability_weight": 0 + } + ], + "filter": { + "min_version": "104.1.44.59", + "channel": [ + "RELEASE", + "BETA", + "NIGHTLY" + ], + "platform": [ + "PLATFORM_WINDOWS", + "PLATFORM_MAC", + "PLATFORM_LINUX", + "PLATFORM_ANDROID" + ] + } + } +]