From 656843e04e5667ae03350f8dbd1a0b444990fb2f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 3 Oct 2024 14:59:05 +0200 Subject: [PATCH] Refactor script and check if enum content is number --- .../scripts/componentSchemas/fileUtils.ts | 17 ++ .../scripts/componentSchemas/languageUtils.ts | 47 +++++- frontend/scripts/componentSchemas/run.ts | 107 ++---------- .../scripts/componentSchemas/schemaUtils.ts | 157 ++++++++++++++---- frontend/scripts/componentSchemas/version.ts | 5 +- 5 files changed, 198 insertions(+), 135 deletions(-) create mode 100644 frontend/scripts/componentSchemas/fileUtils.ts diff --git a/frontend/scripts/componentSchemas/fileUtils.ts b/frontend/scripts/componentSchemas/fileUtils.ts new file mode 100644 index 00000000000..ac70f8e2e26 --- /dev/null +++ b/frontend/scripts/componentSchemas/fileUtils.ts @@ -0,0 +1,17 @@ +import type { AppFrontendVersion } from './version'; +import { versionSettings } from './version'; +import path from 'path'; +import fs from 'fs'; + +export const writeToFile = (name: string, data: any, version: AppFrontendVersion) => { + const dirPath = path.resolve(__dirname, versionSettings[version].componentSchemaPath); + const fileName = `${dirPath}/${name}.schema.v1.json`; + + fs.writeFile(fileName, JSON.stringify(data), (err: any) => { + if (err) { + console.log(err); + return; + } + console.log(`Wrote ${fileName}`); + }); +}; diff --git a/frontend/scripts/componentSchemas/languageUtils.ts b/frontend/scripts/componentSchemas/languageUtils.ts index a137777ac1d..d70d978559b 100644 --- a/frontend/scripts/componentSchemas/languageUtils.ts +++ b/frontend/scripts/componentSchemas/languageUtils.ts @@ -1,7 +1,43 @@ import nb from '../../language/src/nb.json'; -// Logs language keys and values related to the "Tekst" accordion in the component configuration. -// Use it to find missing entries in the language file(s). +export const allTextResourceBindingKeys = []; + +/** + * Adds text resource binding keys from a schema to the global list. + * @param schema The schema to extract keys from. + */ +export const addTextResourceBindingKeys = (schema: any) => { + if (schema.properties?.textResourceBindings) { + const textResourceBindingKeys = Object.keys(schema.properties.textResourceBindings.properties); + allTextResourceBindingKeys.push(...textResourceBindingKeys); + } +}; + +/** + * Sorts text resource bindings, placing 'title', 'description', and 'help' first. + * @param textResourceBindings The text resource bindings to sort. + * @returns The sorted text resource bindings. + */ +export const sortTextResourceBindings = (textResourceBindings: any) => { + const { title, description, help, ...rest } = textResourceBindings; + const sorted: any = {}; + if (title) { + sorted.title = title; + } + if (description) { + sorted.description = description; + } + if (help) { + sorted.help = help; + } + return { ...sorted, ...rest }; +}; + +/** + * Logs language keys and values displayed in the "Tekst" accordion in the component configuration column. + * Use it to find missing entries in the language file. + * @param textResourceBindingKeys Array of text resource binding keys. + */ export const logTextResourceLabels = (textResourceBindingKeys: string[]) => { textResourceBindingKeys.sort().forEach((key) => { console.log( @@ -13,8 +49,11 @@ export const logTextResourceLabels = (textResourceBindingKeys: string[]) => { }); }; -// Logs various language keys and values related to the component configuration. -// Use it to find missing entries in the language file(s). +/** + * Logs all language keys and values in the component configuration column, except for those in the "Tekst" accordion. + * Use it to find missing entries in the language file. + * @param componentPropertyKeys Array of component property keys. + */ export const logComponentPropertyLabels = (componentPropertyKeys: string[]) => { componentPropertyKeys.sort().forEach((key) => { console.log( diff --git a/frontend/scripts/componentSchemas/run.ts b/frontend/scripts/componentSchemas/run.ts index e6fd14cc3b4..ae66d962d53 100644 --- a/frontend/scripts/componentSchemas/run.ts +++ b/frontend/scripts/componentSchemas/run.ts @@ -1,98 +1,19 @@ -import { expandAllOf, expandAnyOf, expandRefsInProperties, verifySchema } from './schemaUtils'; +import { allPropertyKeys, generateComponentSchema } from './schemaUtils'; import type { AppFrontendVersion } from './version'; -import { isValidVersion, versionSettings } from './version'; +import { isValidVersion } from './version'; import { getLayoutSchema } from './api'; -import { logComponentPropertyLabels, logTextResourceLabels } from './languageUtils'; - -const allTextResourceBindingKeys = []; -const allPropertyKeys = []; - -const writeToFile = (name: string, data: any, version: AppFrontendVersion) => { - const path = require('path'); - const fs = require('fs'); - - const dirPath = path.resolve(__dirname, versionSettings[version].componentSchemaPath); - const fileName = `${dirPath}/${name}.schema.v1.json`; - - fs.writeFile(fileName, JSON.stringify(data), function (err: any) { - if (err) return console.log(err); - console.log(`Wrote ${fileName}`); - }); -}; - -const addTextResourceBindingKeys = (schema: any) => { - if (schema.properties?.textResourceBindings) { - const textResourceBindingKeys = Object.keys(schema.properties.textResourceBindings.properties); - allTextResourceBindingKeys.push(...textResourceBindingKeys); - } -}; - -const addProperties = (propertyKeys: string[]) => { - allPropertyKeys.push(...propertyKeys); -}; - -const generateComponentSchema = (name: string, layoutSchema: any, version: string) => { - const definitionName = `Comp${name}`; - console.log('definitionName: ', definitionName); - const componentSchema = layoutSchema.definitions[definitionName]; - let schema: any = { - $id: `https://altinncdn.no/schemas/json/component/${name}.schema.v1.json`, - $schema: layoutSchema.$schema, - }; - - // The v4 schema has external definitions. This code block is needed to fetch v4 properties correctly. - const externalDefinitionName = definitionName + 'External'; - if (version == 'v4' && layoutSchema.definitions[externalDefinitionName]?.allOf) { - componentSchema.allOf = layoutSchema.definitions[externalDefinitionName].allOf; - } - - if (componentSchema.allOf) { - schema = { ...schema, ...expandAllOf(componentSchema, layoutSchema) }; - const expectedProperties = Object.keys( - componentSchema.allOf[componentSchema.allOf.length - 1].properties, - ); - addProperties(expectedProperties); - - if ( - !verifySchema( - schema, - Object.keys(componentSchema.allOf[componentSchema.allOf.length - 1].properties), - ) - ) { - return null; - } - } else if (componentSchema.anyOf) { - schema.anyOf = expandAnyOf(componentSchema, layoutSchema); - } - - // Expand all refs in properties - schema.properties = expandRefsInProperties(schema.properties, layoutSchema); - - // Sort text resource binding keys - if (schema.properties?.textResourceBindings) { - schema.properties.textResourceBindings.properties = sortTextResourceBindings( - schema.properties.textResourceBindings.properties, - ); - } - schema.title = `${name} component schema`; - return schema; -}; - -const sortTextResourceBindings = (textResourceBindings: any) => { - const { title, description, help, ...rest } = textResourceBindings; - const sorted: any = {}; - if (title) { - sorted.title = title; - } - if (description) { - sorted.description = description; - } - if (help) { - sorted.help = help; - } - return { ...sorted, ...rest }; -}; - +import { + addTextResourceBindingKeys, + allTextResourceBindingKeys, + logComponentPropertyLabels, + logTextResourceLabels, +} from './languageUtils'; +import { writeToFile } from './fileUtils'; + +/** + * Main function that runs the script. + * Fetches the layout schema, generates component schemas, and logs language keys. + */ const run = async () => { let version: string = process.argv.length > 2 ? process.argv[2] : ''; if (!isValidVersion(version)) { diff --git a/frontend/scripts/componentSchemas/schemaUtils.ts b/frontend/scripts/componentSchemas/schemaUtils.ts index 3b1e29922c3..31b7c545f8e 100644 --- a/frontend/scripts/componentSchemas/schemaUtils.ts +++ b/frontend/scripts/componentSchemas/schemaUtils.ts @@ -1,4 +1,17 @@ import jsonpointer from 'jsonpointer'; +import { sortTextResourceBindings } from './languageUtils'; + +export const allPropertyKeys = []; + +/** + * Expands a ref in a schema from a reference to the actual schema + * @param ref The ref to expand + * @param layoutSchema The full layout schema + * @returns The expanded schema + */ +export const expandRef = (ref: string, layoutSchema: any) => { + return jsonpointer.get(layoutSchema, ref.replace('#/', '/')); +}; /** * Expands allOf node in schema by combining all properties together in one object. @@ -52,24 +65,55 @@ export const expandAnyOf = (schema: any, layoutSchema: any) => { }; /** - * Expands a ref in a schema from a reference to the actual schema - * @param ref The ref to expand + * Expands all refs in properties in a schema + * @param properties The properties to expand * @param layoutSchema The full layout schema - * @returns The expanded schema + * @returns The expanded properties */ -export const expandRef = (ref: string, layoutSchema: any) => { - return jsonpointer.get(layoutSchema, ref.replace('#/', '/')); +export const expandRefsInProperties = (properties: any, layoutSchema: any) => { + const expandedProperties = { ...properties }; + for (const property in properties) { + if (expandedProperties[property].$ref && expandedProperties[property].$ref.startsWith('#/')) { + expandedProperties[property] = expandRef(expandedProperties[property].$ref, layoutSchema); + } + if (expandedProperties[property].items?.$ref) { + expandedProperties[property].items = expandRef( + expandedProperties[property].items.$ref, + layoutSchema, + ); + } + if (expandedProperties[property].allOf) { + expandedProperties[property] = expandAllOf(expandedProperties[property], layoutSchema); + } + + if (expandedProperties[property].anyOf) { + expandedProperties[property].anyOf = expandAnyOf(expandedProperties[property], layoutSchema); + } + ensureTypeWithEnums(expandedProperties[property]); + } + + return expandedProperties; }; /** - * Ensures that a schema with enum values has type string - * @param schema The schema to ensure string type for + * Ensures that a schema with enum values has correct type (string or number) + * @param schema The schema to ensure type for */ -export const ensureStringTypeWithEnums = (schema: any) => { - if (schema.enum) { - schema.type = 'string'; - } else if (schema.items?.enum) { - schema.items.type = 'string'; +export const ensureTypeWithEnums = (schema: any) => { + if (schema.enum && schema.enum.length > 0) { + const firstEnumValue = schema.enum[0]; + if (typeof firstEnumValue === 'string') { + schema.type = 'string'; + } else if (typeof firstEnumValue === 'number') { + schema.type = 'number'; + } + } else if (schema.items?.enum && schema.items.enum.length > 0) { + const firstEnumValue = schema.items.enum[0]; + if (typeof firstEnumValue === 'string') { + schema.items.type = 'string'; + } else if (typeof firstEnumValue === 'number') { + schema.items.type = 'number'; + } } }; @@ -88,37 +132,78 @@ export const verifySchema = (schema: any, expectedProperties: string[]) => { console.log(`Missing properties: ${missingProperties.join(', ')}`); return false; } - return true; }; /** - * Expands all refs in properties in a schema - * @param properties The properties to expand - * @param layoutSchema The full layout schema - * @returns The expanded properties + * Adds property keys to the global list. + * @param propertyKeys Array of property keys to add. */ -export const expandRefsInProperties = (properties: any, layoutSchema: any) => { - const expandedProperties = { ...properties }; - for (const property in properties) { - if (expandedProperties[property].$ref && expandedProperties[property].$ref.startsWith('#/')) { - expandedProperties[property] = expandRef(expandedProperties[property].$ref, layoutSchema); - } - if (expandedProperties[property].items?.$ref) { - expandedProperties[property].items = expandRef( - expandedProperties[property].items.$ref, - layoutSchema, - ); - } - if (expandedProperties[property].allOf) { - expandedProperties[property] = expandAllOf(expandedProperties[property], layoutSchema); - } +const addProperties = (propertyKeys: string[]) => { + allPropertyKeys.push(...propertyKeys); +}; - if (expandedProperties[property].anyOf) { - expandedProperties[property].anyOf = expandAnyOf(expandedProperties[property], layoutSchema); +/** + * Retrieves the component schema based on the version (the v4 schema has an external reference) + * @param definitionName The name of the component definition. + * @param layoutSchema The full layout schema. + * @param version The app frontend version. + * @returns The component schema. + */ + +const getComponentSchema = (definitionName: string, layoutSchema: any, version: string) => { + if (version === 'v4') { + console.log('definitionName: ', definitionName + 'External'); + return expandRef(layoutSchema.definitions[definitionName].$ref, layoutSchema); + } + console.log('definitionName: ', definitionName); + return layoutSchema.definitions[definitionName]; +}; + +/** + * Generates a component schema by expanding definitions and resolving references. + * @param componentName The name of the component. + * @param layoutSchema The full layout schema. + * @param version The app frontend version. + * @returns The generated component schema. + */ +export const generateComponentSchema = ( + componentName: string, + layoutSchema: any, + version: string, +) => { + const definitionName = `Comp${componentName}`; + const componentSchema = getComponentSchema(definitionName, layoutSchema, version); + + let schema: any = { + $id: `https://altinncdn.no/schemas/json/component/${componentName}.schema.v1.json`, + $schema: layoutSchema.$schema, + }; + + if (componentSchema.allOf) { + schema = { ...schema, ...expandAllOf(componentSchema, layoutSchema) }; + const expectedProperties = Object.keys( + componentSchema.allOf[componentSchema.allOf.length - 1].properties, + ); + addProperties(expectedProperties); + + if (!verifySchema(schema, expectedProperties)) { + return null; } - ensureStringTypeWithEnums(expandedProperties[property]); + } else if (componentSchema.anyOf) { + schema.anyOf = expandAnyOf(componentSchema, layoutSchema); } - return expandedProperties; + // Expand all refs in properties + schema.properties = expandRefsInProperties(schema.properties, layoutSchema); + + // Sort text resource binding keys + if (schema.properties?.textResourceBindings) { + schema.properties.textResourceBindings.properties = sortTextResourceBindings( + schema.properties.textResourceBindings.properties, + ); + } + + schema.title = `${componentName} component schema`; + return schema; }; diff --git a/frontend/scripts/componentSchemas/version.ts b/frontend/scripts/componentSchemas/version.ts index dc10b28c7cf..971da9f065e 100644 --- a/frontend/scripts/componentSchemas/version.ts +++ b/frontend/scripts/componentSchemas/version.ts @@ -12,7 +12,8 @@ export const versionSettings = { componentSchemaPath: '../../packages/ux-editor/src/testing/schemas/json/component', }, }; -export const isValidVersion = (version: string) => - validVersions.includes(version as AppFrontendVersion); + export const validVersions = ['v3', 'v4'] as const; export type AppFrontendVersion = (typeof validVersions)[number]; +export const isValidVersion = (version: string) => + validVersions.includes(version as AppFrontendVersion);