Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
a508c54
test: add operation.deprecated check
CountRedClaw Jul 16, 2025
c6ab782
test: add operation.metadata.originalPath checks
CountRedClaw Jul 16, 2025
27e473a
style: cleanup
CountRedClaw Jul 16, 2025
cf1464e
test: add operation.deprecatedInfo & operation.deprecatedInPreviousVe…
CountRedClaw Jul 17, 2025
6f03215
refactor: compare documents instead of operations
CountRedClaw Aug 8, 2025
3f1f531
feat: implement documents matching based on operations
CountRedClaw Aug 14, 2025
6f736a2
refactor: simplify documents matching
CountRedClaw Aug 15, 2025
e820f6b
feat: support prefix groups comparison
CountRedClaw Aug 25, 2025
00c574f
test: add missing cases
CountRedClaw Aug 25, 2025
227b690
chore: use api-diff from branch
CountRedClaw Aug 25, 2025
4cdcddb
refactor: cleanup
CountRedClaw Aug 26, 2025
bd38e7d
refactor: cleanup
CountRedClaw Aug 26, 2025
98687ac
refactor: simplify comparison logic
CountRedClaw Aug 27, 2025
4cc235b
fix: aggregate missing root-level diffs
CountRedClaw Aug 29, 2025
d25fce4
refactor: cleanup
CountRedClaw Aug 29, 2025
53839b9
test: add missing cases
CountRedClaw Sep 3, 2025
6cc72d3
fix: review remarks
CountRedClaw Sep 3, 2025
3ac3019
refactor
CountRedClaw Sep 3, 2025
53c3f2c
perf: add data for measurements
CountRedClaw Sep 4, 2025
a3b64dc
perf: add data for measurements
CountRedClaw Sep 5, 2025
5eee209
Merge remote-tracking branch 'origin/develop' into feature/performanc…
CountRedClaw Sep 8, 2025
cc7cb9d
chore: use api-diff from branch
CountRedClaw Sep 8, 2025
7817a91
fix: remove prefix from servers case
CountRedClaw Sep 10, 2025
fad1ef1
feat: collect all deep changes in components.securitySchemes
CountRedClaw Sep 11, 2025
67a7f25
fix: servers and security diffs collecting
CountRedClaw Sep 11, 2025
9dc6b5b
fix: review remarks
CountRedClaw Sep 11, 2025
8b4fd8e
fix: review remarks
CountRedClaw Sep 11, 2025
acfd82b
test: path change due to moving a prefix from server.url to path clas…
CountRedClaw Sep 11, 2025
a681614
fix: duplicated tags
CountRedClaw Sep 16, 2025
5d94d4d
refactor
CountRedClaw Sep 16, 2025
c22a7f3
fix: linter
CountRedClaw Sep 16, 2025
ea7bc29
cleanup
CountRedClaw Sep 16, 2025
68307bb
test: fix data
CountRedClaw Sep 16, 2025
45da8ba
fix: ci linter
CountRedClaw Sep 16, 2025
b46ab95
fix: make fileResolver optional
CountRedClaw Sep 18, 2025
26cc504
fix: graphql operations handling
CountRedClaw Sep 19, 2025
29cb21f
fix: prefix groups changelog calculation when prefix is specified in …
CountRedClaw Sep 24, 2025
093cfa3
test: add unsupported cases
CountRedClaw Sep 24, 2025
01fe7e8
fix: use existing document pair (if available) to calculate diffs for…
b41ex Oct 1, 2025
bdcb07d
perf: filter out referentially equal diffs before diffId calculation
CountRedClaw Oct 1, 2025
c1b8052
refactor: fix expected result for test according to new behaviour in …
b41ex Oct 2, 2025
adc8992
fix: use recursive aggregateDiff function function (many-folds perfro…
b41ex Oct 2, 2025
d73840d
fix: optimize diff deduplication- use reference identity when diffs a…
b41ex Oct 2, 2025
4ccb5a4
fix: optimize operationsChanges deduplication for typical case
b41ex Oct 2, 2025
88e6e74
chore: set feature branch version for depdendencies
b41ex Oct 2, 2025
2b977fd
chore: merge remote-tracking branch 'refs/remotes/origin/feature/perf…
b41ex Oct 2, 2025
decea03
chore: merge branch 'develop' into feature/performance-optimization
b41ex Oct 3, 2025
2ab12cf
chore: merge develop
b41ex Oct 3, 2025
a63d62f
chore: fix linter errors
b41ex Oct 3, 2025
9bf8c81
chore: fix linter errors
b41ex Oct 3, 2025
2c895e6
chore: fix linter errors
b41ex Oct 3, 2025
8596d36
chore: disable GraphQL linting
b41ex Oct 3, 2025
17cfd7f
chore: remove unused imports
b41ex Oct 5, 2025
0bd030d
fix: extra leading dash in the the operation id when comparing prefix…
b41ex Oct 5, 2025
4a910a5
fix: operation matching when operation base path moved between path a…
b41ex Oct 8, 2025
4ab42f1
refactor: rename test to avoid problems with selective run
b41ex Oct 9, 2025
68d9c6f
fix: incorrect calculation of operation ids in prefix groups
b41ex Oct 9, 2025
39fde46
fix: enforce group prefix validation
b41ex Oct 9, 2025
a5e0d54
refactor: skip useless test
b41ex Oct 9, 2025
1af80ce
fix: add pahth parameter rename test for prefix groups
b41ex Oct 10, 2025
60fdc32
fix return full operation ids when comparing prefix groups
b41ex Oct 10, 2025
a3a6a64
refactor: reuse group prefix validation function
b41ex Oct 12, 2025
9305c38
refactor: export createCopyWithPrefixGroupOperationsOnly for reuse in UI
b41ex Oct 12, 2025
13b5a9d
refactor: modify createCopyWithPrefixGroupOperationsOnly to keep copy…
b41ex Oct 12, 2025
1f6c82f
refactor: export RestOperationData to use in UI
b41ex Oct 12, 2025
c1d8040
refactor: fix code formatting
b41ex Oct 12, 2025
c1c2345
refactor: use existing isValidHttp method
b41ex Oct 12, 2025
2c83aa2
refactor: extract method to calculateNormalizedOperationId
b41ex Oct 12, 2025
50edb39
refactor: remove obsolete comment
b41ex Oct 12, 2025
b878676
docs: add comments
b41ex Oct 12, 2025
22a8260
refactor: add unit test for removeRedundantPartialPairs
b41ex Oct 12, 2025
2d229f5
docs: rename test group to emphasize it is for the changelog build type
b41ex Oct 12, 2025
15143fc
refactor: use extractOperationBasePath from api-diff to avoid code du…
b41ex Oct 12, 2025
527e525
refactor: switch between and after samples for add-prefix-to-server t…
b41ex Oct 12, 2025
21196bb
refactor: add unit tests for dedupeTuples function
b41ex Oct 12, 2025
4364707
fix: collect openapi version changes for operations
b41ex Oct 17, 2025
5c6cb18
fix: add missing samples
b41ex Oct 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"update-lock-file": "update-lock-file @netcracker"
},
"dependencies": {
"@netcracker/qubership-apihub-api-diff": "2.1.2",
"@netcracker/qubership-apihub-api-diff": "feature-performance-optimization",
"@netcracker/qubership-apihub-api-unifier": "2.2.0",
"@netcracker/qubership-apihub-graphapi": "1.0.8",
"@netcracker/qubership-apihub-json-crawl": "1.0.4",
Expand Down
125 changes: 102 additions & 23 deletions src/apitypes/graphql/graphql.changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,115 @@
* limitations under the License.
*/

import { VersionGraphQLOperation } from './graphql.types'
import { removeComponents, takeIf } from '../../utils'
import { apiDiff, COMPARE_MODE_OPERATION, Diff } from '@netcracker/qubership-apihub-api-diff'
import { NORMALIZE_OPTIONS } from '../../consts'
import { GraphApiSchema } from '@netcracker/qubership-apihub-graphapi'

export const graphqlOperationsCompare = async (current: VersionGraphQLOperation | undefined, previous: VersionGraphQLOperation | undefined): Promise<Diff[]> => {
let previousOperation = removeComponents(previous?.data)
let currentOperation = removeComponents(current?.data)
if (!previousOperation && currentOperation) {
previousOperation = getCopyWithEmptyOperations(currentOperation as GraphApiSchema)
}
import { isEmpty, removeFirstSlash, slugify, takeIf } from '../../utils'
import { apiDiff, Diff, DIFF_META_KEY, DIFFS_AGGREGATED_META_KEY } from '@netcracker/qubership-apihub-api-diff'
import { NORMALIZE_OPTIONS, ORIGINS_SYMBOL } from '../../consts'
import { GraphApiOperation, GraphApiSchema } from '@netcracker/qubership-apihub-graphapi'
import { buildSchema } from 'graphql/utilities'
import { buildGraphQLDocument } from './graphql.document'
import {
CompareOperationsPairContext,
FILE_KIND,
OperationChanges,
ResolvedVersionDocument,
WithAggregatedDiffs,
WithDiffMetaRecord,
} from '../../types'
import { GRAPHQL_TYPE, GRAPHQL_TYPE_KEYS } from './graphql.consts'
import { createOperationChange, getOperationTags, OperationsMap } from '../../components'

export const compareDocuments = async (
operationsMap: OperationsMap,
prevDoc: ResolvedVersionDocument | undefined,
currDoc: ResolvedVersionDocument | undefined,
ctx: CompareOperationsPairContext): Promise<{
operationChanges: OperationChanges[]
tags: string[]
}> => {
const { apiType, rawDocumentResolver, previousVersion, currentVersion, previousPackageId, currentPackageId } = ctx
const prevFile = prevDoc && await rawDocumentResolver(previousVersion, previousPackageId, prevDoc.slug)
const currFile = currDoc && await rawDocumentResolver(currentVersion, currentPackageId, currDoc.slug)
const prevDocSchema = prevFile && buildSchema(await prevFile.text(), { noLocation: true })
const currDocSchema = currFile && buildSchema(await currFile.text(), { noLocation: true })

if (previousOperation && !currentOperation) {
currentOperation = getCopyWithEmptyOperations(previousOperation as GraphApiSchema)
let prevDocData = prevDocSchema && (await buildGraphQLDocument({
...prevDoc,
source: prevFile,
kind: FILE_KIND.TEXT,
data: prevDocSchema,
}, prevDoc)).data
let currDocData = currDocSchema && (await buildGraphQLDocument({
...currDoc,
source: currFile,
kind: FILE_KIND.TEXT,
data: currDocSchema,
}, currDoc)).data

if (!prevDocData && currDocData) {
prevDocData = getCopyWithEmptyOperations(currDocData)
}
if (prevDocData && !currDocData) {
currDocData = getCopyWithEmptyOperations(prevDocData)
}

//todo think about normalize options
const { diffs } = apiDiff(
previousOperation,
currentOperation,
const { merged, diffs } = apiDiff(
prevDocData,
currDocData,
{
...NORMALIZE_OPTIONS,
mode: COMPARE_MODE_OPERATION,
metaKey: DIFF_META_KEY,
originsFlag: ORIGINS_SYMBOL,
diffsAggregatedFlag: DIFFS_AGGREGATED_META_KEY,
// mode: COMPARE_MODE_OPERATION,
normalizedResult: true,
beforeSource: previous?.data,
afterSource: current?.data,
},
)
return diffs
) as { merged: GraphApiSchema; diffs: Diff[] }

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

let operationDiffs: Diff[] = []

const { currentGroup, previousGroup } = ctx
const currGroupSlug = slugify(removeFirstSlash(currentGroup || ''))
const prevGroupSlug = slugify(removeFirstSlash(previousGroup || ''))

const tags = new Set<string>()
const changedOperations: OperationChanges[] = []

for (const type of GRAPHQL_TYPE_KEYS) {
const operationsByType = merged[type]
if (!operationsByType) { continue }

for (const operationKey of Object.keys(operationsByType)) {
const operationId = slugify(`${GRAPHQL_TYPE[type]}-${operationKey}`)
const methodData = operationsByType[operationKey]

const { current, previous } = operationsMap[operationId] ?? {}
if (current && previous) {
operationDiffs = [...(methodData as WithAggregatedDiffs<GraphApiOperation>)[DIFFS_AGGREGATED_META_KEY]]
} else if (current || previous) {
for (const type of GRAPHQL_TYPE_KEYS) {
const operationsByType = (merged[type] as WithDiffMetaRecord<Record<string, GraphApiOperation>>)?.[DIFF_META_KEY]
if (!operationsByType) { continue }
operationDiffs.push(...Object.values(operationsByType))
}
if (isEmpty(operationDiffs)) {
throw new Error('should not happen')
}
}

if (isEmpty(operationDiffs)) {
continue
}

changedOperations.push(createOperationChange(apiType, operationDiffs, previous, current, currGroupSlug, prevGroupSlug, currentGroup, previousGroup))
getOperationTags(current ?? previous).forEach(tag => tags.add(tag))
}
}

return { operationChanges: changedOperations, tags: [...tags.values()] }
}

function getCopyWithEmptyOperations(template: GraphApiSchema): GraphApiSchema {
Expand Down
6 changes: 6 additions & 0 deletions src/apitypes/graphql/graphql.consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ResolvedVersionDocument, ZippableDocument } from '../../types'
import { GraphQLDocumentType } from './graphql.types'

export const GRAPHQL_API_TYPE = 'graphql' as const

Expand Down Expand Up @@ -43,3 +45,7 @@ export const GRAPHQL_TYPE = {
'mutations': 'mutation',
'subscriptions': 'subscription',
} as const

export function isGraphqlDocument(document: ZippableDocument | ResolvedVersionDocument): boolean {
return Object.values(GRAPHQL_DOCUMENT_TYPE).includes(document.type as GraphQLDocumentType)
}
11 changes: 8 additions & 3 deletions src/apitypes/graphql/graphql.document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
* limitations under the License.
*/

import { buildFromIntrospection, buildFromSchema, GraphApiSchema, printGraphApi } from '@netcracker/qubership-apihub-graphapi'
import {
buildFromIntrospection,
buildFromSchema,
GraphApiSchema,
printGraphApi,
} from '@netcracker/qubership-apihub-graphapi'
import type { GraphQLSchema, IntrospectionQuery } from 'graphql'

import type { DocumentBuilder, DocumentDumper } from '../../types'
import { BuildConfigFile, DocumentDumper, TextFile, VersionDocument } from '../../types'
import { GRAPHQL_DOCUMENT_TYPE } from './graphql.consts'

export const buildGraphQLDocument: DocumentBuilder<GraphApiSchema> = async (parsedFile, file) => {
export const buildGraphQLDocument: (parsedFile: TextFile, file: BuildConfigFile) => Promise<VersionDocument<GraphApiSchema>> = async (parsedFile, file) => {
let graphapi: GraphApiSchema
if (parsedFile.type === GRAPHQL_DOCUMENT_TYPE.INTROSPECTION) {
const introspection = (parsedFile?.data && '__schema' in parsedFile.data ? parsedFile?.data : parsedFile.data?.data) as IntrospectionQuery
Expand Down
9 changes: 2 additions & 7 deletions src/apitypes/graphql/graphql.operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,13 @@ export const buildGraphQLOperation = (

const apiKind = document.apiKind || API_KIND.BWC


const dataHash = syncDebugPerformance('[ModelsAndOperationHashing]', () => {
syncDebugPerformance('[ModelsAndOperationHashing]', () => {
calculateSpecRefs(document.data, singleOperationRefsOnlySpec, singleOperationSpec)
const dataHash = calculateObjectHash(singleOperationSpec)
return dataHash
}, debugCtx)



return {
operationId,
dataHash,
documentId: document.slug,
apiType: GRAPHQL_API_TYPE,
apiKind: rawToApiKind(apiKind),
deprecated: !!singleOperationEffectiveSpec[type]?.[method]?.directives?.deprecated,
Expand Down
4 changes: 2 additions & 2 deletions src/apitypes/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { buildGraphQLOperations } from './graphql.operations'
import { GRAPHQL_API_TYPE, GRAPHQL_DOCUMENT_TYPE } from './graphql.consts'
import { parseGraphQLFile } from './graphql.parser'
import { ApiBuilder } from '../../types'
import { graphqlOperationsCompare } from './graphql.changes'
import { compareDocuments } from './graphql.changes'

export * from './graphql.consts'

Expand All @@ -32,5 +32,5 @@ export const graphqlApiBuilder: ApiBuilder<GraphApiSchema> = {
buildDocument: buildGraphQLDocument,
buildOperations: buildGraphQLOperations,
dumpDocument: dumpGraphQLDocument,
compareOperationsData: graphqlOperationsCompare,
compareDocuments: compareDocuments,
}
4 changes: 2 additions & 2 deletions src/apitypes/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { OpenAPIV3 } from 'openapi-types'

import { buildRestDocument, createRestExportDocument, dumpRestDocument } from './rest.document'
import { REST_API_TYPE, REST_DOCUMENT_TYPE } from './rest.consts'
import { compareRestOperationsData } from './rest.changes'
import { compareDocuments } from './rest.changes'
import { buildRestOperations, createNormalizedOperationId } from './rest.operations'
import { parseRestFile } from './rest.parser'

Expand All @@ -33,7 +33,7 @@ export const restApiBuilder: ApiBuilder<OpenAPIV3.Document> = {
buildDocument: buildRestDocument,
buildOperations: buildRestOperations,
dumpDocument: dumpRestDocument,
compareOperationsData: compareRestOperationsData,
compareDocuments: compareDocuments,
createNormalizedOperationId: createNormalizedOperationId,
createExportDocument: createRestExportDocument,
}
Loading