Skip to content

Commit 3f1f531

Browse files
committed
feat: implement documents matching based on operations
1 parent 6f03215 commit 3f1f531

File tree

18 files changed

+182
-41
lines changed

18 files changed

+182
-41
lines changed

src/apitypes/graphql/graphql.changes.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ import { isEmpty, removeComponents, removeFirstSlash, slugify, takeIf } from '..
1919
import {
2020
apiDiff,
2121
COMPARE_MODE_OPERATION,
22-
DEFAULT_DIFFS_AGGREGATED_META_KEY,
2322
Diff,
2423
DIFF_META_KEY,
24+
DIFFS_AGGREGATED_META_KEY,
2525
} from '@netcracker/qubership-apihub-api-diff'
2626
import { NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../consts'
2727
import { GraphApiOperation, GraphApiSchema } from '@netcracker/qubership-apihub-graphapi'
2828
import { buildSchema } from 'graphql/utilities'
2929
import { buildGraphQLDocument } from './graphql.document'
3030
import {
31-
CompareContext,
31+
CompareOperationsPairContext,
3232
FILE_KIND,
3333
NormalizedOperationId,
3434
OperationChanges,
@@ -44,34 +44,44 @@ import { createOperationChange, getOperationTags } from '../../components'
4444
export const compareDocuments = async (apiType: OperationsApiType, operationsMap: Record<NormalizedOperationId, {
4545
previous?: ResolvedOperation
4646
current?: ResolvedOperation
47-
}>, prevFile: File, currFile: File, currDoc: ResolvedVersionDocument, prevDoc: ResolvedVersionDocument, ctx: CompareContext): Promise<{
47+
}>, prevDoc: ResolvedVersionDocument | undefined, currDoc: ResolvedVersionDocument | undefined, ctx: CompareOperationsPairContext): Promise<{
4848
operationChanges: OperationChanges[]
4949
tags: string[]
5050
}> => {
51-
const prevDocSchema = buildSchema(await prevFile.text(), { noLocation: true })
52-
const currDocSchema = buildSchema(await currFile.text(), { noLocation: true })
51+
const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
52+
const prevFile = prevDoc && await rawDocumentResolver(previousVersion, previousPackageId, prevDoc.slug)
53+
const currFile = currDoc && await rawDocumentResolver(currentVersion, currentPackageId, currDoc.slug)
54+
const prevDocSchema = prevFile && buildSchema(await prevFile.text(), { noLocation: true })
55+
const currDocSchema = currFile && buildSchema(await currFile.text(), { noLocation: true })
5356

54-
const prevDocData = (await buildGraphQLDocument({
57+
let prevDocData = prevDocSchema && (await buildGraphQLDocument({
5558
...prevDoc,
5659
source: prevFile,
5760
kind: FILE_KIND.TEXT,
5861
data: prevDocSchema,
5962
}, prevDoc)).data
60-
const currDocData = (await buildGraphQLDocument({
63+
let currDocData = currDocSchema && (await buildGraphQLDocument({
6164
...currDoc,
6265
source: currFile,
6366
kind: FILE_KIND.TEXT,
6467
data: currDocSchema,
6568
}, currDoc)).data
6669

70+
if (!prevDocData && currDocData) {
71+
prevDocData = getCopyWithEmptyOperations(currDocData)
72+
}
73+
if (prevDocData && !currDocData) {
74+
currDocData = getCopyWithEmptyOperations(prevDocData)
75+
}
76+
6777
const { merged, diffs } = apiDiff(
6878
prevDocData,
6979
currDocData,
7080
{
7181
...NORMALIZE_OPTIONS,
7282
metaKey: DIFF_META_KEY,
7383
originsFlag: ORIGINS_SYMBOL,
74-
diffsAggregatedFlag: DEFAULT_DIFFS_AGGREGATED_META_KEY,
84+
diffsAggregatedFlag: DIFFS_AGGREGATED_META_KEY,
7585
// mode: COMPARE_MODE_OPERATION,
7686
normalizedResult: true,
7787
},
@@ -83,7 +93,7 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
8393

8494
let operationDiffs: Diff[] = []
8595

86-
const { currentGroup, previousGroup } = ctx.config
96+
const { currentGroup, previousGroup } = ctx
8797
const currGroupSlug = slugify(removeFirstSlash(currentGroup || ''))
8898
const prevGroupSlug = slugify(removeFirstSlash(previousGroup || ''))
8999

@@ -100,7 +110,7 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
100110

101111
const { current, previous } = operationsMap[operationId] ?? {}
102112
if (current && previous) {
103-
operationDiffs = [...(methodData as WithAggregatedDiffs<GraphApiOperation>)[DEFAULT_DIFFS_AGGREGATED_META_KEY]]
113+
operationDiffs = [...(methodData as WithAggregatedDiffs<GraphApiOperation>)[DIFFS_AGGREGATED_META_KEY]]
104114
} else if (current || previous) {
105115
for (const type of GRAPHQL_TYPE_KEYS) {
106116
const operationsByType = (merged[type] as WithDiffMetaRecord<Record<string, GraphApiOperation>>)?.[DIFF_META_KEY]

src/apitypes/graphql/graphql.operation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const buildGraphQLOperation = (
9494

9595
return {
9696
operationId,
97+
documentId: document.slug,
9798
dataHash: 'dataHash is to be removed',
9899
apiType: GRAPHQL_API_TYPE,
99100
apiKind: rawToApiKind(apiKind),

src/apitypes/rest/rest.changes.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,15 @@ import {
2929
apiDiff,
3030
breaking,
3131
COMPARE_MODE_OPERATION,
32-
DEFAULT_DIFFS_AGGREGATED_META_KEY,
3332
Diff,
3433
DIFF_META_KEY,
3534
DiffAction,
35+
DIFFS_AGGREGATED_META_KEY,
3636
risky,
3737
} from '@netcracker/qubership-apihub-api-diff'
3838
import { MESSAGE_SEVERITY, NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../consts'
3939
import {
4040
BREAKING_CHANGE_TYPE,
41-
CompareContext,
4241
CompareOperationsPairContext,
4342
NormalizedOperationId,
4443
OperationChanges,
@@ -66,12 +65,22 @@ import { createOperationChange, getOperationTags, takeSubstringIf } from '../../
6665
export const compareDocuments = async (apiType: OperationsApiType, operationsMap: Record<NormalizedOperationId, {
6766
previous?: ResolvedOperation
6867
current?: ResolvedOperation
69-
}>, prevFile: File, currFile: File, currDoc: ResolvedVersionDocument, prevDoc: ResolvedVersionDocument, ctx: CompareContext): Promise<{
68+
}>, prevDoc: ResolvedVersionDocument | undefined, currDoc: ResolvedVersionDocument | undefined, ctx: CompareOperationsPairContext): Promise<{
7069
operationChanges: OperationChanges[]
7170
tags: string[]
7271
}> => {
73-
const prevDocData = JSON.parse(await prevFile.text())
74-
const currDocData = JSON.parse(await currFile.text())
72+
const { rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
73+
const prevFile = prevDoc && await rawDocumentResolver(previousVersion, previousPackageId, prevDoc.slug)
74+
const currFile = currDoc && await rawDocumentResolver(currentVersion, currentPackageId, currDoc.slug)
75+
let prevDocData = prevFile && JSON.parse(await prevFile.text())
76+
let currDocData = currFile && JSON.parse(await currFile.text())
77+
78+
if (!prevDocData && currDocData) {
79+
prevDocData = createCopyWithEmptyPath(currDocData)
80+
}
81+
if (prevDocData && !currDocData) {
82+
currDocData = createCopyWithEmptyPath(prevDocData)
83+
}
7584

7685
const { merged, diffs } = apiDiff(
7786
prevDocData,
@@ -80,7 +89,7 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
8089
...NORMALIZE_OPTIONS,
8190
metaKey: DIFF_META_KEY,
8291
originsFlag: ORIGINS_SYMBOL,
83-
diffsAggregatedFlag: DEFAULT_DIFFS_AGGREGATED_META_KEY,
92+
diffsAggregatedFlag: DIFFS_AGGREGATED_META_KEY,
8493
// mode: COMPARE_MODE_OPERATION,
8594
normalizedResult: true,
8695
},
@@ -128,17 +137,22 @@ export const compareDocuments = async (apiType: OperationsApiType, operationsMap
128137
let operationDiffs: Diff[] = []
129138
if (current && previous) {
130139
operationDiffs = [
131-
...(methodData as WithAggregatedDiffs<OpenAPIV3.OperationObject>)[DEFAULT_DIFFS_AGGREGATED_META_KEY],
140+
...(methodData as WithAggregatedDiffs<OpenAPIV3.OperationObject>)[DIFFS_AGGREGATED_META_KEY],
132141
// todo what about security? add test
133142
...extractServersDiffs(merged),
134143
]
135144

136145
const pathParamRenameDiff = (merged.paths as WithDiffMetaRecord<OpenAPIV3.PathsObject>)[DIFF_META_KEY]?.[path]
137146
pathParamRenameDiff && operationDiffs.push(pathParamRenameDiff)
138147
} else if (current || previous) {
139-
const operationDiff = (merged.paths as WithDiffMetaRecord<OpenAPIV3.PathsObject>)[DIFF_META_KEY]?.[path]
148+
const operationDiff = (merged.paths[path] as WithDiffMetaRecord<OpenAPIV3.PathsObject>)[DIFF_META_KEY]?.[inferredMethod]
140149
if (!operationDiff) {
141-
throw new Error('should not happen')
150+
// ignore removed and added operations, they'll be handled in a separate docs comparison
151+
continue
152+
}
153+
const deprecatedInVersionsCount = previousVersionDeprecations?.operations.find((operation) => operation.operationId === operationId)?.deprecatedInPreviousVersions?.length ?? 0
154+
if (isOperationRemove(operationDiff) && deprecatedInVersionsCount > 1) {
155+
operationDiff.type = risky
142156
}
143157
operationDiffs.push(operationDiff)
144158
}
@@ -162,11 +176,11 @@ export const compareRestOperationsData = async (current: VersionRestOperation |
162176
let previousOperation = removeComponents(previous?.data)
163177
let currentOperation = removeComponents(current?.data)
164178
if (!previousOperation && currentOperation) {
165-
previousOperation = getCopyWithEmptyPath(currentOperation as RestOperationData)
179+
previousOperation = createCopyWithEmptyPath(currentOperation as RestOperationData)
166180
}
167181

168182
if (previousOperation && !currentOperation) {
169-
currentOperation = getCopyWithEmptyPath(previousOperation as RestOperationData)
183+
currentOperation = createCopyWithEmptyPath(previousOperation as RestOperationData)
170184
}
171185

172186
const diffResult = apiDiff(
@@ -201,7 +215,6 @@ async function reclassifyBreakingChanges(
201215
if (!previosVersionDeprecations) {
202216
return
203217
}
204-
previosVersionDeprecations.operations[0]
205218

206219
const previousOperation = previosVersionDeprecations.operations[0]
207220

@@ -272,11 +285,14 @@ async function reclassifyBreakingChanges(
272285
}
273286
}
274287

275-
function getCopyWithEmptyPath(template: RestOperationData): RestOperationData {
288+
export function createCopyWithEmptyPath(template: RestOperationData): RestOperationData {
276289
// eslint-disable-next-line @typescript-eslint/no-unused-vars
277290
const { paths, ...rest } = template
291+
278292
return {
279-
paths: {},
293+
paths: {
294+
...Object.fromEntries(Object.keys(template.paths).map(key => [key, {}])),
295+
},
280296
...rest,
281297
}
282298
}

src/apitypes/rest/rest.operation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export const buildRestOperation = (
155155

156156
return {
157157
operationId,
158+
documentId: document.slug,
158159
dataHash: 'dataHash is to be removed',
159160
apiType: REST_API_TYPE,
160161
apiKind: rawToApiKind(apiKind),

src/builder.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import {
5757
BUILD_TYPE,
5858
DEFAULT_BATCH_SIZE,
5959
DEFAULT_VALIDATION_RULES_SEVERITY_CONFIG,
60+
EXPORT_BUILD_TYPES,
61+
ExportBuildType,
6062
MESSAGE_SEVERITY,
6163
SUPPORTED_FILE_FORMATS,
6264
VERSION_STATUS,
@@ -136,13 +138,20 @@ export class PackageVersionBuilder implements IPackageVersionBuilder {
136138

137139
// todo rename
138140
async createNodeVersionPackage(): Promise<{ packageVersion: any; exportFileName?: string }> {
139-
return {packageVersion: await createVersionPackage(this.buildResult, new AdmZipTool(), this.builderContext(this.config)), exportFileName: this.buildResult.exportFileName}
141+
return {
142+
packageVersion: await createVersionPackage(this.buildResult, new AdmZipTool(), this.builderContext(this.config)),
143+
exportFileName: this.buildResult.exportFileName,
144+
}
140145
}
141146

142147
get operationList(): ApiOperation[] {
143148
return getOperationsList(this.buildResult)
144149
}
145150

151+
get documentList(): VersionDocument[] {
152+
return [...this.buildResult.documents.values()]
153+
}
154+
146155
get packageConfig(): PackageConfig {
147156
// eslint-disable-next-line @typescript-eslint/no-unused-vars
148157
const { files, ...config } = this.config
@@ -311,6 +320,19 @@ export class PackageVersionBuilder implements IPackageVersionBuilder {
311320
packageId: PackageId,
312321
slug: string,
313322
): Promise<File> {
323+
if (this.canBeResolvedLocally(version, packageId)) {
324+
const document = this.documentList.find((document) => document.slug === slug)
325+
if (!document) {
326+
throw new Error(`Raw document ${slug} is missing in local cache`)
327+
}
328+
const apiBuilder = this.apiBuilders.find(apiBuilder => apiBuilder.types.includes(document?.type))
329+
if (!apiBuilder) {
330+
// todo
331+
throw new Error(`Cannot find apiBuilder for type ${document?.type}`)
332+
}
333+
return new File([apiBuilder.dumpDocument(document)], document.filename)
334+
}
335+
314336
if (!this.params.resolvers.rawDocumentResolver) {
315337
throw new Error('rawDocumentResolver is not provided')
316338
}
@@ -453,6 +475,13 @@ export class PackageVersionBuilder implements IPackageVersionBuilder {
453475
apiType?: OperationsApiType,
454476
): Promise<ResolvedVersionDocuments | null> {
455477
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
479+
// the only way possible is to get the docs from buildResult, but the referenced packages map will be empty
480+
if (this.canBeResolvedLocally(version, packageId)) {
481+
const apiBuilder = this.apiBuilders.find(apiBuilder => apiBuilder.apiType === apiType)
482+
const currentApiTypeDocuments = this.documentList.filter(({ type }) => apiBuilder?.types.includes(type))
483+
return { documents: currentApiTypeDocuments, packages: {} }
484+
}
456485

457486
const { versionDocumentsResolver } = this.params.resolvers
458487
if (!versionDocumentsResolver) {
@@ -509,6 +538,7 @@ export class PackageVersionBuilder implements IPackageVersionBuilder {
509538
private canBeResolvedLocally(version: string, packageId: string | undefined): boolean {
510539
return this.config.buildType !== BUILD_TYPE.CHANGELOG &&
511540
this.config.buildType !== BUILD_TYPE.PREFIX_GROUPS_CHANGELOG &&
541+
!EXPORT_BUILD_TYPES.includes(this.config.buildType as ExportBuildType) &&
512542
version === this.config.version &&
513543
packageId === this.config.packageId
514544
}

0 commit comments

Comments
 (0)