Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ A Node.js library for fetching events and talks from GitEvents-based GitHub repo

- πŸš€ Fetch upcoming and past events from GitHub Issues
- 🎀 Retrieve event talks and speaker submissions (via sub-issues)
- 🏒 Fetch organization statistics and metadata
- πŸ‘₯ Fetch GitHub Teams and team members
- πŸ” Support for both GitHub Personal Access Tokens (PAT) and GitHub App authentication
- πŸ“Š Parse structured event data using issue forms
Expand Down Expand Up @@ -214,6 +215,41 @@ console.log(team)

**Note:** Returns `null` if the team is not found.

### `getOrganization(org)`

Fetch organization statistics and metadata.

**Parameters:**

- `org` (string) - GitHub organization name

**Returns:** `Promise<Organization | null>`

Returns organization data or `null` if not found.

**Example:**

```javascript
import { getOrganization } from 'gitevents-fetch'

const org = await getOrganization('myorg')

console.log(org)
// {
// name: 'My Organization',
// login: 'myorg',
// description: 'We build amazing things',
// websiteUrl: 'https://myorg.com',
// avatarUrl: 'https://github.com/myorg.png',
// email: 'hello@myorg.com',
// location: 'San Francisco, CA',
// createdAt: Date('2020-01-01T00:00:00.000Z'),
// updatedAt: Date('2024-01-01T00:00:00.000Z'),
// memberCount: 42,
// publicRepoCount: 128
// }
```

### Event Object Structure

