github: stabilize entity order in multi-org provider
When using GithubMultiOrgEntityProvider with alwaysUseDefaultNamespace and teams with identical slugs across orgs, the emitted entity order was non-deterministic. Since the catalog's upsert path uses last-write- wins for duplicate entity refs, this caused the winning org to flip randomly on every refresh cycle, producing constant unnecessary stitching and flickering entity data. Sort the emitted entities by entity ref (primary) and location annotation (tiebreaker) so that the same org consistently wins when duplicate refs exist. Fixes #34263 Signed-off-by: Fredrik Adelöw <freben@gmail.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
---
|
||||
|
||||
The `GithubMultiOrgEntityProvider` now emits entities in a stable order during full mutations. Entities are sorted by entity ref, with the location annotation as a tiebreaker for entities that share the same ref. This prevents entity data from flickering between different GitHub orgs across refresh cycles when `alwaysUseDefaultNamespace` is enabled and teams with identical slugs exist in multiple orgs.
|
||||
+6
-6
@@ -212,7 +212,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
});
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
entities: [
|
||||
entities: expect.arrayContaining([
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -352,7 +352,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
},
|
||||
locationKey: 'github-multi-org-provider:my-id',
|
||||
},
|
||||
],
|
||||
]),
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
@@ -526,7 +526,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
});
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
entities: [
|
||||
entities: expect.arrayContaining([
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -666,7 +666,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
},
|
||||
locationKey: 'github-multi-org-provider:my-id',
|
||||
},
|
||||
],
|
||||
]),
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
@@ -804,7 +804,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
await entityProvider.read();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
entities: [
|
||||
entities: expect.arrayContaining([
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -942,7 +942,7 @@ describe('GithubMultiOrgEntityProvider', () => {
|
||||
},
|
||||
locationKey: 'github-multi-org-provider:my-id',
|
||||
},
|
||||
],
|
||||
]),
|
||||
type: 'full',
|
||||
});
|
||||
});
|
||||
|
||||
+24
-7
@@ -351,15 +351,32 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
|
||||
const { markCommitComplete } = markReadComplete({ allUsers, allTeams });
|
||||
|
||||
const allEntities = [...allUsers, ...allTeams].map(entity => ({
|
||||
locationKey: `github-multi-org-provider:${this.options.id}`,
|
||||
entity: withLocations(
|
||||
`https://${this.options.gitHubConfig.host}`,
|
||||
entity,
|
||||
),
|
||||
}));
|
||||
allEntities.sort((a, b) => {
|
||||
const am = a.entity.metadata;
|
||||
const bm = b.entity.metadata;
|
||||
if (am.name !== bm.name) return am.name < bm.name ? -1 : 1;
|
||||
const al = am.annotations?.[ANNOTATION_LOCATION] ?? '';
|
||||
const bl = bm.annotations?.[ANNOTATION_LOCATION] ?? '';
|
||||
if (al !== bl) return al < bl ? -1 : 1;
|
||||
if (a.entity.kind !== b.entity.kind) {
|
||||
return a.entity.kind < b.entity.kind ? -1 : 1;
|
||||
}
|
||||
const ans = am.namespace ?? '';
|
||||
const bns = bm.namespace ?? '';
|
||||
if (ans !== bns) return ans < bns ? -1 : 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
await this.connection.applyMutation({
|
||||
type: 'full',
|
||||
entities: [...allUsers, ...allTeams].map(entity => ({
|
||||
locationKey: `github-multi-org-provider:${this.options.id}`,
|
||||
entity: withLocations(
|
||||
`https://${this.options.gitHubConfig.host}`,
|
||||
entity,
|
||||
),
|
||||
})),
|
||||
entities: allEntities,
|
||||
});
|
||||
|
||||
markCommitComplete();
|
||||
|
||||
Reference in New Issue
Block a user