Skip to content

Commit 98687ac

Browse files
committed
refactor: simplify comparison logic
1 parent bd38e7d commit 98687ac

File tree

7 files changed

+166
-152
lines changed

7 files changed

+166
-152
lines changed

src/apitypes/graphql/graphql.changes.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,23 @@ import { buildGraphQLDocument } from './graphql.document'
2323
import {
2424
CompareOperationsPairContext,
2525
FILE_KIND,
26-
NormalizedOperationId,
2726
OperationChanges,
28-
OperationsApiType,
29-
ResolvedOperation,
3027
ResolvedVersionDocument,
3128
WithAggregatedDiffs,
3229
WithDiffMetaRecord,
3330
} from '../../types'
3431
import { GRAPHQL_TYPE, GRAPHQL_TYPE_KEYS } from './graphql.consts'
35-
import { createOperationChange, getOperationTags } from '../../components'
32+
import { createOperationChange, getOperationTags, OperationsMap } from '../../components'
3633

37-
export const compareDocuments = async (apiType: OperationsApiType, operationsMap: Record<NormalizedOperationId, {
38-
previous?: ResolvedOperation
39-
current?: ResolvedOperation
40-
}>, prevDoc: ResolvedVersionDocument | undefined, currDoc: ResolvedVersionDocument | undefined, ctx: CompareOperationsPairContext): Promise<{
34+
export const compareDocuments = async (
35+
operationsMap: OperationsMap,
36+
prevDoc: ResolvedVersionDocument | undefined,
37+
currDoc: ResolvedVersionDocument | undefined,
38+
ctx: CompareOperationsPairContext): Promise<{
4139
operationChanges: OperationChanges[]
4240
tags: string[]
4341
}> => {
44-
const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
42+
const { apiType, rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
4543
const prevFile = prevDoc && await rawDocumentResolver(previousVersion, previousPackageId, prevDoc.slug)
4644
const currFile = currDoc && await rawDocumentResolver(currentVersion, currentPackageId, currDoc.slug)
4745
const prevDocSchema = prevFile && buildSchema(await prevFile.text(), { noLocation: true })
@@ -78,7 +76,7 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
7876
// mode: COMPARE_MODE_OPERATION,
7977
normalizedResult: true,
8078
},
81-
) as {merged: GraphApiSchema; diffs: Diff[]}
79+
) as { merged: GraphApiSchema; diffs: Diff[] }
8280