```typescript
Expand Down
21 changes: 21 additions & 0 deletions src/graphql/organization.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
query (
$organization: String!
) {
organization(login: $organization) {
name
login
description
websiteUrl
avatarUrl
email
location
createdAt
updatedAt
membersWithRole {
totalCount
}
repositories(privacy: PUBLIC) {
totalCount
}
}
}
5 changes: 5 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { graphql } from '@octokit/graphql'
import { ghAppId, ghAppInstallationId, ghPrivateKey, ghPAT } from './config.js'
import { listUpcomingEvents, listPastEvents, getEvent } from './events.js'
import { getTeamById } from './teams.js'
import { getOrganization as getOrg } from './organization.js'

function createAuth() {
// Use PAT if provided (and no private key)
Expand Down Expand Up @@ -55,3 +56,7 @@ export async function event(org, repo, number) {
export async function getTeam(org, teamSlug) {
return getTeamById(graphqlWithAuth, org, teamSlug)
}

export async function getOrganization(org) {
return getOrg(graphqlWithAuth, org)
}
4 changes: 3 additions & 1 deletion src/lib/parseGql.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { defaultApprovedEventLabel } from '../config.js'
import eventsQuery from '../graphql/events.gql?raw'
import eventQuery from '../graphql/event.gql?raw'
import teamQuery from '../graphql/team.gql?raw'
import organizationQuery from '../graphql/organization.gql?raw'

const queries = {
events: eventsQuery,
event: eventQuery,
team: teamQuery
team: teamQuery,
organization: organizationQuery
}

export async function parseGql(path) {
Expand Down
46 changes: 46 additions & 0 deletions src/organization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { parseGql } from './lib/parseGql.js'

function validateParams(params) {
const missing = []
for (const [key, value] of Object.entries(params)) {
if (!value) missing.push(key)
}
if (missing.length > 0) {
throw new Error(`Missing required parameters: ${missing.join(', ')}`)
}
}

export async function getOrganization(graphql, org) {
validateParams({ graphql, org })

try {
const query = await parseGql('organization')
const vars = {
organization: org

Copilot AI Oct 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable 'org' is used before its declaration.

Copilot uses AI. Check for mistakes.
}

const result = await graphql(query, vars)

if (!result.organization) {
return null
}

const org = result.organization

return {
name: org.name || null,
login: org.login || null,
description: org.description || null,
websiteUrl: org.websiteUrl || null,
avatarUrl: org.avatarUrl || null,
email: org.email || null,
location: org.location || null,
createdAt: org.createdAt ? new Date(org.createdAt) : null,
updatedAt: org.updatedAt ? new Date(org.updatedAt) : null,
memberCount: org.membersWithRole?.totalCount || 0,
publicRepoCount: org.repositories?.totalCount || 0
}
} catch (error) {
throw new Error(`Failed to fetch organization: ${error.message}`)
}
}
143 changes: 143 additions & 0 deletions test/organization.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import test from 'node:test'
import assert from 'node:assert'
import { getOrganization } from '../src/organization.js'

test('getOrganization - validates required parameters', async () => {
await assert.rejects(
async () => {
await getOrganization(null, 'org')
},
{
message: /Missing required parameters/
},
'Should validate graphql parameter'
)

await assert.rejects(
async () => {
const mockGraphql = () => {}
await getOrganization(mockGraphql, null)
},
{
message: /Missing required parameters/
},
'Should validate org parameter'
)
})

test('getOrganization - fetches organization successfully', async () => {
const mockGraphql = async () => ({
organization: {
name: 'Test Organization',
login: 'test-org',
description: 'A test organization',
websiteUrl: 'https://test-org.com',
avatarUrl: 'https://github.com/test-org.png',
email: 'hello@test-org.com',
location: 'San Francisco, CA',
createdAt: '2020-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
membersWithRole: {
totalCount: 42
},
repositories: {
totalCount: 128
}
}
})

const result = await getOrganization(mockGraphql, 'test-org')

assert.equal(result.name, 'Test Organization')
assert.equal(result.login, 'test-org')
assert.equal(result.description, 'A test organization')
assert.equal(result.websiteUrl, 'https://test-org.com')
assert.equal(result.avatarUrl, 'https://github.com/test-org.png')
assert.equal(result.email, 'hello@test-org.com')
assert.equal(result.location, 'San Francisco, CA')
assert.ok(result.createdAt instanceof Date)
assert.ok(result.updatedAt instanceof Date)
assert.equal(result.memberCount, 42)
assert.equal(result.publicRepoCount, 128)
})

test('getOrganization - handles missing optional fields', async () => {
const mockGraphql = async () => ({
organization: {
name: 'Test Org',
login: 'test-org',
description: null,
websiteUrl: null,
avatarUrl: 'https://github.com/test-org.png',
email: null,
location: null,
createdAt: '2020-01-01T00:00:00Z',
updatedAt: null,
membersWithRole: {
totalCount: 5
},
repositories: null
}
})

const result = await getOrganization(mockGraphql, 'test-org')

assert.equal(result.description, null)
assert.equal(result.websiteUrl, null)
assert.equal(result.email, null)
assert.equal(result.location, null)
assert.equal(result.updatedAt, null)
assert.equal(result.publicRepoCount, 0)
})

test('getOrganization - returns null if organization not found', async () => {
const mockGraphql = async () => ({
organization: null
})

const result = await getOrganization(mockGraphql, 'nonexistent-org')

assert.equal(result, null)
})

test('getOrganization - handles GraphQL errors', async () => {
const mockGraphql = async () => {
throw new Error('API rate limit exceeded')
}

await assert.rejects(
async () => {
await getOrganization(mockGraphql, 'test-org')
},
{
message: /Failed to fetch organization: API rate limit exceeded/
},
'Should wrap GraphQL errors'
)
})

// Integration test with real API
test(
'getOrganization - real API call',
{
skip: !process.env.GH_PAT && !process.env.GH_PRIVATE_KEY
},
async () => {
const { getOrganization: getOrgAPI } = await import('../src/index.js')

// Fetch gitevents organization
const org = await getOrgAPI('gitevents')

assert.ok(org, 'Should return organization data')
assert.equal(org.login, 'gitevents')
assert.ok(org.name, 'Should have name')
assert.ok(typeof org.memberCount === 'number', 'Should have member count')
assert.ok(
typeof org.publicRepoCount === 'number',
'Should have public repo count'
)
assert.ok('description' in org, 'Should have description field')
assert.ok('websiteUrl' in org, 'Should have websiteUrl field')
assert.ok(org.avatarUrl, 'Should have avatar URL')
}
)
Loading