1+ /*
2+ * Licensed to Elasticsearch B.V. under one or more contributor
3+ * license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright
5+ * ownership. Elasticsearch B.V. licenses this file to you under
6+ * the Apache License, Version 2.0 (the "License"); you may
7+ * not use this file except in compliance with the License.
8+ * You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing,
13+ * software distributed under the License is distributed on an
14+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+ * KIND, either express or implied. See the License for the
16+ * specific language governing permissions and limitations
17+ * under the License.
18+ */
19+
20+ import { readFile } from 'fs/promises'
21+ import * as core from '@actions/core'
22+
23+ const tick = '`'
24+
25+ async function run ( ) {
26+ // Parse command line arguments
27+ const [ baselineReportPath , prReportPath ] = process . argv . slice ( 2 )
28+
29+ if ( ! baselineReportPath || ! prReportPath ) {
30+ console . error ( 'Usage: node compare-reports.js <baseline-report.json> <pr-report.json>' )
31+ process . exit ( 1 )
32+ }
33+
34+ // Load both validation reports
35+ const baselineReports = JSON . parse ( await readFile ( baselineReportPath , 'utf-8' ) )
36+ const prReports = JSON . parse ( await readFile ( prReportPath , 'utf-8' ) )
37+
38+ // Compare reports and find changes
39+ const changedApis = [ ]
40+
41+ // Get all API names from both reports
42+ const allApiNames = new Set ( [
43+ ...Object . keys ( baselineReports ) ,
44+ ...Object . keys ( prReports )
45+ ] )
46+
47+ for ( const apiName of allApiNames ) {
48+ const baselineReport = baselineReports [ apiName ]
49+ const prReport = prReports [ apiName ]
50+
51+ // If API exists in PR but not baseline, or vice versa, or has changes
52+ if ( hasChanges ( baselineReport , prReport ) ) {
53+ changedApis . push ( {
54+ api : apiName ,
55+ baseline : baselineReport || null ,
56+ current : prReport || null
57+ } )
58+ }
59+ }
60+
61+ changedApis . sort ( ( a , b ) => a . api . localeCompare ( b . api ) )
62+
63+ // Build PR comment
64+ let comment = `Following you can find the validation changes against the target branch for the API${ changedApis . length === 1 ? '' : 's' } .\n\n`
65+ if ( changedApis . length > 0 ) {
66+ comment += '| API | Status | Request | Response |\n'
67+ comment += '| --- | --- | --- | --- |\n'
68+ for ( const change of changedApis ) {
69+ comment += buildDiffTableLine ( change )
70+ }
71+ } else {
72+ comment += '**No changes detected**.\n'
73+ }
74+ comment += `\nYou can validate ${ changedApis . length === 1 ? 'this' : 'these' } API${ changedApis . length === 1 ? '' : 's' } yourself by using the ${ tick } make validate${ tick } target.\n`
75+
76+ core . setOutput ( 'comment_body' , comment )
77+ core . info ( 'Done!' )
78+ }
79+
80+ function hasChanges ( baselineReport , prReport ) {
81+ // If one exists and the other doesn't, that's a change
82+ if ( ! baselineReport && prReport ) return true
83+ if ( baselineReport && ! prReport ) return true
84+ if ( ! baselineReport && ! prReport ) return false
85+
86+ return baselineReport . status !== prReport . status ||
87+ baselineReport . passingRequest !== prReport . passingRequest ||
88+ baselineReport . passingResponse !== prReport . passingResponse
89+ }
90+
91+ function buildDiffTableLine ( change ) {
92+ const { api, baseline, current } = change
93+
94+ // Handle cases where API exists in only one report
95+ if ( ! baseline && current ) {
96+ // New API in PR
97+ const status = generateStatus ( current . status )
98+ const request = generateRequest ( current )
99+ const response = generateResponse ( current )
100+ return `| ${ tick } ${ api } ${ tick } | ➕ ${ status } | ${ request } | ${ response } |\n`
101+ }
102+
103+ if ( baseline && ! current ) {
104+ // API removed in PR
105+ const status = generateStatus ( baseline . status )
106+ const request = generateRequest ( baseline )
107+ const response = generateResponse ( baseline )
108+ return `| ${ tick } ${ api } ${ tick } | ➖ ${ status } | ${ request } | ${ response } |\n`
109+ }
110+
111+ // Both exist - show changes
112+ const status = generateStatus ( current . status )
113+ const request = generateRequest ( current )
114+ const response = generateResponse ( current )
115+
116+ const baselineStatus = generateStatus ( baseline . status )
117+ const baselineRequest = generateRequest ( baseline )
118+ const baselineResponse = generateResponse ( baseline )
119+
120+ const statusDiff = status !== baselineStatus ? `${ baselineStatus } → ${ status } ` : status
121+ const requestDiff = request !== baselineRequest ? `${ baselineRequest } → ${ request } ` : request
122+ const responseDiff = response !== baselineResponse ? `${ baselineResponse } → ${ response } ` : response
123+
124+ return `| ${ tick } ${ api } ${ tick } | ${ statusDiff } | ${ requestDiff } | ${ responseDiff } |\n`
125+ }
126+
127+ function generateStatus ( status ) {
128+ if ( status === 'missing_types' || status === 'missing_request_type' || status === 'missing_response_type' ) {
129+ return ':orange_circle:'
130+ }
131+ if ( status === 'missing_test' ) {
132+ return ':white_circle:'
133+ }
134+ if ( status === 'passing' ) {
135+ return ':green_circle:'
136+ }
137+ return ':red_circle:'
138+ }
139+
140+ function generateRequest ( r ) {
141+ if ( r . status === 'missing_test' ) return 'Missing test'
142+ if ( r . status === 'missing_types' || r . status == 'missing_request_type' ) return 'Missing type'
143+ return `${ r . passingRequest } /${ r . totalRequest } `
144+ }
145+
146+ function generateResponse ( r ) {
147+ if ( r . status === 'missing_test' ) return 'Missing test'
148+ if ( r . status === 'missing_types' || r . status == 'missing_response_type' ) return 'Missing type'
149+ return `${ r . passingResponse } /${ r . totalResponse } `
150+ }
151+
152+ run ( ) . catch ( ( err ) => {
153+ core . error ( err )
154+ process . exit ( 1 )
155+ } )
0 commit comments