-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add teams client, invoke and resolver
- Loading branch information
Showing
13 changed files
with
445 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { DEFAULT_SERVER_ERROR_MESSAGE } from '../models/error-messages'; | ||
|
||
export const aggResponseErrorHandler = (responseBody: any) => { | ||
const { data, errors } = responseBody; | ||
if (errors) { | ||
console.error('AGG error returned', errors); | ||
throw new Error(DEFAULT_SERVER_ERROR_MESSAGE); | ||
} | ||
return data; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import api from '@forge/api'; | ||
import { aggResponseErrorHandler } from './agg-response-error-handler'; | ||
import { AggOperation } from '../types'; | ||
|
||
export const aggQuery = async (request: AggOperation) => { | ||
const response = await api.asApp().requestGraph(request.query, request.variables); | ||
const responseBody = await response.json(); | ||
console.log({ | ||
message: 'AGG request', | ||
requestName: request.name, | ||
responseStatus: response.status, | ||
requestId: responseBody?.extensions?.gateway?.request_id, | ||
}); | ||
return aggResponseErrorHandler(responseBody); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { TEAMS_AMOUNT } from '../constants'; | ||
|
||
export function getTeamsQuery(organizationId: string, siteId: string, accountId?: string, searchValue?: string) { | ||
const memberIds = accountId ? [accountId] : []; | ||
const query = searchValue ?? ''; | ||
|
||
const sortBy = [ | ||
{ | ||
field: 'DISPLAY_NAME', | ||
order: 'ASC', | ||
}, | ||
{ | ||
field: 'STATE', | ||
order: 'ASC', | ||
}, | ||
]; | ||
|
||
return { | ||
name: 'teams', | ||
query: ` | ||
query teamSearchV2 ($organizationId: ID!, $siteId: String!, $memberIds: [ID] = [], $sortBy: [TeamSort], $first: Int, $query: String!) { | ||
team { | ||
teamSearchV2 (organizationId: $organizationId, siteId: $siteId, filter: { query: $query, membership: {memberIds: $memberIds}}, sortBy: $sortBy, first: $first) @optIn(to: "Team-search-v2") { | ||
nodes { | ||
team { | ||
id | ||
displayName | ||
smallAvatarImageUrl | ||
state | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`, | ||
variables: { | ||
organizationId, | ||
siteId, | ||
sortBy, | ||
memberIds, | ||
query, | ||
first: TEAMS_AMOUNT, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export function getTenantContextQuery(cloudId: string) { | ||
return { | ||
name: 'tenantContext', | ||
query: ` | ||
query tenantContext($cloudId: ID!) { | ||
tenantContexts(cloudIds: [$cloudId]) { | ||
orgId | ||
} | ||
} | ||
`, | ||
variables: { | ||
cloudId, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* eslint-disable import/first */ | ||
class MockResolver { | ||
resolvers: { [key: string]: any }; | ||
|
||
constructor() { | ||
this.resolvers = {}; | ||
} | ||
|
||
define = (name: any, fn: any) => { | ||
this.resolvers[name] = fn; | ||
}; | ||
|
||
getDefinitions = () => this.resolvers; | ||
} | ||
jest.mock('@forge/resolver', () => MockResolver); | ||
|
||
import handler from './import-resolvers'; | ||
import * as featureFlags from '../services/feature-flags'; | ||
import * as getTeams from '../services/get-teams'; | ||
import { MOCK_CLOUD_ID } from '../__tests__/fixtures/gitlab-data'; | ||
|
||
describe('importResolvers', () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
describe('getFirstPageOfTeamsWithMembershipStatus', () => { | ||
const { getFirstPageOfTeamsWithMembershipStatus } = handler as any; | ||
|
||
test('successfully returns teams data', async () => { | ||
const mockTeam = { | ||
teamId: 'teamId', | ||
displayName: 'displayName', | ||
imageUrl: 'https://test', | ||
}; | ||
const mockTeamsResponse = { teamsWithMembership: [mockTeam], otherTeams: [mockTeam] }; | ||
|
||
jest | ||
.spyOn(featureFlags, 'listFeatures') | ||
.mockReturnValueOnce({ isOwnerTeamEnabled: true, isSendStagingEventsEnabled: false }); | ||
jest.spyOn(getTeams, 'getFirstPageOfTeamsWithMembershipStatus').mockResolvedValueOnce(mockTeamsResponse); | ||
|
||
const teamsResponse = await getFirstPageOfTeamsWithMembershipStatus({ | ||
context: { | ||
cloudId: MOCK_CLOUD_ID, | ||
accountId: 'test-account-id', | ||
}, | ||
payload: { | ||
searchTeamValue: 'searchTeamValue', | ||
}, | ||
}); | ||
|
||
expect(teamsResponse).toEqual({ | ||
success: true, | ||
data: { | ||
teams: mockTeamsResponse, | ||
}, | ||
}); | ||
}); | ||
|
||
test('returns an error if getFirstPageOfTeamsWithMembershipStatus request failed', async () => { | ||
const mockError = new Error('error'); | ||
jest | ||
.spyOn(featureFlags, 'listFeatures') | ||
.mockReturnValueOnce({ isOwnerTeamEnabled: true, isSendStagingEventsEnabled: false }); | ||
jest.spyOn(getTeams, 'getFirstPageOfTeamsWithMembershipStatus').mockRejectedValueOnce(mockError); | ||
|
||
const teamsResponse = await getFirstPageOfTeamsWithMembershipStatus({ | ||
context: { | ||
cloudId: MOCK_CLOUD_ID, | ||
accountId: 'test-account-id', | ||
}, | ||
payload: { | ||
searchTeamValue: 'searchTeamValue', | ||
}, | ||
}); | ||
|
||
expect(teamsResponse).toEqual({ | ||
success: false, | ||
errors: [{ message: mockError.message }], | ||
}); | ||
}); | ||
|
||
test('returns empty teams data if the isOwnerTeamEnabled disabled', async () => { | ||
const mockTeam = { | ||
teamId: 'teamId', | ||
displayName: 'displayName', | ||
imageUrl: 'https://test', | ||
}; | ||
const mockTeamsResponse = { teamsWithMembership: [mockTeam], otherTeams: [mockTeam] }; | ||
|
||
jest | ||
.spyOn(featureFlags, 'listFeatures') | ||
.mockReturnValueOnce({ isOwnerTeamEnabled: false, isSendStagingEventsEnabled: false }); | ||
jest.spyOn(getTeams, 'getFirstPageOfTeamsWithMembershipStatus').mockResolvedValueOnce(mockTeamsResponse); | ||
|
||
const teamsResponse = await getFirstPageOfTeamsWithMembershipStatus({ | ||
context: { | ||
cloudId: MOCK_CLOUD_ID, | ||
accountId: 'test-account-id', | ||
}, | ||
payload: { | ||
searchTeamValue: 'searchTeamValue', | ||
}, | ||
}); | ||
|
||
expect(teamsResponse).toEqual({ | ||
success: true, | ||
data: { | ||
teams: { teamsWithMembership: [], otherTeams: [] }, | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { mocked } from 'ts-jest/utils'; | ||
|
||
import { getFirstPageOfTeamsWithMembershipStatus } from './get-teams'; | ||
import { getTeams, getTenantContext } from '../client/compass'; | ||
import { MappedTeam } from '../types'; | ||
import { MOCK_CLOUD_ID } from '../__tests__/fixtures/gitlab-data'; | ||
|
||
jest.mock('../client/compass'); | ||
|
||
const mockGetTenantContext = mocked(getTenantContext); | ||
const mockGetTeams = mocked(getTeams); | ||
|
||
const MOCK_GET_TENANT_CONTEXT = { | ||
tenantContexts: [{ orgId: 'orgId' }], | ||
}; | ||
const TEAM_ID = 'team_id'; | ||
|
||
const mockGetFirstPageOfTeamsWithMembershipStatus = (id: string, teamsAmount = 1) => { | ||
const team = { | ||
team: { | ||
id, | ||
displayName: 'team_name', | ||
smallAvatarImageUrl: 'imageUrl', | ||
state: 'ACTIVE', | ||
}, | ||
}; | ||
|
||
return new Array(teamsAmount).fill(team); | ||
}; | ||
|
||
const mockMappedTeams = (teamId: string, teamsAmount = 1) => { | ||
const mappedTeam: MappedTeam = { | ||
teamId, | ||
displayName: 'team_name', | ||
imageUrl: 'imageUrl', | ||
}; | ||
|
||
return new Array(teamsAmount).fill(mappedTeam); | ||
}; | ||
|
||
describe('Get teams', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('returns just teams with membership in case there are 30 of them', async () => { | ||
mockGetTenantContext.mockResolvedValue(MOCK_GET_TENANT_CONTEXT); | ||
mockGetTeams.mockResolvedValueOnce(mockGetFirstPageOfTeamsWithMembershipStatus(TEAM_ID, 30)); | ||
|
||
const expectedResult = mockMappedTeams(TEAM_ID, 30); | ||
|
||
const result = await getFirstPageOfTeamsWithMembershipStatus(MOCK_CLOUD_ID); | ||
|
||
expect(result).toEqual({ teamsWithMembership: expectedResult, otherTeams: [] }); | ||
}); | ||
|
||
test('returns just other teams in case there are no teams with membership', async () => { | ||
mockGetTenantContext.mockResolvedValue(MOCK_GET_TENANT_CONTEXT); | ||
mockGetTeams.mockResolvedValueOnce([]); | ||
mockGetTeams.mockResolvedValueOnce(mockGetFirstPageOfTeamsWithMembershipStatus(TEAM_ID, 1)); | ||
|
||
const expectedResult = mockMappedTeams(TEAM_ID, 1); | ||
|
||
const result = await getFirstPageOfTeamsWithMembershipStatus(MOCK_CLOUD_ID); | ||
|
||
expect(result).toEqual({ teamsWithMembership: [], otherTeams: expectedResult }); | ||
}); | ||
|
||
test('returns all teams', async () => { | ||
mockGetTenantContext.mockResolvedValue(MOCK_GET_TENANT_CONTEXT); | ||
mockGetTeams.mockResolvedValueOnce(mockGetFirstPageOfTeamsWithMembershipStatus(TEAM_ID, 2)); | ||
mockGetTeams.mockResolvedValueOnce([ | ||
...mockGetFirstPageOfTeamsWithMembershipStatus(TEAM_ID, 2), | ||
...mockGetFirstPageOfTeamsWithMembershipStatus('team_id_2', 2), | ||
]); | ||
|
||
const expectedResult = { | ||
teamsWithMembership: mockMappedTeams(TEAM_ID, 2), | ||
otherTeams: mockMappedTeams('team_id_2', 2), | ||
}; | ||
|
||
const result = await getFirstPageOfTeamsWithMembershipStatus(MOCK_CLOUD_ID); | ||
|
||
expect(result).toEqual(expectedResult); | ||
}); | ||
|
||
test('throws an error in case of error while getting tenant context', async () => { | ||
mockGetTenantContext.mockRejectedValue(new Error('Error')); | ||
|
||
const errorMessage = 'Error while getting teams.'; | ||
|
||
await expect(getFirstPageOfTeamsWithMembershipStatus(MOCK_CLOUD_ID)).rejects.toThrow(new Error(errorMessage)); | ||
}); | ||
}); |
Oops, something went wrong.