Merge pull request #19311 from jgrigg/relax-github-transforms

Relax github transform types
This commit is contained in:
Ben Lambert
2023-08-29 09:56:20 +02:00
committed by GitHub
10 changed files with 175 additions and 98 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-github': patch
---
Allow github user and team transforms to return any Entity
@@ -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<GroupEntity | undefined>;
) => Promise<Entity | undefined>;
// @public
export interface TransformerContext {
@@ -319,5 +317,5 @@ export interface TransformerContext {
export type UserTransformer = (
item: GithubUser,
ctx: TransformerContext,
) => Promise<UserEntity | undefined>;
) => Promise<Entity | undefined>;
```
@@ -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<UserEntity | undefined>;
) => Promise<Entity | undefined>;
/**
* Transformer for GitHub Team to GroupEntity
* Transformer for GitHub Team to an Entity
*
* @public
*/
export type TeamTransformer = (
item: GithubTeam,
ctx: TransformerContext,
) => Promise<GroupEntity | undefined>;
) => Promise<Entity | undefined>;
/**
* Default transformer for GitHub users to UserEntity
@@ -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);
});
});
@@ -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<GroupEntity | undefined> => {
): Promise<Entity | undefined> => {
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<GroupEntity | undefined> => {
): Promise<Entity | undefined> => {
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<GroupEntity | undefined> => {
): Promise<Entity | undefined> => {
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(
@@ -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));
}
@@ -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<UserEntity | undefined> => {
async (githubUser, ctx): Promise<Entity | undefined> => {
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<GroupEntity | undefined> => {
async (team, ctx): Promise<Entity | undefined> => {
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(
@@ -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));
@@ -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<GroupEntity | undefined> {
): Promise<Entity | undefined> {
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...`);
@@ -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...`);