diff --git a/backend/apps/github/api/internal/queries/user.py b/backend/apps/github/api/internal/queries/user.py index d99df0de43..cf39bd1cc3 100644 --- a/backend/apps/github/api/internal/queries/user.py +++ b/backend/apps/github/api/internal/queries/user.py @@ -2,6 +2,7 @@ import strawberry import strawberry_django +from django.db.models import Case, IntegerField, Value, When from apps.github.api.internal.nodes.repository import RepositoryNode from apps.github.api.internal.nodes.user import UserNode @@ -52,3 +53,25 @@ def user( """ return User.objects.filter(has_public_member_page=True, login=login).first() + + @strawberry_django.field + def users(self, logins: list[str]) -> list[UserNode]: + """Resolve users by their logins. + + Args: + logins (list[str]): List of user logins. + + Returns: + list[User]: List of user objects. + + """ + if not logins: + return [] + qs = User.objects.filter(login__in=logins, has_public_member_page=True) + if logins: + order = Case( + *[When(login=login, then=Value(pos)) for pos, login in enumerate(logins)], + output_field=IntegerField(), + ) + qs = qs.order_by(order) + return qs diff --git a/backend/tests/apps/github/api/internal/queries/user_test.py b/backend/tests/apps/github/api/internal/queries/user_test.py index 4b430d030b..5896862d9e 100644 --- a/backend/tests/apps/github/api/internal/queries/user_test.py +++ b/backend/tests/apps/github/api/internal/queries/user_test.py @@ -79,3 +79,55 @@ def test_top_contributed_repositories(self): mock_queryset.filter.return_value.order_by.assert_called_once_with( "-contributions_count" ) + + def test_resolve_users_with_logins(self, mock_user): + """Test resolving multiple users by their logins.""" + with patch("apps.github.models.user.User.objects.filter") as mock_filter: + mock_queryset = mock_filter.return_value + mock_queryset.order_by.return_value = [mock_user] + + result = UserQuery().users(logins=["user1", "user2"]) + + assert result == [mock_user] + mock_filter.assert_called_once_with( + login__in=["user1", "user2"], has_public_member_page=True + ) + mock_queryset.order_by.assert_called_once() + + def test_resolve_users_empty_list(self): + """Test resolving users with an empty logins list.""" + with patch("apps.github.models.user.User.objects.filter") as mock_filter: + mock_queryset = mock_filter.return_value + mock_queryset.order_by.return_value = [] + + result = UserQuery().users(logins=[]) + + assert result == [] + + def test_resolve_users_filters_by_public_member_page_and_login_in(self): + """Test that users query filters by both has_public_member_page and login__in.""" + with patch("apps.github.models.user.User.objects.filter") as mock_filter: + mock_queryset = mock_filter.return_value + mock_queryset.order_by.return_value = [] + + UserQuery().users(logins=["user1", "user2", "user3"]) + + mock_filter.assert_called_once_with( + login__in=["user1", "user2", "user3"], has_public_member_page=True + ) + + def test_resolve_users_preserves_order(self, mock_user): + """Test that users are returned in the order of input logins.""" + mock_user1 = Mock(spec=User, login="user1") + mock_user2 = Mock(spec=User, login="user2") + + with patch("apps.github.models.user.User.objects.filter") as mock_filter: + mock_queryset = mock_filter.return_value + mock_queryset.order_by.return_value = [mock_user1, mock_user2] + + logins = ["user1", "user2"] + result = UserQuery().users(logins=logins) + + assert result == [mock_user1, mock_user2] + mock_filter.assert_called_once_with(login__in=logins, has_public_member_page=True) + mock_queryset.order_by.assert_called_once() diff --git a/docker-compose/local/compose.yaml b/docker-compose/local/compose.yaml index d1d8121c18..dcfccedec6 100644 --- a/docker-compose/local/compose.yaml +++ b/docker-compose/local/compose.yaml @@ -31,7 +31,7 @@ services: - 8000:8000 volumes: - ../../backend:/home/owasp - - backend-venv:/home/owasp/.venv + - backend-venv_3592:/home/owasp/.venv cache: command: > @@ -50,7 +50,7 @@ services: networks: - nest-network volumes: - - cache-data:/data + - cache-data_3592:/data db: container_name: nest-db @@ -67,7 +67,7 @@ services: networks: - nest-network volumes: - - db-data:/var/lib/postgresql/data + - db-data_3592:/var/lib/postgresql/data docs: container_name: nest-docs @@ -84,7 +84,7 @@ services: - 8001:8001 volumes: - ../../docs:/home/owasp/docs - - docs-venv:/home/owasp/.venv + - docs-venv_3592:/home/owasp/.venv frontend: container_name: nest-frontend @@ -106,8 +106,8 @@ services: - 3000:3000 volumes: - ../../frontend:/home/owasp - - frontend-next:/home/owasp/.next - - frontend-node-modules:/home/owasp/node_modules + - frontend-next_3592:/home/owasp/.next + - frontend-node-modules_3592:/home/owasp/node_modules worker: container_name: nest-worker @@ -136,15 +136,15 @@ services: - nest-network volumes: - ../../backend:/home/owasp - - backend-venv:/home/owasp/.venv + - backend-venv_3592:/home/owasp/.venv networks: nest-network: volumes: - backend-venv: - cache-data: - db-data: - docs-venv: - frontend-next: - frontend-node-modules: + backend-venv_3592: + cache-data_3592: + db-data_3592: + docs-venv_3592: + frontend-next_3592: + frontend-node-modules_3592: diff --git a/frontend/__tests__/e2e/pages/About.spec.ts b/frontend/__tests__/e2e/pages/About.spec.ts index 56dd64ddfc..1e1762e5ea 100644 --- a/frontend/__tests__/e2e/pages/About.spec.ts +++ b/frontend/__tests__/e2e/pages/About.spec.ts @@ -8,17 +8,17 @@ test.describe('About Page', () => { const request = route.request() const postData = request.postDataJSON() - if (postData.query?.includes('user')) { - const username = postData.variables.key - const userData = mockAboutData.users[username] - await route.fulfill({ status: 200, json: { data: { user: userData } } }) - } else if (postData.query?.includes('topContributors')) { + if (postData.operationName === 'GetAboutPageData') { await route.fulfill({ status: 200, - json: { data: { topContributors: mockAboutData.topContributors } }, + json: { + data: { + project: mockAboutData.project, + topContributors: mockAboutData.topContributors, + users: mockAboutData.users, + }, + }, }) - } else { - await route.fulfill({ status: 200, json: { data: { project: mockAboutData.project } } }) } }) diff --git a/frontend/__tests__/mockData/mockAboutData.ts b/frontend/__tests__/mockData/mockAboutData.ts index 47c329613d..a50b87aa31 100644 --- a/frontend/__tests__/mockData/mockAboutData.ts +++ b/frontend/__tests__/mockData/mockAboutData.ts @@ -42,21 +42,21 @@ export const mockAboutData = { login: `contributor${i + 1}`, name: `Contributor ${i + 1}`, })), - users: { - arkid15r: { + users: [ + { avatarUrl: 'https://avatars.githubusercontent.com/u/2201626?v=4', login: 'arkid15r', name: 'Arkadii Yakovets', }, - kasya: { + { avatarUrl: 'https://avatars.githubusercontent.com/u/5873153?v=4', login: 'kasya', name: 'Kate Golovanova', }, - mamicidal: { + { avatarUrl: 'https://avatars.githubusercontent.com/u/112129498?v=4', login: 'mamicidal', name: 'Starr Brown', }, - }, + ], } diff --git a/frontend/__tests__/unit/pages/About.test.tsx b/frontend/__tests__/unit/pages/About.test.tsx index 9af466b734..980068feb4 100644 --- a/frontend/__tests__/unit/pages/About.test.tsx +++ b/frontend/__tests__/unit/pages/About.test.tsx @@ -6,12 +6,6 @@ import { useRouter } from 'next/navigation' import React, { act } from 'react' import { render } from 'wrappers/testUtil' import About from 'app/about/page' -import { - GetProjectMetadataDocument, - GetTopContributorsDocument, -} from 'types/__generated__/projectQueries.generated' -import { GetLeaderDataDocument } from 'types/__generated__/userQueries.generated' - jest.mock('@apollo/client/react', () => ({ ...jest.requireActual('@apollo/client/react'), useQuery: jest.fn(), @@ -201,43 +195,13 @@ jest.mock('components/ShowMoreButton', () => ({ }, })) -const mockUserData = (username) => ({ - data: { user: mockAboutData.users[username] }, - loading: false, - error: null, -}) - -const mockProjectData = { - data: { project: mockAboutData.project }, - loading: false, - error: null, -} - -const mockTopContributorsData = { - data: { topContributors: mockAboutData.topContributors }, - loading: false, - error: null, -} - describe('About Component', () => { let mockRouter: { push: jest.Mock } beforeEach(() => { - ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - const key = options?.variables?.key - - if (query === GetProjectMetadataDocument) { - if (key === 'nest') { - return mockProjectData - } - } else if (query === GetTopContributorsDocument) { - if (key === 'nest') { - return mockTopContributorsData - } - } else if (query === GetLeaderDataDocument) { - return mockUserData(key) - } - - return { loading: true } + ;(useQuery as unknown as jest.Mock).mockReturnValue({ + data: mockAboutData, + loading: false, + error: null, }) mockRouter = { push: jest.fn() } ;(useRouter as jest.Mock).mockReturnValue(mockRouter) @@ -451,13 +415,10 @@ describe('About Component', () => { }) test('handles null project in data response gracefully', async () => { - ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - if (options?.variables?.key === 'nest') { - return { data: { project: null }, loading: false, error: null } - } else if (['arkid15r', 'kasya', 'mamicidal'].includes(options?.variables?.key)) { - return mockUserData(options?.variables?.key) - } - return { loading: true } + ;(useQuery as unknown as jest.Mock).mockReturnValue({ + data: { ...mockAboutData, project: null }, + loading: false, + error: null, }) await act(async () => { @@ -488,30 +449,22 @@ describe('About Component', () => { test('handles partial user data in leader response', async () => { const partialUserData = { + avatarUrl: 'https://avatars.githubusercontent.com/u/2201626?v=4', + company: 'OWASP', + // name is missing + login: 'arkid15r', + url: '/members/arkid15r', + } + + ;(useQuery as unknown as jest.Mock).mockReturnValue({ data: { - user: { - avatarUrl: 'https://avatars.githubusercontent.com/u/2201626?v=4', - company: 'OWASP', - // name is missing - login: 'arkid15r', - url: '/members/arkid15r', - }, + ...mockAboutData, + users: mockAboutData.users.map((user) => + user.login === 'arkid15r' ? partialUserData : user + ), }, loading: false, error: null, - } - - ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - if (query === GetProjectMetadataDocument && options?.variables?.key === 'nest') { - return mockProjectData - } else if (query === GetTopContributorsDocument && options?.variables?.key === 'nest') { - return mockTopContributorsData - } else if (options?.variables?.key === 'arkid15r') { - return partialUserData - } else if (options?.variables?.key === 'kasya' || options?.variables?.key === 'mamicidal') { - return mockUserData(options?.variables?.key) - } - return { loading: true } }) await act(async () => { @@ -571,35 +524,9 @@ describe('About Component', () => { }) }) - test('triggers toaster error when GraphQL request fails for project', async () => { + test('triggers toaster error when GraphQL request fails', async () => { ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - if (query === GetProjectMetadataDocument && options?.variables?.key === 'nest') { - return { loading: false, data: null, error: new Error('GraphQL error') } - } - return { - loading: false, - data: { user: { avatarUrl: '', company: '', name: 'Dummy', location: '' } }, - error: null, - } - }) - await act(async () => { - render() - }) - await waitFor(() => { - expect(addToast).toHaveBeenCalledWith({ - color: 'danger', - description: 'GraphQL error', - shouldShowTimeoutProgress: true, - timeout: 5000, - title: 'Server Error', - variant: 'solid', - }) - }) - }) - - test('triggers toaster error when GraphQL request fails for topContributors', async () => { - ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - if (query === GetTopContributorsDocument && options?.variables?.key === 'nest') { + if (options?.variables?.key === 'nest') { return { loading: false, data: null, error: new Error('GraphQL error') } } return { @@ -684,37 +611,4 @@ describe('About Component', () => { expect(screen.getByText('Timeline Event 2')).toBeInTheDocument() expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() }) - - test('triggers toaster error when GraphQL request fails for a leader', async () => { - ;(useQuery as unknown as jest.Mock).mockImplementation((query, options) => { - if (query === GetLeaderDataDocument && options?.variables?.key === 'arkid15r') { - return { loading: false, data: null, error: new Error('GraphQL error for leader') } - } - if (query === GetProjectMetadataDocument) { - return mockProjectData - } - if (query === GetTopContributorsDocument) { - return mockTopContributorsData - } - if (query === GetLeaderDataDocument) { - return mockUserData(options?.variables?.key) - } - return { loading: true } - }) - - await act(async () => { - render() - }) - - await waitFor(() => { - expect(addToast).toHaveBeenCalledWith({ - color: 'danger', - description: 'GraphQL error for leader', - shouldShowTimeoutProgress: true, - timeout: 5000, - title: 'Server Error', - variant: 'solid', - }) - }) - }) }) diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index f447e70caa..0f686b414e 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -11,11 +11,7 @@ import { FaCircleCheck, FaClock, FaScroll, FaBullseye, FaUser, FaUsersGear } fro import { HiUserGroup } from 'react-icons/hi' import { IconWrapper } from 'wrappers/IconWrapper' import { ErrorDisplay, handleAppError } from 'app/global-error' -import { - GetProjectMetadataDocument, - GetTopContributorsDocument, -} from 'types/__generated__/projectQueries.generated' -import { GetLeaderDataDocument } from 'types/__generated__/userQueries.generated' +import { GetAboutPageDataDocument } from 'types/__generated__/aboutQueries.generated' import { technologies, missionContent, @@ -66,50 +62,35 @@ const getMilestoneIcon = (progress: number) => { const About = () => { const { - data: projectMetadataResponse, - loading: projectMetadataLoading, - error: projectMetadataRequestError, - } = useQuery(GetProjectMetadataDocument, { - variables: { key: projectKey }, - }) - - const { - data: topContributorsResponse, - loading: topContributorsLoading, - error: topContributorsRequestError, - } = useQuery(GetTopContributorsDocument, { + data: aboutPageDataResponse, + loading: aboutPageDataLoading, + error: aboutPageDataRequestError, + } = useQuery(GetAboutPageDataDocument, { variables: { - excludedUsernames: Object.keys(leaders), - hasFullName: true, key: projectKey, + leaderLogins: Object.keys(leaders), + hasFullName: true, limit: 24, }, }) - const { leadersData, isLoading: leadersLoading } = useLeadersData() - - // Derive data directly from response to prevent race conditions. - const projectMetadata = projectMetadataResponse?.project - const topContributors = topContributorsResponse?.topContributors - + const projectMetadata = aboutPageDataResponse?.project + const topContributors = aboutPageDataResponse?.topContributors + const leadersData = (aboutPageDataResponse?.users ?? []).filter(Boolean).map((leader) => ({ + description: leaders[leader.login], + memberName: leader.name || leader.login, + member: leader, + })) const [showAllRoadmap, setShowAllRoadmap] = useState(false) const [showAllTimeline, setShowAllTimeline] = useState(false) useEffect(() => { - if (projectMetadataRequestError) { - handleAppError(projectMetadataRequestError) - } - }, [projectMetadataRequestError]) - - useEffect(() => { - if (topContributorsRequestError) { - handleAppError(topContributorsRequestError) + if (aboutPageDataRequestError) { + handleAppError(aboutPageDataRequestError) } - }, [topContributorsRequestError]) - - const isLoading = leadersLoading || projectMetadataLoading || topContributorsLoading + }, [aboutPageDataRequestError]) - if (isLoading) { + if (aboutPageDataLoading) { return } @@ -319,46 +300,4 @@ const About = () => { ) } -const useLeadersData = () => { - const { - data: leader1Data, - loading: loading1, - error: error1, - } = useQuery(GetLeaderDataDocument, { - variables: { key: 'arkid15r' }, - }) - const { - data: leader2Data, - loading: loading2, - error: error2, - } = useQuery(GetLeaderDataDocument, { - variables: { key: 'kasya' }, - }) - const { - data: leader3Data, - loading: loading3, - error: error3, - } = useQuery(GetLeaderDataDocument, { - variables: { key: 'mamicidal' }, - }) - - const isLoading = loading1 || loading2 || loading3 - - useEffect(() => { - if (error1) handleAppError(error1) - if (error2) handleAppError(error2) - if (error3) handleAppError(error3) - }, [error1, error2, error3]) - - const leadersData = [leader1Data?.user, leader2Data?.user, leader3Data?.user] - .filter(Boolean) - .map((user) => ({ - description: leaders[user.login], - memberName: user.name || user.login, - member: user, - })) - - return { leadersData, isLoading } -} - export default About diff --git a/frontend/src/server/queries/aboutQueries.ts b/frontend/src/server/queries/aboutQueries.ts new file mode 100644 index 0000000000..2cc312cf09 --- /dev/null +++ b/frontend/src/server/queries/aboutQueries.ts @@ -0,0 +1,55 @@ +import { gql } from '@apollo/client' + +export const GET_ABOUT_PAGE_DATA = gql` + query GetAboutPageData( + $key: String! + $leaderLogins: [String!]! + $hasFullName: Boolean = false + $limit: Int = 20 + ) { + project(key: $key) { + id + contributorsCount + forksCount + issuesCount + name + starsCount + summary + recentMilestones(limit: 25) { + id + title + url + body + progress + state + } + } + + topContributors( + excludedUsernames: $leaderLogins + hasFullName: $hasFullName + limit: $limit + project: $key + ) { + id + avatarUrl + login + name + } + + users(logins: $leaderLogins) { + id + avatarUrl + login + name + badgeCount + badges { + cssClass + description + id + name + weight + } + } + } +` diff --git a/frontend/src/types/__generated__/aboutQueries.generated.ts b/frontend/src/types/__generated__/aboutQueries.generated.ts new file mode 100644 index 0000000000..1944d604ff --- /dev/null +++ b/frontend/src/types/__generated__/aboutQueries.generated.ts @@ -0,0 +1,15 @@ +import * as Types from './graphql'; + +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; +export type GetAboutPageDataQueryVariables = Types.Exact<{ + key: Types.Scalars['String']['input']; + leaderLogins: Array | Types.Scalars['String']['input']; + hasFullName?: Types.InputMaybe; + limit?: Types.InputMaybe; +}>; + + +export type GetAboutPageDataQuery = { project: { __typename: 'ProjectNode', id: string, contributorsCount: number, forksCount: number, issuesCount: number, name: string, starsCount: number, summary: string, recentMilestones: Array<{ __typename: 'MilestoneNode', id: string, title: string, url: string, body: string, progress: number, state: string }> } | null, topContributors: Array<{ __typename: 'RepositoryContributorNode', id: string, avatarUrl: string, login: string, name: string }>, users: Array<{ __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string, badgeCount: number, badges: Array<{ __typename: 'BadgeNode', cssClass: string, description: string, id: string, name: string, weight: number }> }> }; + + +export const GetAboutPageDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAboutPageData"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"leaderLogins"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"hasFullName"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}},"defaultValue":{"kind":"BooleanValue","value":false}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"20"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"contributorsCount"}},{"kind":"Field","name":{"kind":"Name","value":"forksCount"}},{"kind":"Field","name":{"kind":"Name","value":"issuesCount"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"starsCount"}},{"kind":"Field","name":{"kind":"Name","value":"summary"}},{"kind":"Field","name":{"kind":"Name","value":"recentMilestones"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"25"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"topContributors"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"excludedUsernames"},"value":{"kind":"Variable","name":{"kind":"Name","value":"leaderLogins"}}},{"kind":"Argument","name":{"kind":"Name","value":"hasFullName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"hasFullName"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"project"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"logins"},"value":{"kind":"Variable","name":{"kind":"Name","value":"leaderLogins"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"login"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"badgeCount"}},{"kind":"Field","name":{"kind":"Name","value":"badges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cssClass"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/frontend/src/types/__generated__/graphql.ts b/frontend/src/types/__generated__/graphql.ts index c1eb07d419..88a03df559 100644 --- a/frontend/src/types/__generated__/graphql.ts +++ b/frontend/src/types/__generated__/graphql.ts @@ -725,6 +725,7 @@ export type Query = { topContributors: Array; upcomingEvents: Array; user?: Maybe; + users: Array; }; @@ -939,6 +940,11 @@ export type QueryUserArgs = { login: Scalars['String']['input']; }; + +export type QueryUsersArgs = { + logins: Array; +}; + export type ReleaseNode = Node & { __typename?: 'ReleaseNode'; author?: Maybe;