8381
if (isEmpty(diffs)) {
8482
return { operationChanges: [], tags: [] }

src/apitypes/rest/rest.changes.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { RestOperationData } from './rest.types'
1818
import {
1919
areDeprecatedOriginsNotEmpty,
20+
convertToSlug,
2021
IGNORE_PATH_PARAM_UNIFIED_PLACEHOLDER,
2122
isEmpty,
2223
isOperationRemove,
@@ -38,10 +39,7 @@ import { MESSAGE_SEVERITY, NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../const
3839
import {
3940
BREAKING_CHANGE_TYPE,
4041
CompareOperationsPairContext,
41-
NormalizedOperationId,
4242
OperationChanges,
43-
OperationsApiType,
44-
ResolvedOperation,
4543
ResolvedVersionDocument,
4644
RISKY_CHANGE_TYPE,
4745
WithAggregatedDiffs,
@@ -59,16 +57,27 @@ import { calculateObjectHash } from '../../utils/hashes'
5957
import { REST_API_TYPE } from './rest.consts'
6058
import { OpenAPIV3 } from 'openapi-types'
6159
import { extractServersDiffs, getOperationBasePath } from './rest.utils'
62-
import { createOperationChange, getOperationTags } from '../../components'
60+
import { createOperationChange, getOperationTags, OperationsMap } from '../../components'
6361

64-
export const compareDocuments = async (apiType: OperationsApiType, operationsMap: Record<NormalizedOperationId, {
65-
previous?: ResolvedOperation
66-
current?: ResolvedOperation
67-
}>, prevDoc: ResolvedVersionDocument | undefined, currDoc: ResolvedVersionDocument | undefined, ctx: CompareOperationsPairContext): Promise<{
62+
export const compareDocuments = async (
63+
operationsMap: OperationsMap,
64+
prevDoc: ResolvedVersionDocument | undefined,
65+
currDoc: ResolvedVersionDocument | undefined,
66+
ctx: CompareOperationsPairContext,
67+
): Promise<{
6868
operationChanges: OperationChanges[]
6969
tags: string[]
7070
}> => {
71-
const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId, currentGroup, previousGroup } = ctx
71+
const {
72+
apiType,
73+
rawDocumentResolver,
74+
previousVersion,
75+
currentVersion,
76+
previousPackageId,
77+
currentPackageId,
78+
currentGroup,
79+
previousGroup,
80+
} = ctx
7281
const prevFile = prevDoc && await rawDocumentResolver(previousVersion, previousPackageId, prevDoc.slug)
7382
const currFile = currDoc && await rawDocumentResolver(currentVersion, currentPackageId, currDoc.slug)
7483
let prevDocData = prevFile && JSON.parse(await prevFile.text())
@@ -108,8 +117,8 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
108117
return { operationChanges: [], tags: [] }
109118
}
110119

111-
const currGroupSlug = slugify(removeFirstSlash(currentGroup || ''))
112-
const prevGroupSlug = slugify(removeFirstSlash(previousGroup || ''))
120+
const currGroupSlug = convertToSlug(currentGroup || '')
121+
const prevGroupSlug = convertToSlug(previousGroup || '')
113122

114123
const tags = new Set<string>()
115124
const changedOperations: OperationChanges[] = []
@@ -127,6 +136,7 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
127136
}
128137

129138
const methodData = pathData[inferredMethod]
139+
// todo if there were actually servers here, we wouldn't have handle it, add a test
130140
const basePath = getOperationBasePath(methodData?.servers || pathData?.servers || merged.servers || [])
131141
const operationPath = basePath + path
132142
const operationId = slugify(`${operationPath}-${inferredMethod}`)

src/builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ export class PackageVersionBuilder implements IPackageVersionBuilder {
475475
apiType?: OperationsApiType,
476476
): Promise<ResolvedVersionDocuments | null> {
477477
packageId = packageId ?? this.config.packageId
478-
// this is a case when the version has been built just now, and there's nothing to fetch yet, so
478+
// this is the case when a version has been built just now, and there's nothing to fetch yet, so
479479
// the only way to get the docs is to get them from buildResult, but the referenced packages map will be empty (packages: {})
480480
if (this.canBeResolvedLocally(version, packageId)) {
481481
const apiBuilder = this.apiBuilders.find(apiBuilder => apiBuilder.apiType === apiType)

src/components/compare/compare.operations.ts

Lines changed: 23 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,28 @@ import {
1818
CompareContext,
1919
CompareOperationsPairContext,
2020
OperationChanges,
21-
OperationId,
2221
OperationsApiType,
2322
OperationType,
24-
ResolvedVersionDocument,
2523
VersionCache,
2624
VersionParams,
2725
VersionsComparison,
2826
} from '../../types'
2927
import {
28+
calculatePairedDocs,
3029
calculateTotalImpactedSummary,
30+
comparePairedDocs,
3131
createPairOperationsMap,
32-
dedupePairs,
3332
getUniqueApiTypesFromVersions,
34-
normalizeOperationIds,
3533
} from './compare.utils'
3634
import {
3735
calculateApiAudienceTransitions,
3836
calculateChangeSummary,
3937
calculateDiffId,
4038
convertToSlug,
41-
difference,
42-
executeInBatches,
4339
getSplittedVersionKey,
44-
intersection,
45-
removeFirstSlash,
4640
removeObjectDuplicates,
47-
slugify,
4841
} from '../../utils'
49-
import { asyncDebugPerformance, DebugPerformanceContext } from '../../utils/logs'
42+
import { asyncDebugPerformance, DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs'
5043

5144
export async function compareVersionsOperations(
5245
prev: VersionParams,
@@ -109,125 +102,59 @@ async function compareCurrentApiType(
109102
debugCtx?: DebugPerformanceContext,
110103
): Promise<[OperationType, OperationChanges[]] | null> {
111104
const {
112-
batchSize,
113105
versionOperationsResolver,
114-
versionDocumentsResolver,
115106
rawDocumentResolver,
116107
config: { currentGroup = '', previousGroup = '' },
117108
} = ctx
118109
const apiBuilder = ctx.apiBuilders.find((builder) => apiType === builder.apiType)
119110
if (!apiBuilder) { return null }
120111

121-
const { operations: prevOperations = [] } = await versionOperationsResolver(apiType, prev?.version ?? '', prev?.packageId ?? '', undefined, false) || {}
122-
const { operations: currOperations = [] } = await versionOperationsResolver(apiType, curr?.version ?? '', curr?.packageId ?? '', undefined, false) || {}
123-
124-
const previousGroupSlug = convertToSlug(previousGroup)
125-
const prevOperationsWithPrefix = previousGroupSlug ? prevOperations.filter(operation => operation.operationId.startsWith(previousGroupSlug)) : prevOperations
126-
127-
const currentGroupSlug = convertToSlug(currentGroup)
128-
const currOperationsWithPrefix = currentGroupSlug ? currOperations.filter(operation => operation.operationId.startsWith(currentGroupSlug)) : currOperations
129-
130-
const [prevNormalizedOperationIds, prevNormalizedOperationIdToOperation] = normalizeOperationIds(prevOperationsWithPrefix, apiBuilder, previousGroup)
131-
const [currNormalizedOperationIds, currNormalizedOperationIdToOperation] = normalizeOperationIds(currOperationsWithPrefix, apiBuilder, currentGroup)
132-
133-
const added = difference(currNormalizedOperationIds, prevNormalizedOperationIds)
134-
const removed = difference(prevNormalizedOperationIds, currNormalizedOperationIds)
135-
const potentiallyChanged = intersection(prevNormalizedOperationIds, currNormalizedOperationIds)
136-
137112
const { version: prevVersion, packageId: prevPackageId } = prev ?? { version: '', packageId: '' }
138113
const { version: currVersion, packageId: currPackageId } = curr ?? { version: '', packageId: '' }
139114

140-
const { documents: prevDocuments } = await versionDocumentsResolver(prev?.version ?? '', prev?.packageId ?? '', apiType) ?? { documents: [] }
141-
const { documents: unfilteredCurrDocuments } = await versionDocumentsResolver(curr?.version ?? '', curr?.packageId ?? '', apiType) ?? { documents: [] }
142-
// todo this filters out files that are not in build config, but config.files is stored between tests and that breaks the '[Response] Should be non-breaking if response code changed in case' test
143-
// const currDocuments = ctx.config.files ? unfilteredCurrDocuments.filter(({fileId}) => ctx.config.files?.find((file) => file.fileId === fileId)) : unfilteredCurrDocuments
144-
const currDocuments = unfilteredCurrDocuments
145-
146-
const pairedRestDocs: [ResolvedVersionDocument, ResolvedVersionDocument][] = []
147-
for (const normalizedOperationId of potentiallyChanged) {
148-
const prevDocumentId = prevNormalizedOperationIdToOperation[normalizedOperationId]?.documentId
149-
const currDocumentId = currNormalizedOperationIdToOperation[normalizedOperationId]?.documentId
150-
const prevDoc = prevDocuments.find(document => document.slug === prevDocumentId)
151-
const currDoc = currDocuments.find(document => document.slug === currDocumentId)
152-
153-
if (!prevDoc || !currDoc) {
154-
throw new Error('should not happen')
155-
}
156-
157-
pairedRestDocs.push([prevDoc, currDoc])
158-
}
159-
160-
const pairedDocs: [ResolvedVersionDocument | undefined, ResolvedVersionDocument | undefined][] = dedupePairs(pairedRestDocs)
161-
const uniqueCurrentDocuments = [...new Set(added.map((normalizedOperationId) => {
162-
const currDocumentId = currNormalizedOperationIdToOperation[normalizedOperationId]?.documentId
163-
return currDocuments.find(document => document.slug === currDocumentId)
164-
}))]
165-
for (const currDoc of uniqueCurrentDocuments) {
166-
pairedDocs.push([undefined, currDoc])
167-
}
168-
169-
const asd = [...new Set(removed.map((normalizedOperationId) => {
170-
const prevDocumentId = prevNormalizedOperationIdToOperation[normalizedOperationId]?.documentId
171-
return prevDocuments.find(document => document.slug === prevDocumentId)
172-
}))]
173-
for (const prevDoc of asd) {
174-
pairedDocs.push([prevDoc, undefined])
175-
}
176-
177-
const currGroupSlug = slugify(removeFirstSlash(currentGroup || ''))
178-
const prevGroupSlug = slugify(removeFirstSlash(previousGroup || ''))
115+
const { operations: prevOperations = [] } = await versionOperationsResolver(apiType, prevVersion, prevPackageId, undefined, false) || {}
116+
const { operations: currOperations = [] } = await versionOperationsResolver(apiType, currVersion, currPackageId, undefined, false) || {}
179117

180-
const operationsMap = createPairOperationsMap(prevGroupSlug, currGroupSlug, prevOperationsWithPrefix, currOperationsWithPrefix, apiBuilder)
118+
const previousGroupSlug = convertToSlug(previousGroup)
119+
const currentGroupSlug = convertToSlug(currentGroup)
181120

182-
const operationChanges: OperationChanges[] = []
183-
const tags: string[] = []
121+
const prevOperationsWithPrefix = previousGroupSlug ? prevOperations.filter(operation => operation.operationId.startsWith(`${previousGroupSlug}-`)) : prevOperations
122+
const currOperationsWithPrefix = currentGroupSlug ? currOperations.filter(operation => operation.operationId.startsWith(`${currentGroupSlug}-`)) : currOperations
184123

185124
const pairContext: CompareOperationsPairContext = {
125+
apiType: apiType,
186126
notifications: ctx.notifications,
187127
rawDocumentResolver,
188128
versionDeprecatedResolver: ctx.versionDeprecatedResolver,
129+
versionDocumentsResolver: ctx.versionDocumentsResolver,
189130
previousVersion: prevVersion,
190131
currentVersion: currVersion,
191-
previousPackageId: prevPackageId,
192132
currentPackageId: currPackageId,
193-
currentGroup: currentGroup,
133+
previousPackageId: prevPackageId,
194134
previousGroup: previousGroup,
135+
currentGroup: currentGroup,
136+
previousGroupSlug: previousGroupSlug,
137+
currentGroupSlug: currentGroupSlug,
195138
}
196139

197-
for (const [prevDoc, currDoc] of pairedDocs) {
198-
const {
199-
operationChanges: docsPairOperationChanges,
200-
tags: docsPairTags,
201-
} = await apiBuilder.compareDocuments!(apiType, operationsMap, prevDoc, currDoc, pairContext)
202-
203-
operationChanges.push(...docsPairOperationChanges)
204-
tags.push(...docsPairTags)
205-
}
140+
const operationsMap = createPairOperationsMap(previousGroupSlug, currentGroupSlug, prevOperationsWithPrefix, currOperationsWithPrefix, apiBuilder)
141+
const operationPairs = Object.values(operationsMap)
142+
const pairedDocs = await calculatePairedDocs(operationPairs, pairContext)
143+
const [operationChanges, tags] = await comparePairedDocs(operationsMap, pairedDocs, apiBuilder, pairContext)
206144

207145
const dedupedChanges = removeObjectDuplicates(operationChanges.flatMap(({ diffs }) => diffs), calculateDiffId)
208146
const changesSummary = calculateChangeSummary(dedupedChanges)
209147
const numberOfImpactedOperations = calculateTotalImpactedSummary(
210148
operationChanges.map(({ impactedSummary }) => impactedSummary),
211149
)
212150

213-
const pairedOperationIds =
214-
Object.entries(operationsMap)
215-
.filter(([, { previous, current }]) => previous && current)
216-
.map(([, { previous, current }]) => [previous?.operationId, current?.operationId] as [OperationId, OperationId])
217-
218151
const apiAudienceTransitions: ApiAudienceTransition[] = []
219152
// todo: convert from objects analysis to apihub-diff result analysis after the "info" section participates in the comparison of operations
220-
await asyncDebugPerformance('[ApiAudience]', async () => await executeInBatches(pairedOperationIds, async (operationsBatch) => {
221-
const previousBatch = operationsBatch.map(([prevOperationId]) => prevOperationId)
222-
const currentBatch = operationsBatch.map(([, currOperationId]) => currOperationId)
223-
const { operations: currOperationsWithoutData = [] } = await versionOperationsResolver(apiType, currVersion, currPackageId, previousBatch, false) || {}
224-
const { operations: prevOperationsWithoutData = [] } = await versionOperationsResolver(apiType, prevVersion, prevPackageId, currentBatch, false) || {}
225-
226-
const pairOperationsMap = createPairOperationsMap(prevGroupSlug, currGroupSlug, prevOperationsWithoutData, currOperationsWithoutData, apiBuilder)
227-
Object.values(pairOperationsMap).forEach((pair) => {
228-
calculateApiAudienceTransitions(pair.current, pair.previous, apiAudienceTransitions)
153+
syncDebugPerformance('[ApiAudience]', () => {
154+
operationPairs.forEach(({current, previous}) => {
155+
calculateApiAudienceTransitions(current, previous, apiAudienceTransitions)
229156
})
230-
}, batchSize), debugCtx)
157+
}, debugCtx)
231158

232159
return [
233160
{

0 commit comments

Comments
 (0)