diff --git a/.changeset/young-ants-nail.md b/.changeset/young-ants-nail.md new file mode 100644 index 0000000000..6648098687 --- /dev/null +++ b/.changeset/young-ants-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Allow github user and team transforms to return any Entity diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 7b944b33a9..12ab6d35e7 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -16,7 +16,6 @@ import { EventSubscriber } from '@backstage/plugin-events-node'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GithubIntegrationConfig } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; -import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -25,7 +24,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; import { TokenManager } from '@backstage/backend-common'; -import { UserEntity } from '@backstage/catalog-model'; // @public export const defaultOrganizationTeamTransformer: TeamTransformer; @@ -303,7 +301,7 @@ export type GithubUser = { export type TeamTransformer = ( item: GithubTeam, ctx: TransformerContext, -) => Promise; +) => Promise; // @public export interface TransformerContext { @@ -319,5 +317,5 @@ export interface TransformerContext { export type UserTransformer = ( item: GithubUser, ctx: TransformerContext, -) => Promise; +) => Promise; ``` diff --git a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts index 7669fd5b47..71613d88d2 100644 --- a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts +++ b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { Entity, GroupEntity, UserEntity } from '@backstage/catalog-model'; import { graphql } from '@octokit/graphql'; import { ANNOTATION_GITHUB_TEAM_SLUG, @@ -34,24 +34,24 @@ export interface TransformerContext { } /** - * Transformer for GitHub users to UserEntity + * Transformer for GitHub users to an Entity * * @public */ export type UserTransformer = ( item: GithubUser, ctx: TransformerContext, -) => Promise; +) => Promise; /** - * Transformer for GitHub Team to GroupEntity + * Transformer for GitHub Team to an Entity * * @public */ export type TeamTransformer = ( item: GithubTeam, ctx: TransformerContext, -) => Promise; +) => Promise; /** * Default transformer for GitHub users to UserEntity diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index 7a0a29e098..d83e8031cc 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -229,7 +229,7 @@ describe('github', () => { it('reads teams', async () => { const output = { - groups: [ + teams: [ expect.objectContaining({ metadata: expect.objectContaining({ name: 'team', @@ -329,7 +329,7 @@ describe('github', () => { it('reads teams', async () => { const output = { - groups: [ + teams: [ expect.objectContaining({ metadata: expect.objectContaining({ name: 'Team-custom', @@ -409,7 +409,7 @@ describe('github', () => { }; const output = { - groups: [ + teams: [ expect.objectContaining({ metadata: expect.objectContaining({ name: 'Team-custom', @@ -444,7 +444,7 @@ describe('github', () => { customTeamTransformer, ); - expect(teams.groups).toHaveLength(1); + expect(teams.teams).toHaveLength(1); expect(teams).toEqual(output); }); }); diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 412b751c38..6b490f363a 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity, GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { GithubCredentialType } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; import { @@ -139,7 +139,7 @@ export async function getOrganizationUsers( org: string, tokenType: GithubCredentialType, userTransformer: UserTransformer = defaultUserTransformer, -): Promise<{ users: UserEntity[] }> { +): Promise<{ users: Entity[] }> { const query = ` query users($org: String!, $email: Boolean!, $cursor: String) { organization(login: $org) { @@ -188,7 +188,7 @@ export async function getOrganizationTeams( org: string, teamTransformer: TeamTransformer = defaultOrganizationTeamTransformer, ): Promise<{ - groups: GroupEntity[]; + teams: Entity[]; }> { const query = ` query teams($org: String!, $cursor: String) { @@ -222,7 +222,7 @@ export async function getOrganizationTeams( const materialisedTeams = async ( item: GithubTeamResponse, ctx: TransformerContext, - ): Promise => { + ): Promise => { const memberNames: GithubUser[] = []; if (!item.members.pageInfo.hasNextPage) { @@ -247,7 +247,7 @@ export async function getOrganizationTeams( return await teamTransformer(team, ctx); }; - const groups = await queryWithPaging( + const teams = await queryWithPaging( client, query, org, @@ -256,7 +256,7 @@ export async function getOrganizationTeams( { org }, ); - return { groups }; + return { teams }; } export async function getOrganizationTeamsFromUsers( @@ -265,7 +265,7 @@ export async function getOrganizationTeamsFromUsers( userLogins: string[], teamTransformer: TeamTransformer = defaultOrganizationTeamTransformer, ): Promise<{ - groups: GroupEntity[]; + teams: Entity[]; }> { const query = ` query teams($org: String!, $cursor: String, $userLogins: [String!] = "") { @@ -306,7 +306,7 @@ export async function getOrganizationTeamsFromUsers( const materialisedTeams = async ( item: GithubTeamResponse, ctx: TransformerContext, - ): Promise => { + ): Promise => { const memberNames: GithubUser[] = []; if (!item.members.pageInfo.hasNextPage) { @@ -331,7 +331,7 @@ export async function getOrganizationTeamsFromUsers( return await teamTransformer(team, ctx); }; - const groups = await queryWithPaging( + const teams = await queryWithPaging( client, query, org, @@ -340,7 +340,7 @@ export async function getOrganizationTeamsFromUsers( { org, userLogins }, ); - return { groups }; + return { teams }; } export async function getOrganizationsFromUser( @@ -377,7 +377,7 @@ export async function getOrganizationTeam( teamSlug: string, teamTransformer: TeamTransformer = defaultOrganizationTeamTransformer, ): Promise<{ - group: GroupEntity; + team: Entity; }> { const query = ` query teams($org: String!, $teamSlug: String!) { @@ -401,7 +401,7 @@ export async function getOrganizationTeam( const materialisedTeam = async ( item: GithubTeamResponse, ctx: TransformerContext, - ): Promise => { + ): Promise => { const memberNames: GithubUser[] = []; if (!item.members.pageInfo.hasNextPage) { @@ -432,17 +432,17 @@ export async function getOrganizationTeam( }); if (!response.organization?.team) - throw new Error(`Found no match for group ${teamSlug}`); + throw new Error(`Found no match for team ${teamSlug}`); - const group = await materialisedTeam(response.organization?.team, { + const team = await materialisedTeam(response.organization?.team, { query, client, org, }); - if (!group) throw new Error(`Can't transform for group ${teamSlug}`); + if (!team) throw new Error(`Can't transform for team ${teamSlug}`); - return { group }; + return { team }; } export async function getOrganizationRepositories( diff --git a/plugins/catalog-backend-module-github/src/lib/guards.ts b/plugins/catalog-backend-module-github/src/lib/guards.ts new file mode 100644 index 0000000000..f092d2365a --- /dev/null +++ b/plugins/catalog-backend-module-github/src/lib/guards.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Entity, + GroupEntity, + UserEntity, + isGroupEntity, + isUserEntity, +} from '@backstage/catalog-model'; + +export function areGroupEntities( + entities: Entity[], +): entities is GroupEntity[] { + return entities.every(e => isGroupEntity(e)); +} + +export function areUserEntities(entities: Entity[]): entities is UserEntity[] { + return entities.every(e => isUserEntity(e)); +} diff --git a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts index cf5e484fc2..8bfbd20e2e 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts @@ -16,9 +16,9 @@ import { DEFAULT_NAMESPACE, - GroupEntity, + Entity, + isGroupEntity, stringifyEntityRef, - UserEntity, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -49,6 +49,7 @@ import { TeamTransformer, UserTransformer, } from '../lib'; +import { areGroupEntities, areUserEntities } from '../lib/guards'; /** * Extracts teams and users out of a multiple GitHub orgs namespaced per org. @@ -147,7 +148,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { client, orgConfig.name, tokenType, - async (githubUser, ctx): Promise => { + async (githubUser, ctx): Promise => { const result = this.options.userTransformer ? await this.options.userTransformer(githubUser, ctx) : await defaultUserTransformer(githubUser, ctx); @@ -160,15 +161,15 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { }, ); - const { groups } = await getOrganizationTeams( + const { teams } = await getOrganizationTeams( client, orgConfig.name, - async (team, ctx): Promise => { + async (team, ctx): Promise => { const result = this.options.teamTransformer ? await this.options.teamTransformer(team, ctx) : await defaultOrganizationTeamTransformer(team, ctx); - if (result) { + if (result && isGroupEntity(result)) { result.metadata.namespace = orgConfig.groupNamespace; // Group `spec.members` inherits the namespace of it's group so need to explicitly specify refs here result.spec.members = team.members.map( @@ -185,7 +186,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1); this.logger.debug( - `Read ${users.length} GitHub users and ${groups.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`, + `Read ${users.length} GitHub users and ${teams.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`, ); // Grab current users from `allUsersMap` if they already exist in our @@ -199,11 +200,15 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { return allUsersMap.get(userRef); }); - assignGroupsToUsers(pendingUsers, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(pendingUsers)) { + assignGroupsToUsers(pendingUsers, teams); + } + } - for (const group of groups) { - emit(processingResult.entity(location, group)); + for (const team of teams) { + emit(processingResult.entity(location, team)); } } catch (e) { this.logger.error( diff --git a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts index 654deb3079..63a273378b 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts @@ -37,6 +37,7 @@ import { getOrganizationUsers, parseGithubOrgUrl, } from '../lib'; +import { areGroupEntities, areUserEntities } from '../lib/guards'; type GraphQL = typeof graphql; @@ -101,19 +102,23 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { this.logger.info('Reading GitHub users and groups'); const { users } = await getOrganizationUsers(client, org, tokenType); - const { groups } = await getOrganizationTeams(client, org); + const { teams } = await getOrganizationTeams(client, org); const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1); this.logger.debug( - `Read ${users.length} GitHub users and ${groups.length} GitHub groups in ${duration} seconds`, + `Read ${users.length} GitHub users and ${teams.length} GitHub teams in ${duration} seconds`, ); - assignGroupsToUsers(users, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(users)) { + assignGroupsToUsers(users, teams); + } + } // Done! - for (const group of groups) { - emit(processingResult.entity(location, group)); + for (const team of teams) { + emit(processingResult.entity(location, team)); } for (const user of users) { emit(processingResult.entity(location, user)); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 4d343493f6..65a622890d 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -20,7 +20,7 @@ import { ANNOTATION_ORIGIN_LOCATION, DEFAULT_NAMESPACE, Entity, - GroupEntity, + isGroupEntity, parseEntityRef, stringifyEntityRef, UserEntity, @@ -78,6 +78,7 @@ import { getOrganizationTeamsFromUsers, } from '../lib/github'; import { splitTeamSlug } from '../lib/util'; +import { areGroupEntities, areUserEntities } from '../lib/guards'; /** * Options for {@link GithubMultiOrgEntityProvider}. @@ -240,7 +241,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const { markReadComplete } = trackProgress(logger); const allUsersMap = new Map(); - const allGroups: Entity[] = []; + const allTeams: Entity[] = []; const orgsToProcess = this.options.orgs?.length ? this.options.orgs @@ -266,7 +267,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { this.options.userTransformer, ); - const { groups } = await getOrganizationTeams( + const { teams } = await getOrganizationTeams( client, org, this.defaultMultiOrgTeamTransformer.bind(this), @@ -283,10 +284,14 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { return allUsersMap.get(userRef); }); - assignGroupsToUsers(pendingUsers, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(pendingUsers)) { + assignGroupsToUsers(pendingUsers, teams); + } + } - allGroups.push(...groups); + allTeams.push(...teams); } catch (e) { logger.error(`Failed to read GitHub org data for ${org}: ${e}`); } @@ -294,11 +299,11 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const allUsers = Array.from(allUsersMap.values()); - const { markCommitComplete } = markReadComplete({ allUsers, allGroups }); + const { markCommitComplete } = markReadComplete({ allUsers, allTeams }); await this.connection.applyMutation({ type: 'full', - entities: [...allUsers, ...allGroups].map(entity => ({ + entities: [...allUsers, ...allTeams].map(entity => ({ locationKey: `github-multi-org-provider:${this.options.id}`, entity: withLocations( `https://${this.options.gitHubConfig.host}`, @@ -422,7 +427,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { this.options.userTransformer, ); - const { groups } = await getOrganizationTeams( + const { teams } = await getOrganizationTeams( client, org, this.defaultMultiOrgTeamTransformer.bind(this), @@ -440,7 +445,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { groups: userGroups } = await getOrganizationTeamsFromUsers( + const { teams: userTeams } = await getOrganizationTeamsFromUsers( orgClient, userOrg, users.map( @@ -451,12 +456,14 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { this.defaultMultiOrgTeamTransformer.bind(this), ); - assignGroupsToUsers(users, userGroups); + if (areGroupEntities(userTeams) && areUserEntities(users)) { + assignGroupsToUsers(users, userTeams); + } } const { added, removed } = this.createAddEntitiesOperation([ ...users, - ...groups, + ...teams, ]); await this.connection.applyMutation({ type: 'delta', @@ -536,14 +543,16 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { groups } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsFromUsers( orgClient, userOrg, [login], this.defaultMultiOrgTeamTransformer.bind(this), ); - assignGroupsToUsers([user], groups); + if (areGroupEntities(teams)) { + assignGroupsToUsers([user], teams); + } } } @@ -623,7 +632,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { }); const teamSlug = event.team.slug; - const { group } = await getOrganizationTeam( + const { team } = await getOrganizationTeam( client, org, teamSlug, @@ -637,10 +646,11 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { this.options.userTransformer, ); - const usersFromChangedGroup = - group.spec.members?.map(m => - stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })), - ) || []; + const usersFromChangedGroup = isGroupEntity(team) + ? team.spec.members?.map(m => + stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })), + ) || [] + : []; const usersToRebuild = users.filter(u => usersFromChangedGroup.includes(stringifyEntityRef(u)), ); @@ -656,7 +666,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { groups } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsFromUsers( orgClient, userOrg, usersToRebuild.map( @@ -667,7 +677,9 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { this.defaultMultiOrgTeamTransformer.bind(this), ); - assignGroupsToUsers(usersToRebuild, groups); + if (areGroupEntities(teams) && areUserEntities(usersToRebuild)) { + assignGroupsToUsers(usersToRebuild, teams); + } } const oldName = event.changes.name?.from || ''; @@ -693,7 +705,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const { removed } = this.createRemoveEntitiesOperation([oldGroup]); const { added } = this.createAddEntitiesOperation([ ...usersToRebuild, - group, + team, ]); await this.connection.applyMutation({ type: 'delta', @@ -729,7 +741,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { }); const teamSlug = event.team.slug; - const { group } = await getOrganizationTeam( + const { team } = await getOrganizationTeam( client, org, teamSlug, @@ -765,17 +777,19 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { groups } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsFromUsers( orgClient, userOrg, [login], this.defaultMultiOrgTeamTransformer.bind(this), ); - assignGroupsToUsers([user], groups); + if (areGroupEntities(teams)) { + assignGroupsToUsers([user], teams); + } } - const { added, removed } = this.createAddEntitiesOperation([user, group]); + const { added, removed } = this.createAddEntitiesOperation([user, team]); await this.connection.applyMutation({ type: 'delta', removed, @@ -812,14 +826,14 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { private async defaultMultiOrgTeamTransformer( team: GithubTeam, ctx: TransformerContext, - ): Promise { + ): Promise { if (this.options.teamTransformer) { return await this.options.teamTransformer(team, ctx); } const result = await defaultOrganizationTeamTransformer(team, ctx); - if (result) { + if (result && result.spec) { result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US'); // Group `spec.members` inherits the namespace of it's group so need to explicitly specify refs here result.spec.members = team.members.map( @@ -885,9 +899,9 @@ function trackProgress(logger: Logger) { function markReadComplete(read: { allUsers: unknown[]; - allGroups: unknown[]; + allTeams: unknown[]; }) { - summary = `${read.allUsers.length} GitHub users and ${read.allGroups.length} GitHub groups`; + summary = `${read.allUsers.length} GitHub users and ${read.allTeams.length} GitHub groups`; const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1); timestamp = Date.now(); logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index bf82607c6b..22f1f12e8d 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -19,6 +19,7 @@ import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, Entity, + isGroupEntity, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -68,6 +69,7 @@ import { } from '../lib/github'; import { assignGroupsToUsers, buildOrgHierarchy } from '../lib/org'; import { parseGithubOrgUrl, splitTeamSlug } from '../lib/util'; +import { areGroupEntities, areUserEntities } from '../lib/guards'; /** * Options for {@link GithubOrgEntityProvider}. @@ -223,20 +225,24 @@ export class GithubOrgEntityProvider tokenType, this.options.userTransformer, ); - const { groups } = await getOrganizationTeams( + const { teams } = await getOrganizationTeams( client, org, this.options.teamTransformer, ); - assignGroupsToUsers(users, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(users)) { + assignGroupsToUsers(users, teams); + } + } - const { markCommitComplete } = markReadComplete({ users, groups }); + const { markCommitComplete } = markReadComplete({ users, teams }); await this.connection.applyMutation({ type: 'full', - entities: [...users, ...groups].map(entity => ({ + entities: [...users, ...teams].map(entity => ({ locationKey: `github-org-provider:${this.options.id}`, entity: withLocations( `https://${this.options.gitHubConfig.host}`, @@ -340,7 +346,7 @@ export class GithubOrgEntityProvider }); const { org } = parseGithubOrgUrl(this.options.orgUrl); - const { group } = await getOrganizationTeam( + const { team } = await getOrganizationTeam( client, org, teamSlug, @@ -354,20 +360,28 @@ export class GithubOrgEntityProvider this.options.userTransformer, ); - const usersFromChangedGroup = group.spec.members || []; + if (!isGroupEntity(team)) { + return; + } + + const usersFromChangedGroup = team.spec.members || []; const usersToRebuild = users.filter(u => usersFromChangedGroup.includes(u.metadata.name), ); - const { groups } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsFromUsers( client, org, usersToRebuild.map(u => u.metadata.name), this.options.teamTransformer, ); - assignGroupsToUsers(usersToRebuild, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(usersToRebuild)) { + assignGroupsToUsers(usersToRebuild, teams); + } + } const oldName = event.changes.name?.from || event.team.name; const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-'); @@ -380,14 +394,14 @@ export class GithubOrgEntityProvider const { removed } = createDeltaOperation(org, [ { - ...group, + ...team, metadata: { name: oldSlug, description: oldDescriptionSlug, }, }, ]); - const { added } = createDeltaOperation(org, [...usersToRebuild, ...groups]); + const { added } = createDeltaOperation(org, [...usersToRebuild, ...teams]); await this.connection.applyMutation({ type: 'delta', removed, @@ -423,7 +437,7 @@ export class GithubOrgEntityProvider }); const { org } = parseGithubOrgUrl(this.options.orgUrl); - const { group } = await getOrganizationTeam( + const { team } = await getOrganizationTeam( client, org, teamSlug, @@ -439,7 +453,7 @@ export class GithubOrgEntityProvider const usersToRebuild = users.filter(u => u.metadata.name === userLogin); - const { groups } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsFromUsers( client, org, [userLogin], @@ -447,16 +461,20 @@ export class GithubOrgEntityProvider ); // we include group because the removed event need to update the old group too - if (!groups.some(g => g.metadata.name === group.metadata.name)) { - groups.push(group); + if (!teams.some(t => t.metadata.name === team.metadata.name)) { + teams.push(team); } - assignGroupsToUsers(usersToRebuild, groups); - buildOrgHierarchy(groups); + if (areGroupEntities(teams)) { + buildOrgHierarchy(teams); + if (areUserEntities(usersToRebuild)) { + assignGroupsToUsers(usersToRebuild, teams); + } + } const { added, removed } = createDeltaOperation(org, [ ...usersToRebuild, - ...groups, + ...teams, ]); await this.connection.applyMutation({ type: 'delta', @@ -588,10 +606,10 @@ function trackProgress(logger: Logger) { let timestamp = Date.now(); let summary: string; - logger.info('Reading GitHub users and groups'); + logger.info('Reading GitHub users and teams'); - function markReadComplete(read: { users: unknown[]; groups: unknown[] }) { - summary = `${read.users.length} GitHub users and ${read.groups.length} GitHub groups`; + function markReadComplete(read: { users: unknown[]; teams: unknown[] }) { + summary = `${read.users.length} GitHub users and ${read.teams.length} GitHub teams`; const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1); timestamp = Date.now(); logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);