Skip to content

Commit

Permalink
Merge branch 'main' into 13476-support-full-config-subform-component
Browse files Browse the repository at this point in the history
  • Loading branch information
Jondyr authored Oct 17, 2024
2 parents f40810d + 8889dbf commit e4c3af6
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 158 deletions.
44 changes: 36 additions & 8 deletions .github/workflows/deploy-repositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ on:
- 'charts/altinn-repositories-config/**'

workflow_dispatch:
inputs:
environments:
description: 'Environments to deploy to. Multiple environments can be specified by separating them with a comma.'
required: false
default: 'dev'

permissions:
id-token: write
Expand All @@ -22,45 +27,68 @@ jobs:
get-short-sha:
uses: ./.github/workflows/template-short-sha.yaml

docker-build-push:
construct-environments-array:
uses: ./.github/workflows/template-construct-environments.yaml
with:
environments: ${{ github.event.inputs.environments || 'dev,staging,prod' }}

determine-tag:
needs: get-short-sha
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.determine-tag.outputs.tag }}
steps:
- name: Determine tag
id: determine-tag
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.get-short-sha.outputs.short-sha }}" >> $GITHUB_OUTPUT
else
sanitized_branch_name=$(echo "${{ github.ref_name }}" | tr -d '()' | tr '/' '-')
echo "tag=${sanitized_branch_name}-${{ needs.get-short-sha.outputs.short-sha }}" >> $GITHUB_OUTPUT
fi
docker-build-push:
needs: [get-short-sha, determine-tag]
uses: ./.github/workflows/template-docker-push.yaml
with:
tags: ${{ needs.get-short-sha.outputs.short-sha }},latest
tags: ${{ needs.determine-tag.outputs.tag }},latest
registry-name: altinntjenestercontainerregistry.azurecr.io
repository-name: altinn-repositories
context: gitea
dockerfile: gitea/Dockerfile
environment: dev # dev environment has push access and doesn't require review
secrets:
client-id: ${{ secrets.AZURE_CLIENT_ID_FC }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_FC }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_FC }}

helm-push:
needs: get-short-sha
needs: [get-short-sha, determine-tag]
uses: ./.github/workflows/template-helm-push.yaml
with:
tag: 0.1.0+${{ needs.get-short-sha.outputs.short-sha }} # Helm version needs to be valid sematic version
tag: 0.1.0+${{ needs.determine-tag.outputs.tag }} # Helm version needs to be valid semantic version
chart-name: altinn-repositories
registry-name: altinntjenestercontainerregistry.azurecr.io
environment: dev # dev environment has push access and doesn't require review
secrets:
client-id: ${{ secrets.AZURE_CLIENT_ID_FC }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_FC }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_FC }}

flux-config-push:
needs: [get-short-sha, docker-build-push, helm-push]
needs: [construct-environments-array, get-short-sha, determine-tag, docker-build-push, helm-push]
strategy:
matrix:
environment: [dev, staging, prod]
environment: ${{ fromJSON(needs.construct-environments-array.outputs.environmentsjson) }}
uses: ./.github/workflows/template-flux-config-push.yaml
with:
tag: ${{ needs.get-short-sha.outputs.short-sha }}
tag: ${{ needs.determine-tag.outputs.tag }}
registry-name: altinntjenestercontainerregistry.azurecr.io
environment: ${{ matrix.environment }}
config-chart-name: altinn-repositories-config
artifact-name: altinn-repositories
helm-set-arguments: environmentName=${{ matrix.environment }},chartVersion=0.1.0+${{ needs.get-short-sha.outputs.short-sha }},imageTag=${{ needs.get-short-sha.outputs.short-sha }}
helm-set-arguments: environmentName=${{ matrix.environment }},chartVersion=0.1.0+${{ needs.determine-tag.outputs.tag }},imageTag=${{ needs.determine-tag.outputs.tag }}
secrets:
client-id: ${{ secrets.AZURE_CLIENT_ID_FC }}
tenant-id: ${{ secrets.AZURE_TENANT_ID_FC }}
Expand Down
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
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: string[] = [];

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

0 comments on commit e4c3af6

Please sign in to comment.