diff --git a/.changeset/shiny-apes-design.md b/.changeset/shiny-apes-design.md new file mode 100644 index 0000000000..69dfbf57ab --- /dev/null +++ b/.changeset/shiny-apes-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Updated parseEntityTransformParams to handle keys with '.' in them. This will allow for querying of entities based off annotations such as 'backstage.io/orgin-location' or other entity field keys that have '.' in them. diff --git a/plugins/catalog-backend/src/service/request/parseEntityTransformParams.test.ts b/plugins/catalog-backend/src/service/request/parseEntityTransformParams.test.ts index 1d5d41fc03..3dce579ae5 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityTransformParams.test.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityTransformParams.test.ts @@ -24,6 +24,11 @@ describe('parseEntityTransformParams', () => { metadata: { name: 'n', tags: ['t1', 't2'], + annotations: { + 'example.test/url-like-key': 'ul1', + 'example.com/other-url-like-key': 'ul2', + 'other-example.test/next-url-like-key': 'ul3', + }, }, spec: { type: 't', @@ -61,7 +66,115 @@ describe('parseEntityTransformParams', () => { parseEntityTransformParams({ fields: 'kind,metadata.name' })!(entity), ).toEqual({ kind: 'k', metadata: { name: 'n' } }); expect(parseEntityTransformParams({ fields: 'metadata' })!(entity)).toEqual( - { metadata: { name: 'n', tags: ['t1', 't2'] } }, + { + metadata: { + name: 'n', + tags: ['t1', 't2'], + annotations: { + 'example.test/url-like-key': 'ul1', + 'example.com/other-url-like-key': 'ul2', + 'other-example.test/next-url-like-key': 'ul3', + }, + }, + }, ); }); + + it('supports dot notated fields properly', () => { + expect( + parseEntityTransformParams({ + fields: 'kind,metadata.annotations.example.com/other-url-like-key', + })!(entity), + ).toEqual({ + kind: 'k', + metadata: { annotations: { 'example.com/other-url-like-key': 'ul2' } }, + }); + }); + + it('supports nested dot notated fields properly', () => { + entity.spec = { + ...entity.spec, + 'field-with.dot': 'fd1', + 'other-field-with.dot-also': { + subItem: 'fd2.sub', + 'subite.with/dot': 'fd2.sub.dot', + }, + 'third-field-with.dot-again': 'fd3', + }; + + expect( + parseEntityTransformParams({ + fields: 'kind,spec.other-field-with.dot-also', + })!(entity), + ).toEqual({ + kind: 'k', + spec: { + 'other-field-with.dot-also': { + subItem: 'fd2.sub', + 'subite.with/dot': 'fd2.sub.dot', + }, + }, + }); + expect( + parseEntityTransformParams({ + fields: 'kind,spec.other-field-with.dot-also.subite.with/dot', + })!(entity), + ).toEqual({ + kind: 'k', + spec: { + 'other-field-with.dot-also': { + 'subite.with/dot': 'fd2.sub.dot', + }, + }, + }); + }); + + it('does not return a sub key if an incorrect longer key is requested', () => { + entity.spec = { + ...entity.spec, + strValue: 'st1', + boolValue: true, + numValue: 4, + arrValue: [4, 5], + nullValue: null, + undefValue: undefined, + 'field-with.dot': 'fd1', + }; + + expect( + parseEntityTransformParams({ fields: 'kind,spec.strValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.boolValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.numValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.arrValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.nullValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.undefValue.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + expect( + parseEntityTransformParams({ fields: 'kind,spec.field-with.dot.other' })!( + entity, + ), + ).toEqual({ kind: 'k' }); + }); }); diff --git a/plugins/catalog-backend/src/service/request/parseEntityTransformParams.ts b/plugins/catalog-backend/src/service/request/parseEntityTransformParams.ts index 9935da07a8..cef6e5ef64 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityTransformParams.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityTransformParams.ts @@ -20,6 +20,22 @@ import lodash from 'lodash'; import { RecursivePartial } from '../../util/RecursivePartial'; import { parseStringsParam } from './common'; +function getPathArrayAndValue(input: Entity, field: string) { + return field.split('.').reduce( + ([pathArray, inputSubset], pathPart, index, fieldParts) => { + if (lodash.hasIn(inputSubset, pathPart)) { + return [pathArray.concat(pathPart), inputSubset[pathPart]]; + } else if (fieldParts[index + 1] !== undefined) { + fieldParts[index + 1] = `${pathPart}.${fieldParts[index + 1]}`; + return [pathArray, inputSubset]; + } + + return [pathArray, undefined]; + }, + [[] as string[], input as any], + ); +} + export function parseEntityTransformParams( params: Record, ): ((entity: Entity) => Entity) | undefined { @@ -46,9 +62,10 @@ export function parseEntityTransformParams( const output: RecursivePartial = {}; for (const field of fields) { - const value = lodash.get(input, field); + const [pathArray, value] = getPathArrayAndValue(input, field); + if (value !== undefined) { - lodash.set(output, field, value); + lodash.set(output, pathArray, value); } }