-
Notifications
You must be signed in to change notification settings - Fork 70
Add deployment summary to the job summary and pull request comment #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chhateauuu
wants to merge
11
commits into
master
Choose a base branch
from
t-achhatkuli/dev/pr-deploy-summary
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,995
−26
Open
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
b154754
Add fast-xml-parser and @actions/github dependencies
chhateauuu 23b84ad
Add SqlPackage deployment report parser
chhateauuu d519de1
Add deployment summary Markdown renderer
chhateauuu f8e943b
Capture deployment report and script during publish
chhateauuu 0fe6e98
Add reporter to publish job summary and PR comment
chhateauuu 910780c
Publish deployment summary after a successful deploy
chhateauuu 183d2e5
Document summary inputs and outputs
chhateauuu 616976b
Rebuild bundle with deployment summary feature
chhateauuu e3f615d
Strip byte order mark from the embedded deployment script
chhateauuu 9843dd1
Escape pipe characters in deployment summary table cells
chhateauuu c25d594
Address PR review feedback
chhateauuu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <DeploymentReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DeployReport/2012/02"> | ||
| <Alerts> | ||
| <Alert Name="DataIssue"> | ||
| <Issue Value="[dbo].[OldAudit] will be dropped, which may result in data loss." /> | ||
| </Alert> | ||
| </Alerts> | ||
| <Operations> | ||
| <Operation Name="Create"> | ||
| <Item Value="[dbo].[Reactions]" Type="SqlTable" /> | ||
| <Item Value="[dbo].[GetUserMentions]" Type="SqlProcedure" /> | ||
| </Operation> | ||
| <Operation Name="Alter"> | ||
| <Item Value="[dbo].[Messages]" Type="SqlTable" /> | ||
| </Operation> | ||
| <Operation Name="Drop"> | ||
| <Item Value="[dbo].[OldAudit]" Type="SqlTable" /> | ||
| </Operation> | ||
| </Operations> | ||
| </DeploymentReport> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import { parseDeploymentReport } from '../src/DeploymentReport'; | ||
|
|
||
| describe('DeploymentReport tests', () => { | ||
|
|
||
| const fixture = fs.readFileSync(path.join(__dirname, '..', '__testdata__', 'deployReport.xml'), 'utf8'); | ||
|
|
||
| it('parses every create, alter, and drop operation', () => { | ||
| const report = parseDeploymentReport(fixture); | ||
| expect(report.operations).toHaveLength(4); | ||
| }); | ||
|
|
||
| it('captures the operation name, object, and friendly type', () => { | ||
| const report = parseDeploymentReport(fixture); | ||
| expect(report.operations).toContainEqual({ operation: 'Create', object: '[dbo].[Reactions]', type: 'Table' }); | ||
| expect(report.operations).toContainEqual({ operation: 'Create', object: '[dbo].[GetUserMentions]', type: 'Procedure' }); | ||
| expect(report.operations).toContainEqual({ operation: 'Alter', object: '[dbo].[Messages]', type: 'Table' }); | ||
| expect(report.operations).toContainEqual({ operation: 'Drop', object: '[dbo].[OldAudit]', type: 'Table' }); | ||
| }); | ||
|
|
||
| it('parses data-loss alerts', () => { | ||
| const report = parseDeploymentReport(fixture); | ||
| expect(report.alerts).toHaveLength(1); | ||
| expect(report.alerts[0].kind).toBe('DataIssue'); | ||
| expect(report.alerts[0].detail).toContain('[dbo].[OldAudit]'); | ||
| }); | ||
|
|
||
| it('handles a report with a single operation that is not an array', () => { | ||
| const xml = `<?xml version="1.0"?><DeploymentReport><Operations><Operation Name="Create"><Item Value="[dbo].[Only]" Type="SqlTable" /></Operation></Operations></DeploymentReport>`; | ||
| const report = parseDeploymentReport(xml); | ||
| expect(report.operations).toEqual([{ operation: 'Create', object: '[dbo].[Only]', type: 'Table' }]); | ||
| }); | ||
|
|
||
| it('returns an empty report when there are no operations', () => { | ||
| const xml = `<?xml version="1.0"?><DeploymentReport></DeploymentReport>`; | ||
| const report = parseDeploymentReport(xml); | ||
| expect(report.operations).toHaveLength(0); | ||
| expect(report.alerts).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('returns an empty report for blank input', () => { | ||
| const report = parseDeploymentReport(' '); | ||
| expect(report.operations).toHaveLength(0); | ||
| expect(report.alerts).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('returns an empty report for unparseable input', () => { | ||
| const report = parseDeploymentReport('this is not xml <<<'); | ||
| expect(report.operations).toHaveLength(0); | ||
| expect(report.alerts).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('expands a multi-word type name into a friendly label', () => { | ||
| const xml = `<?xml version="1.0"?><DeploymentReport><Operations><Operation Name="Create"><Item Value="[dbo].[PK]" Type="SqlPrimaryKeyConstraint" /></Operation></Operations></DeploymentReport>`; | ||
| const report = parseDeploymentReport(xml); | ||
| expect(report.operations[0].type).toBe('Primary Key Constraint'); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| import { buildSummary, SummaryContext, SUMMARY_MARKER } from '../src/DeploymentSummary'; | ||
| import { DeploymentReport } from '../src/DeploymentReport'; | ||
|
|
||
| describe('DeploymentSummary tests', () => { | ||
|
|
||
| const baseContext: SummaryContext = { | ||
| action: 'Publish', | ||
| source: 'Database.dacpac', | ||
| server: 'myserver.database.windows.net', | ||
| database: 'mydb' | ||
| }; | ||
|
|
||
| const report: DeploymentReport = { | ||
| operations: [ | ||
| { operation: 'Create', object: '[dbo].[Reactions]', type: 'Table' }, | ||
| { operation: 'Alter', object: '[dbo].[Messages]', type: 'Table' }, | ||
| { operation: 'Drop', object: '[dbo].[OldAudit]', type: 'Table' } | ||
| ], | ||
| alerts: [ | ||
| { kind: 'DataIssue', detail: '[dbo].[OldAudit] will be dropped.' } | ||
| ] | ||
| }; | ||
|
|
||
| it('renders a headline with the action, source, and redacted target', () => { | ||
| const markdown = buildSummary(baseContext); | ||
| expect(markdown).toContain('✅ **Published** `Database.dacpac` → `myserver.database.windows.net / mydb`'); | ||
| }); | ||
|
|
||
| it('summarizes the change counts and data loss in the headline', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown).toContain('**3 changes** (1 created · 1 altered · 1 dropped) · ⚠️ 1 data-loss warning'); | ||
| }); | ||
|
|
||
| it('renders the metadata table', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown).toContain('| **Action** | Publish |'); | ||
| expect(markdown).toContain('| **Target** | `myserver.database.windows.net / mydb` |'); | ||
| }); | ||
|
|
||
| it('renders the duration when provided', () => { | ||
| const markdown = buildSummary({ ...baseContext, report, durationMs: 3200 }); | ||
| expect(markdown).toContain('| **Duration** | 3.2s |'); | ||
| }); | ||
|
|
||
| it('renders the triggering user and commit when provided', () => { | ||
| const markdown = buildSummary({ ...baseContext, report, actor: 'octocat', commit: 'abc1234' }); | ||
| expect(markdown).toContain('| **Triggered by** | @octocat |'); | ||
| expect(markdown).toContain('| **Commit** | `abc1234` |'); | ||
| }); | ||
|
|
||
| it('renders the deployment options when provided', () => { | ||
| const markdown = buildSummary({ ...baseContext, report, options: '/p:DropObjectsNotInSource=true' }); | ||
| expect(markdown).toContain('| **Options** | `/p:DropObjectsNotInSource=true` |'); | ||
| }); | ||
|
|
||
| it('renders the by-type and by-schema breakdowns', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown).toContain('**By type:** Table ×3'); | ||
| expect(markdown).toContain('**By schema:** dbo ×3'); | ||
| }); | ||
|
|
||
| it('renders a collapsible section per change category', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown).toContain('<summary>➕ Created (1)</summary>'); | ||
| expect(markdown).toContain('<summary>🔄 Altered (1)</summary>'); | ||
| expect(markdown).toContain('<summary>🗑️ Dropped (1)</summary>'); | ||
| expect(markdown).toContain('| `[dbo].[Reactions]` | Table |'); | ||
| }); | ||
|
|
||
| it('escapes pipe characters in object names so the table is not broken', () => { | ||
| const markdown = buildSummary({ ...baseContext, report: { operations: [{ operation: 'Create', object: '[dbo].[Odd|Name]', type: 'Table' }], alerts: [] } }); | ||
| expect(markdown).toContain('[dbo].[Odd\\|Name]'); | ||
| }); | ||
|
|
||
| it('renders a data-loss callout when alerts are present', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown).toContain('### ⚠️ Possible data loss (1)'); | ||
| expect(markdown).toContain('> - [dbo].[OldAudit] will be dropped.'); | ||
| }); | ||
|
|
||
| it('omits the data-loss callout when there are no alerts', () => { | ||
| const markdown = buildSummary({ ...baseContext, report: { operations: report.operations, alerts: [] } }); | ||
| expect(markdown).not.toContain('Possible data loss'); | ||
| }); | ||
|
|
||
| it('renders a no-changes message for an empty report', () => { | ||
| const markdown = buildSummary({ ...baseContext, report: { operations: [], alerts: [] } }); | ||
| expect(markdown).toContain('no schema changes'); | ||
| expect(markdown).toContain('### 📦 Changes'); | ||
| }); | ||
|
|
||
| it('truncates a section beyond the row limit', () => { | ||
| const operations = Array.from({ length: 150 }, (_, index) => ({ operation: 'Create', object: `[dbo].[Table${index}]`, type: 'Table' })); | ||
| const markdown = buildSummary({ ...baseContext, report: { operations, alerts: [] } }); | ||
| expect(markdown).toContain('_…and 50 more_'); | ||
| }); | ||
|
|
||
| it('embeds the deployment script in a collapsible block with batch and size counts', () => { | ||
| const markdown = buildSummary({ ...baseContext, script: 'CREATE TABLE [dbo].[Reactions] (Id INT);\nGO' }); | ||
| expect(markdown).toContain('📄 Deployment T-SQL script · 1 batch ·'); | ||
| expect(markdown).toContain('CREATE TABLE [dbo].[Reactions] (Id INT);'); | ||
| }); | ||
|
|
||
| it('truncates a very large deployment script', () => { | ||
| const markdown = buildSummary({ ...baseContext, script: 'A'.repeat(40000) }); | ||
| expect(markdown).toContain('Script truncated for display'); | ||
| }); | ||
|
|
||
| it('strips a leading byte order mark from the script', () => { | ||
| const markdown = buildSummary({ ...baseContext, script: '\uFEFFCREATE TABLE [dbo].[Reactions] (Id INT);' }); | ||
| expect(markdown).not.toContain('\uFEFF'); | ||
| expect(markdown).toContain('CREATE TABLE [dbo].[Reactions] (Id INT);'); | ||
| }); | ||
|
|
||
| it('renders a run link in the footer when provided', () => { | ||
| const markdown = buildSummary({ ...baseContext, report, runUrl: 'https://github.com/o/r/actions/runs/1' }); | ||
| expect(markdown).toContain('Generated by <b>Azure SQL Deploy</b>'); | ||
| expect(markdown).toContain('<a href="https://github.com/o/r/actions/runs/1">View run</a>'); | ||
| }); | ||
|
|
||
| it('ends with the sticky comment marker', () => { | ||
| const markdown = buildSummary({ ...baseContext, report }); | ||
| expect(markdown.replace(/\s+$/, '').endsWith(SUMMARY_MARKER)).toBe(true); | ||
| }); | ||
|
|
||
| it('never includes a password even if one is present in the context object', () => { | ||
| const markdown = buildSummary({ ...baseContext, report, options: '/p:DropObjectsNotInSource=true' }); | ||
| expect(markdown).not.toContain('Password'); | ||
| expect(markdown).not.toContain('placeholder'); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.