From e55f5ca2b1a3e919e6d4e5ea8bc10c833f67c7da Mon Sep 17 00:00:00 2001 From: Jeff Wilcox <427913+jeffwilcox@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:48:54 -0700 Subject: [PATCH] Repository invitations list support --- .cspell.json | 1 + api/client/index.ts | 2 ++ api/client/people.ts | 4 ++-- api/client/person.ts | 4 +++- business/account.ts | 6 ++++++ business/repository.ts | 40 ++++++++++++++++++++++++++++++++++++ interfaces/github/account.ts | 1 + lib/github/collections.ts | 24 +++++++++++++++++++++- utils.ts | 4 ++++ 9 files changed, 82 insertions(+), 4 deletions(-) diff --git a/.cspell.json b/.cspell.json index 2fa7eaabe..343d1ea96 100644 --- a/.cspell.json +++ b/.cspell.json @@ -737,6 +737,7 @@ "shortcutting", "shouldrestore", "showids", + "shuf", "Sida", "signin", "signoff", diff --git a/api/client/index.ts b/api/client/index.ts index 6cc9bc95c..a662e58a4 100644 --- a/api/client/index.ts +++ b/api/client/index.ts @@ -31,6 +31,7 @@ import routeNews from './news'; import routeCrossOrganizationPeople from './people'; import routeCrossOrganizationRepos from './repos'; import routeCrossOrganizationTeams from './teams'; +import routeUsers from './users'; const router: Router = Router(); @@ -63,6 +64,7 @@ router.use('/signout', routeSession); router.use('/people', routeCrossOrganizationPeople); router.use('/repos', routeCrossOrganizationRepos); router.use('/teams', routeCrossOrganizationTeams); +router.use('/users', routeUsers); router.use('/news', routeNews); const dynamicStartupInstance = getCompanySpecificDeployment(); diff --git a/api/client/people.ts b/api/client/people.ts index 8a71f2508..41b30e959 100644 --- a/api/client/people.ts +++ b/api/client/people.ts @@ -12,7 +12,7 @@ import { type GitHubSimpleAccount, type ICorporateLink, ReposAppRequest } from ' import JsonPager from './jsonPager'; import getCompanySpecificDeployment from '../../middleware/companySpecificDeployment'; -import RouteGetPerson from './person'; +import { getPerson as routeGetPerson } from './person'; import { equivalentLegacyPeopleSearch } from './peopleSearch'; const router: Router = Router(); @@ -37,7 +37,7 @@ interface IOrganizationMembershipAccount { [id: string]: GitHubSimpleAccount; } -router.get('/:login', RouteGetPerson); +router.get('/:login', routeGetPerson); router.get( '/', diff --git a/api/client/person.ts b/api/client/person.ts index c82a54a50..7b58e626a 100644 --- a/api/client/person.ts +++ b/api/client/person.ts @@ -12,7 +12,7 @@ import { IGraphEntry } from '../../lib/graphProvider'; import { jsonError } from '../../middleware'; import { getProviders } from '../../transitional'; -export default asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => { +const getPerson = asyncHandler(async (req: ReposAppRequest, res: Response, next: NextFunction) => { const providers = getProviders(req); const { operations, queryCache, graphProvider } = providers; const login = req.params.login as string; @@ -77,3 +77,5 @@ export default asyncHandler(async (req: ReposAppRequest, res: Response, next: Ne return next(jsonError(`login ${login} error: ${error}`, 500)); } }); + +export { getPerson }; diff --git a/business/account.ts b/business/account.ts index 1e5e0ec8d..17a6277a0 100644 --- a/business/account.ts +++ b/business/account.ts @@ -70,6 +70,12 @@ export class Account { case AccountJsonFormat.GitHub: { return basic; } + case AccountJsonFormat.GitHubExtended: { + const cloneEntity = Object.assign({}, this._originalEntity || {}); + delete (cloneEntity as any).cost; + delete (cloneEntity as any).headers; + return cloneEntity; + } case AccountJsonFormat.GitHubDetailedWithLink: { const cloneEntity = Object.assign({}, this._originalEntity || {}); delete (cloneEntity as any).cost; diff --git a/business/repository.ts b/business/repository.ts index bb253ec18..aa3453777 100644 --- a/business/repository.ts +++ b/business/repository.ts @@ -56,6 +56,7 @@ import { RepositoryActions } from './repositoryActions'; import { RepositoryPullRequest } from './repositoryPullRequest'; import { ErrorHelper } from '../transitional'; import { augmentInertiaPreview, RepositoryProject } from './repositoryProject'; +import { RepositoryInvitation } from './repositoryInvitation'; interface IRepositoryMoments { created?: moment.Moment; @@ -1057,6 +1058,39 @@ export class Repository { return collaborators; } + async listCollaboratorInvitations(cacheOptions?: IPagedCacheOptions): Promise { + cacheOptions = cacheOptions || {}; + const operations = throwIfNotGitHubCapable(this._operations); + const github = operations.github; + const parameters = { + owner: this.organization.name, + repo: this.name, + per_page: getPageSize(operations), + }; + if (!cacheOptions.maxAgeSeconds) { + cacheOptions.maxAgeSeconds = getMaxAgeSeconds( + operations, + CacheDefault.orgRepoCollaboratorsStaleSeconds + ); + } + if (cacheOptions.backgroundRefresh === undefined) { + cacheOptions.backgroundRefresh = true; + } + const invitationEntities = await github.collections.getRepoInvitations( + this.authorize(AppPurpose.Data), + parameters, + cacheOptions + ); + const invitations = common.createInstances( + this, + invitationFromEntity, + invitationEntities + ); + invitationEntities?.cost && ((invitations as any).cost = invitationEntities.cost); + invitationEntities?.headers && ((invitations as any).headers = invitationEntities.headers); + return invitations; + } + async addCollaborator( username: string, permission: GitHubRepositoryPermission @@ -1945,3 +1979,9 @@ function collaboratorPermissionFromEntity(entity) { const permission = new Collaborator(entity); return permission; } + +function invitationFromEntity(entity) { + // 'this' is bound for this function to be a private method + const invitation = new RepositoryInvitation(this, entity); + return invitation; +} diff --git a/interfaces/github/account.ts b/interfaces/github/account.ts index c57efafa5..f4bf943e3 100644 --- a/interfaces/github/account.ts +++ b/interfaces/github/account.ts @@ -5,6 +5,7 @@ export enum AccountJsonFormat { GitHub = 'github', + GitHubExtended = 'github+extended', UplevelWithLink = 'github+link', GitHubDetailedWithLink = 'detailed+link', } diff --git a/lib/github/collections.ts b/lib/github/collections.ts index cb18c7215..7f69f1229 100644 --- a/lib/github/collections.ts +++ b/lib/github/collections.ts @@ -13,11 +13,17 @@ import { IRestResponse, flattenData } from './core'; import { CompositeApiContext, CompositeIntelligentEngine } from './composite'; import { Collaborator } from '../../business/collaborator'; import { Team } from '../../business/team'; -import { IPagedCacheOptions, IGetAuthorizationHeader, IDictionary } from '../../interfaces'; +import { + IPagedCacheOptions, + IGetAuthorizationHeader, + IDictionary, + GitHubRepositoryPermission, +} from '../../interfaces'; import { RestLibrary } from '.'; import { sleep } from '../../utils'; import GitHubApplication from '../../business/application'; import { RepositoryPrimaryProperties } from '../../business/primaryProperties'; +import { RepositoryInvitation } from '../../business/repositoryInvitation'; export interface IGetAppInstallationsParameters { app_id: string; @@ -57,6 +63,7 @@ const teamDetailsToCopy = Team.PrimaryProperties; const memberDetailsToCopy = Collaborator.PrimaryProperties; const appInstallDetailsToCopy = GitHubApplication.PrimaryInstallationProperties; const contributorsDetailsToCopy = [...Collaborator.PrimaryProperties, 'contributions']; +const repoInviteDetailsToCopy = RepositoryInvitation.PrimaryProperties; const teamPermissionsToCopy = [ 'id', @@ -294,6 +301,21 @@ export class RestCollections { ); } + getRepoInvitations( + token: string | IGetAuthorizationHeader, + options, + cacheOptions: IPagedCacheOptions + ): Promise { + return this.generalizedCollectionWithFilter( + 'repoInvitations', + 'repos.listInvitations', + repoInviteDetailsToCopy, + token, + options, + cacheOptions + ); + } + getRepoBranches( token: string | IGetAuthorizationHeader, options, diff --git a/utils.ts b/utils.ts index dd9576020..84046d22b 100644 --- a/utils.ts +++ b/utils.ts @@ -17,6 +17,10 @@ export function daysInMilliseconds(days: number): number { return 1000 * 60 * 60 * 24 * days; } +export function dateToDateString(date: Date) { + return date.toISOString().substr(0, 10); +} + export function stringOrNumberAsString(value: any) { if (typeof value === 'number') { return (value as number).toString();