Merge pull request #8936 from backstage/freben/healer

Clear parent entity hashes on deletion, to get healing behaviors back
This commit is contained in:
Fredrik Adelöw
2022-01-14 17:14:02 +01:00
committed by GitHub
3 changed files with 95 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Now when entities are deleted, the parent entity state is updated such that it will "heal" accidental deletes on the next refresh round.
@@ -72,6 +72,8 @@ describe('NextEntitiesCatalog', () => {
target_entity_ref: stringifyEntityRef(entity),
});
}
return id;
}
async function addEntityToSearch(knex: Knex, entity: Entity) {
@@ -468,4 +470,69 @@ describe('NextEntitiesCatalog', () => {
},
);
});
describe('removeEntityByUid', () => {
it.each(databases.eachSupportedId())(
'also clears parent hashes',
async databaseId => {
const { knex } = await createDatabase(databaseId);
const grandparent: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'grandparent' },
spec: {},
};
const parent1: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'parent1' },
spec: {},
};
const parent2: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'parent2' },
spec: {},
};
const root: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'root' },
spec: {},
};
const unrelated: Entity = {
apiVersion: 'a',
kind: 'k',
metadata: { name: 'unrelated' },
spec: {},
};
await addEntity(knex, grandparent, [{ source: 's' }]);
await addEntity(knex, parent1, [{ entity: grandparent }]);
await addEntity(knex, parent2, [{ entity: grandparent }]);
const uid = await addEntity(knex, root, [
{ entity: parent1 },
{ entity: parent2 },
]);
await addEntity(knex, unrelated, []);
await knex('refresh_state').update({ result_hash: 'not-changed' });
const catalog = new NextEntitiesCatalog(knex);
await catalog.removeEntityByUid(uid);
await expect(
knex
.from('refresh_state')
.select('entity_ref', 'result_hash')
.orderBy('entity_ref'),
).resolves.toEqual([
{ entity_ref: 'k:default/grandparent', result_hash: 'not-changed' },
{ entity_ref: 'k:default/parent1', result_hash: 'child-was-deleted' },
{ entity_ref: 'k:default/parent2', result_hash: 'child-was-deleted' },
{ entity_ref: 'k:default/unrelated', result_hash: 'not-changed' },
]);
},
);
});
});
@@ -204,6 +204,29 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
}
async removeEntityByUid(uid: string): Promise<void> {
// Clear the hashed state of the immediate parents of the deleted entity.
// This makes sure that when they get reprocessed, their output is written
// down again. The reason for wanting to do this, is that if the user
// deletes entities that ARE still emitted by the parent, the parent
// processing will still generate the same output hash as always, which
// means it'll never try to write down the children again (it assumes that
// they already exist). This means that without the code below, the database
// never "heals" from accidental deletes.
await this.database<DbRefreshStateRow>('refresh_state')
.update({
result_hash: 'child-was-deleted',
})
.whereIn('entity_ref', function parents(builder) {
return builder
.from<DbRefreshStateRow>('refresh_state')
.innerJoin<DbRefreshStateReferencesRow>('refresh_state_references', {
'refresh_state_references.target_entity_ref':
'refresh_state.entity_ref',
})
.where('refresh_state.entity_id', '=', uid)
.select('refresh_state_references.source_entity_ref');
});
await this.database<DbRefreshStateRow>('refresh_state')
.where('entity_id', uid)
.delete();