Fixes #27325 Stitch entity for which target of relationship has changed

Signed-off-by: Jordan Slott <jordan.slott@twosigma.com>
This commit is contained in:
Jordan Slott
2024-10-31 17:03:55 -04:00
parent 5077e1262d
commit b89834bfa6
3 changed files with 116 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Fixed an issue where entities would not be marked for restitching if only the target of a relationship changed.
@@ -464,6 +464,111 @@ describe('DefaultCatalogProcessingEngine', () => {
await engine.stop();
});
it('should stitch both the previous and new sources when relation target changes', async () => {
const engine = new DefaultCatalogProcessingEngine({
config: new ConfigReader({}),
logger: mockServices.logger.mock(),
processingDatabase: db,
knex: {} as any,
orchestrator: orchestrator,
stitcher: stitcher,
createHash: () => hash,
pollingIntervalMs: 100,
});
db.transaction.mockImplementation(cb => cb((() => {}) as any));
const entity = {
apiVersion: '1',
kind: 'k',
metadata: { name: 'me', namespace: 'ns' },
};
const processableEntity = {
entityRef: 'foo',
id: '1',
unprocessedEntity: entity,
resultHash: '',
state: [] as any,
nextUpdateAt: DateTime.now(),
lastDiscoveryAt: DateTime.now(),
};
db.listParents.mockResolvedValue({ entityRefs: [] });
db.getProcessableEntities
.mockResolvedValueOnce({
items: [processableEntity],
})
.mockResolvedValueOnce({
items: [processableEntity],
});
db.updateProcessedEntity
.mockImplementationOnce(async () => ({
previous: { relations: [] },
}))
.mockImplementationOnce(async () => ({
previous: {
relations: [
{
originating_entity_id: '',
type: 't',
source_entity_ref: 'k:ns/other1',
target_entity_ref: 'k:ns/me',
},
],
},
}));
orchestrator.process
.mockResolvedValueOnce({
ok: true,
completedEntity: entity,
relations: [
{
type: 't',
source: { kind: 'k', namespace: 'ns', name: 'other1' },
target: { kind: 'k', namespace: 'ns', name: 'me' },
},
],
errors: [],
deferredEntities: [],
state: {},
refreshKeys: [],
})
.mockResolvedValueOnce({
ok: true,
completedEntity: entity,
// change just the target of the relationship to a new entity,
// leaving the source and relation type the same.
// see: https://github.com/backstage/backstage/issues/27325
relations: [
{
type: 't',
source: { kind: 'k', namespace: 'ns', name: 'other1' },
target: { kind: 'k', namespace: 'ns', name: 'newtarget' },
},
],
errors: [],
deferredEntities: [],
state: {},
refreshKeys: [],
});
await engine.start();
await waitForExpect(() => {
expect(stitcher.stitch).toHaveBeenCalledTimes(2);
});
expect([...stitcher.stitch.mock.calls[0][0].entityRefs!]).toEqual(
expect.arrayContaining(['k:ns/me', 'k:ns/other1']),
);
// As a result of switching the relationship for source other1 to
// a new target entity, the other1 relationship source must be
// restitched.
expect([...stitcher.stitch.mock.calls[1][0].entityRefs!]).toEqual(
expect.arrayContaining(['k:ns/me', 'k:ns/other1']),
);
await engine.stop();
});
it('should not stitch sources entities when relations are the same', async () => {
const engine = new DefaultCatalogProcessingEngine({
config: new ConfigReader({}),
@@ -295,7 +295,7 @@ export class DefaultCatalogProcessingEngine {
});
oldRelationSources = new Map(
previous.relations.map(r => [
`${r.source_entity_ref}:${r.type}`,
`${r.source_entity_ref}:${r.type}->${r.target_entity_ref}`,
r.source_entity_ref,
]),
);
@@ -304,7 +304,11 @@ export class DefaultCatalogProcessingEngine {
const newRelationSources = new Map<string, string>(
result.relations.map(relation => {
const sourceEntityRef = stringifyEntityRef(relation.source);
return [`${sourceEntityRef}:${relation.type}`, sourceEntityRef];
const targetEntityRef = stringifyEntityRef(relation.target);
return [
`${sourceEntityRef}:${relation.type}->${targetEntityRef}`,
sourceEntityRef,
];
}),
);