Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 63 additions & 16 deletions src/strategies/export-version.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@
* limitations under the License.
*/

import { BuilderStrategy, BuildResult, BuildTypeContexts, ExportDocument, ExportVersionBuildConfig } from '../types'
import { getDocumentTitle, getSplittedVersionKey } from '../utils'
import {
BuilderStrategy,
BuildResult,
BuildTypeContexts,
ExportDocument,
ExportVersionBuildConfig,
ResolvedVersionDocument,
SHAREABILITY_STATUS_UNKNOWN,
ShareabilityStatus,
} from '../types'
import { getDocumentTitle, getFileExtension, getSplittedVersionKey } from '../utils'
import {
createCommonStaticExportDocuments,
createSingleFileExportName,
Expand All @@ -27,44 +36,65 @@ import { FILE_FORMAT_HTML, FILE_FORMAT_JSON } from '../consts'

export class ExportVersionStrategy implements BuilderStrategy {
async execute(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise<BuildResult> {
const { versionDocumentsResolver } = contexts.builderContext(config)
const { packageId, version: versionWithRevision } = config
const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] }

const documentsToExport = filterDocumentsByShareabilityStatus(documents, config)

const isSingleNonRestDocument = documentsToExport.length === 1 && !isRestDocument(documentsToExport[0])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this flag used once, move it to line 63


switch (config.format) {
case FILE_FORMAT_HTML:
await exportToHTML(config, buildResult, contexts)
await exportToHTML(config, buildResult, contexts, documentsToExport)
break
default:
await defaultExport(config, buildResult, contexts)
await defaultExport(config, buildResult, contexts, documentsToExport)
break
}

const { packageId, version: versionWithRevision, format = FILE_FORMAT_JSON } = config
const { format = FILE_FORMAT_JSON } = config
const [version] = getSplittedVersionKey(versionWithRevision)
if (buildResult.exportDocuments.length > 1) {
buildResult.exportFileName = `${packageId}_${version}.zip`
return buildResult
}

buildResult.exportFileName = createSingleFileExportName(packageId, version, getDocumentTitle(buildResult.exportDocuments[0].filename), format)
const singleExportDocument = buildResult.exportDocuments[0]
const singleExportDocumentExtension = isSingleNonRestDocument
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add tests to this logic?
I.e. that the output is either zip archive or single file depending on number of documents to be exported (and format?).

? getFileExtension(singleExportDocument.filename)
: format

buildResult.exportFileName = createSingleFileExportName(
packageId,
version,
getDocumentTitle(singleExportDocument.filename),
singleExportDocumentExtension,
)
return buildResult
}
}

