Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Refactor component script and check if enum contents are numbers #13694

Merged
merged 16 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"default": false,
"$ref": "expression.schema.v1.json#/definitions/boolean"
},
"headingLevel": { "title": "HeadingLevel", "enum": [2, 3, 4, 5, 6], "type": "string" }
"headingLevel": { "title": "HeadingLevel", "enum": [2, 3, 4, 5, 6], "type": "number" }
},
"required": ["id", "type", "children"],
"title": "Accordion component schema"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"title": "Heading level",
"description": "The heading level of the group title.",
"enum": [2, 3, 4, 5, 6],
"type": "string"
"type": "number"
}
},
"required": ["id", "type", "children"],
Expand Down
4 changes: 2 additions & 2 deletions frontend/scripts/componentSchemas/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import axios from 'axios';
import type { AppFrontendVersion } from './version';
import type { AppFrontendVersion, LayoutSchema } from './types';
import { versionSettings } from './version';

export const getLayoutSchema = async (version?: AppFrontendVersion) => {
export const getLayoutSchema = async (version?: AppFrontendVersion): Promise<LayoutSchema> => {
const response = await axios.get(versionSettings[version || 'v4'].layoutSchemaUrl);
return response?.data;
};
Expand Down
21 changes: 21 additions & 0 deletions frontend/scripts/componentSchemas/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AppFrontendVersion, ComponentName, ExpandedComponentSchema } from './types';
import { versionSettings } from './version';
import path from 'path';
import fs from 'fs';

export const writeToFile = (
name: ComponentName,
data: ExpandedComponentSchema,
version: AppFrontendVersion,
) => {
const dirPath = path.resolve(__dirname, versionSettings[version].componentSchemaPath);
const fileName = `${dirPath}/${name}.schema.v1.json`;

fs.writeFile(fileName, JSON.stringify(data), (err) => {
if (err) {
console.log(err);
return;
}
console.log(`Wrote ${fileName}`);
});
};
40 changes: 36 additions & 4 deletions frontend/scripts/componentSchemas/languageUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
import nb from '../../language/src/nb.json';
import type { ExpandedComponentSchema } from './types';
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';

// 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 = [];

export const pushTextResourceBindingKeys = (schema: ExpandedComponentSchema) => {
if (schema.properties?.textResourceBindings) {
const textResourceBindingKeys = Object.keys(schema.properties.textResourceBindings.properties);
allTextResourceBindingKeys.push(...textResourceBindingKeys);
}
};

export const sortTextResourceBindings = (textResourceBindings: KeyValuePairs): KeyValuePairs => {
const { title, description, help, ...rest } = textResourceBindings;
const sorted: KeyValuePairs = {};
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(
Expand All @@ -13,8 +42,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(
Expand Down
115 changes: 18 additions & 97 deletions frontend/scripts/componentSchemas/run.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,14 @@
import { expandAllOf, expandAnyOf, expandRefsInProperties, verifySchema } from './schemaUtils';
import type { AppFrontendVersion } from './version';
import { isValidVersion, versionSettings } from './version';
import { allPropertyKeys, generateComponentSchema } from './schemaUtils';
import type { AppFrontendVersion, ComponentName } from './types';
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 {
pushTextResourceBindingKeys,
allTextResourceBindingKeys,
logComponentPropertyLabels,
logTextResourceLabels,
} from './languageUtils';
import { writeToFile } from './fileUtils';

const run = async () => {
let version: string = process.argv.length > 2 ? process.argv[2] : '';
Expand All @@ -102,15 +19,19 @@ const run = async () => {
version = 'v4';
}

const layoutSchema: any = await getLayoutSchema(version as AppFrontendVersion);
const layoutSchema = await getLayoutSchema(version as AppFrontendVersion);
const allComponents = layoutSchema.definitions.AnyComponent.properties.type.enum;

allComponents.forEach((componentName: string) => {
componentName = componentName === 'AddressComponent' ? 'Address' : componentName;

const schema = generateComponentSchema(componentName, layoutSchema, version);
addTextResourceBindingKeys(schema);
writeToFile(componentName, schema, version as AppFrontendVersion);
const schema = generateComponentSchema(
componentName as ComponentName,
layoutSchema,
version as AppFrontendVersion,
);
pushTextResourceBindingKeys(schema);
writeToFile(componentName as ComponentName, schema, version as AppFrontendVersion);
});

const uniqueTextResourceBindingKeys = [...new Set(allTextResourceBindingKeys)];
Expand Down
66 changes: 66 additions & 0 deletions frontend/scripts/componentSchemas/schemaUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ensureTypeWithEnums } from './schemaUtils';
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';

describe('ensureTypeWithEnums', () => {
it('should set schema.type to "string" when schema.enum contains a string', () => {
const schema: KeyValuePairs = {
enum: ['value1', 'value2'],
};
ensureTypeWithEnums(schema);
expect(schema.type).toBe('string');
});

it('should set schema.type to "number" when schema.enum contains a number', () => {
const schema: KeyValuePairs = {
enum: [1, 2, 3],
};
ensureTypeWithEnums(schema);
expect(schema.type).toBe('number');
});

it('should set schema.items.type to "string" when schema.items.enum contains a string', () => {
const schema: KeyValuePairs = {
items: {
enum: ['item1', 'item2'],
},
};
ensureTypeWithEnums(schema);
expect(schema.items.type).toBe('string');
});

it('should set schema.items.type to "number" when schema.items.enum contains a number', () => {
const schema: KeyValuePairs = {
items: {
enum: [10, 20, 30],
},
};
ensureTypeWithEnums(schema);
expect(schema.items.type).toBe('number');
});

it('should not set schema.type when schema.enum is empty', () => {
const schema: KeyValuePairs = {
enum: [],
};
ensureTypeWithEnums(schema);
expect(schema.type).toBeUndefined();
});

it('should not set schema.items.type when schema.items.enum is empty', () => {
const schema: KeyValuePairs = {
items: {
enum: [],
},
};
ensureTypeWithEnums(schema);
expect(schema.items.type).toBeUndefined();
});

it('should not modify schema if there is no enum or items.enum', () => {
const schema: KeyValuePairs = {
type: 'array',
};
ensureTypeWithEnums(schema);
expect(schema).toEqual({ type: 'array' });
});
});
Loading