async function exportToHTML(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise<void> {
async function exportToHTML(
config: ExportVersionBuildConfig,
buildResult: BuildResult,
contexts: BuildTypeContexts,
documentsToExport: ReadonlyArray<ResolvedVersionDocument>,
): Promise<void> {
const {
versionDocumentsResolver,
rawDocumentResolver,
templateResolver,
packageResolver,
apiBuilders,
packageResolver,
} = contexts.builderContext(config)
const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config
const [version] = getSplittedVersionKey(versionWithRevision)
const { name: packageName } = await packageResolver(packageId)
const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] }

const generatedHtmlExportDocuments: ExportDocument[] = []
const restDocuments = documents.filter(isRestDocument)
const restDocuments = documentsToExport.filter(isRestDocument)
const shouldAddIndexPage = restDocuments.length > 0
const transformedDocuments = await Promise.all(documents.map(async document => {
const transformedDocuments = await Promise.all(documentsToExport.map(async document => {
const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder
const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug)
return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions, generatedHtmlExportDocuments, shouldAddIndexPage) ?? createUnknownExportDocument(file.name, file)
Expand All @@ -81,9 +111,13 @@ async function exportToHTML(config: ExportVersionBuildConfig, buildResult: Build
}
}

async function defaultExport(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise<void> {
async function defaultExport(
config: ExportVersionBuildConfig,
buildResult: BuildResult,
contexts: BuildTypeContexts,
documentsToExport: ReadonlyArray<ResolvedVersionDocument>,
): Promise<void> {
const {
versionDocumentsResolver,
rawDocumentResolver,
templateResolver,
packageResolver,
Expand All @@ -92,13 +126,26 @@ async function defaultExport(config: ExportVersionBuildConfig, buildResult: Buil
const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config
const [version] = getSplittedVersionKey(versionWithRevision)
const { name: packageName } = await packageResolver(packageId)
const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] }

const transformedDocuments = await Promise.all(documents.map(async document => {
const transformedDocuments = await Promise.all(documentsToExport.map(async document => {
const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder
const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug)
return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions) ?? createUnknownExportDocument(file.name, file)
}))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

callback on lines 97-101 and 130-134 seems the same


buildResult.exportDocuments.push(...transformedDocuments)
}

function filterDocumentsByShareabilityStatus(
documents: ReadonlyArray<ResolvedVersionDocument>,
config: ExportVersionBuildConfig,
): ReadonlyArray<ResolvedVersionDocument> {
const { allowedShareabilityStatuses } = config

if (!allowedShareabilityStatuses || allowedShareabilityStatuses.length === 0) {
return documents
}
const allowedSet = new Set<ShareabilityStatus>(allowedShareabilityStatuses)

return documents.filter(doc => allowedSet.has(doc.shareabilityStatus ?? SHAREABILITY_STATUS_UNKNOWN))
}
2 changes: 2 additions & 0 deletions src/types/external/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
VERSION_STATUS,
} from '../../consts'
import { OpenApiExtensionKey } from '@netcracker/qubership-apihub-api-unifier'
import { ShareabilityStatus } from './documents'

export type BuildType = KeyOfConstType<typeof BUILD_TYPE>
export type VersionStatus = KeyOfConstType<typeof VERSION_STATUS>
Expand Down Expand Up @@ -104,6 +105,7 @@ export interface ExportVersionBuildConfig extends BuildConfigBase {
version: VersionId
format: ExportFormat
allowedOasExtensions?: OpenApiExtensionKey[]
readonly allowedShareabilityStatuses?: readonly ShareabilityStatus[]
}

export interface ExportRestDocumentBuildConfig extends BuildConfigBase {
Expand Down
19 changes: 16 additions & 3 deletions src/types/external/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* limitations under the License.
*/

import { FileId, OperationsApiType, PackageId, TemplatePath, VersionId } from './types'
import { ResolvedReferenceMap } from './references'
import { FileFormat } from '../internal'
import { ApihubApiCompatibilityKind } from '../../consts'
import { FileFormat } from '../internal'
import { ResolvedReferenceMap } from './references'
import { FileId, OperationsApiType, PackageId, TemplatePath, VersionId } from './types'

export type ResolvedDocument = {
fileId: string
Expand All @@ -44,6 +44,7 @@ export type ResolvedVersionDocuments = {
export type ResolvedVersionDocument = ResolvedDocument & {
packageRef?: string
apiKind?: ApihubApiCompatibilityKind
shareabilityStatus?: ShareabilityStatus
}

export type Labels = string[]
Expand Down Expand Up @@ -76,3 +77,15 @@ export type RawDocumentResolver = (
export type FileResolver = (fileId: FileId) => Promise<Blob | null>

export type TemplateResolver = (templatePath: TemplatePath) => Promise<Blob | null>

export const SHAREABILITY_STATUS_SHAREABLE = 'shareable' as const
export const SHAREABILITY_STATUS_NON_SHAREABLE = 'non-shareable' as const
export const SHAREABILITY_STATUS_UNKNOWN = 'unknown' as const

export const SHAREABILITY_STATUSES = [
SHAREABILITY_STATUS_SHAREABLE,
SHAREABILITY_STATUS_NON_SHAREABLE,
SHAREABILITY_STATUS_UNKNOWN,
] as const

export type ShareabilityStatus = (typeof SHAREABILITY_STATUSES)[number]
91 changes: 91 additions & 0 deletions test/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@

import { Editor, exportDocumentMatcher, exportDocumentsMatcher, loadFileAsString, LocalRegistry } from './helpers'
import {
SHAREABILITY_STATUSES,
BUILD_TYPE,
ExportRestOperationsGroupBuildConfig,
FILE_FORMAT_HTML,
FILE_FORMAT_JSON,
FILE_FORMAT_YAML,
SHAREABILITY_STATUS_SHAREABLE,
SHAREABILITY_STATUS_NON_SHAREABLE,
SHAREABILITY_STATUS_UNKNOWN,
TRANSFORMATION_KIND_MERGED,
TRANSFORMATION_KIND_REDUCED,
} from '../src'
Expand Down Expand Up @@ -397,3 +401,90 @@ describe('Export test', () => {
expect(await result.exportDocuments[0].data.text()).toEqual(expectedResult)
})
})

describe('Export shareability filtering', () => {
let shareabilityPkg: LocalRegistry
let shareabilityEditor: Editor
const SHAREABILITY_VERSION = 'shareability-version@1'

beforeAll(async () => {
shareabilityPkg = LocalRegistry.openPackage('export')
await shareabilityPkg.publish(shareabilityPkg.packageId, { version: SHAREABILITY_VERSION })

shareabilityPkg.setDocumentShareability('1', SHAREABILITY_STATUS_SHAREABLE)
shareabilityPkg.setDocumentShareability('2', SHAREABILITY_STATUS_NON_SHAREABLE)

shareabilityEditor = await Editor.openProject(shareabilityPkg.packageId, shareabilityPkg)
})

test('should export all documents when allowedShareabilityStatuses includes all statuses', async () => {
const result = await shareabilityEditor.run({
packageId: shareabilityPkg.packageId,
buildType: BUILD_TYPE.EXPORT_VERSION,
version: SHAREABILITY_VERSION,
format: FILE_FORMAT_YAML,
allowedShareabilityStatuses: SHAREABILITY_STATUSES,
})
const exportedFilenames = result.exportDocuments.map(d => d.filename)
expect(exportedFilenames).toContain('1.yaml')
expect(exportedFilenames).toContain('2.yaml')
})

test('should export all documents when allowedShareabilityStatuses is not provided', async () => {
const result = await shareabilityEditor.run({
packageId: shareabilityPkg.packageId,
buildType: BUILD_TYPE.EXPORT_VERSION,
version: SHAREABILITY_VERSION,
format: FILE_FORMAT_YAML,
})
const exportedFilenames = result.exportDocuments.map(d => d.filename)
expect(exportedFilenames).toContain('1.yaml')
expect(exportedFilenames).toContain('2.yaml')
})

const shareabilityOnlyCases = [
{
label: 'non-shareable',
version: 'shareability-non-shareable@1',
statuses: [
['1', SHAREABILITY_STATUS_SHAREABLE],
['2', SHAREABILITY_STATUS_NON_SHAREABLE],
] as const,
},
{
label: 'unknown',
version: 'shareability-unknown@1',
statuses: [
['1', SHAREABILITY_STATUS_SHAREABLE],
['2', SHAREABILITY_STATUS_UNKNOWN],
] as const,
},
] as const

test.each(shareabilityOnlyCases)(
'should export only shareable documents when allowedShareabilityStatuses is [shareable] and second doc is $label',
async ({ version, statuses }) => {
const pkg = LocalRegistry.openPackage('export')
await pkg.publish(pkg.packageId, { version })
statuses.forEach(([slug, status]) => pkg.setDocumentShareability(slug, status))

const caseEditor = await Editor.openProject(pkg.packageId, pkg)

const exportFormats = [FILE_FORMAT_YAML, FILE_FORMAT_HTML] as const
for (const format of exportFormats) {
const result = await caseEditor.run({
packageId: pkg.packageId,
buildType: BUILD_TYPE.EXPORT_VERSION,
version,
format,
allowedShareabilityStatuses: [SHAREABILITY_STATUS_SHAREABLE],
})

const exportedFilenames = result.exportDocuments.map(d => d.filename)

expect(exportedFilenames).toContain(`1.${format}`)
expect(exportedFilenames).not.toContain(`2.${format}`)
}
},
)
})
6 changes: 6 additions & 0 deletions test/helpers/registry/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,17 @@ export class LocalRegistry implements IRegistry {

groupToOperationIdsMap: Record<string, string[]> = {}
projectsDir: string = DEFAULT_PROJECTS_PATH
shareabilityOverrides: Map<string, string> = new Map()

constructor(public packageId: string, groupOperationIds: Record<string, string[]> = {}, projectsDir: string = DEFAULT_PROJECTS_PATH) {
this.groupToOperationIdsMap = groupOperationIds
this.projectsDir = projectsDir
}

setDocumentShareability(slug: string, status: string): void {
this.shareabilityOverrides.set(slug, status)
}

static openPackage(packageId: string, groupOperationIds: Record<string, string[]> = {}, projectsDir: string = DEFAULT_PROJECTS_PATH): LocalRegistry {
return new LocalRegistry(packageId, groupOperationIds, projectsDir)
}
Expand Down Expand Up @@ -334,6 +339,7 @@ export class LocalRegistry implements IRegistry {
description: document.description,
data: this.resolveDocumentData(document),
...takeIfDefined({ packageRef: refId }),
...takeIfDefined({ shareabilityStatus: this.shareabilityOverrides.get(document.slug) }),
}))
}

Expand Down
Loading